Skip to content

Commit 90d1d8e

Browse files
committed
auth: add dictionary storage
Allows not to save anything to disk, which is useful overall but especially useful when working with env vars, which could be sensitive so saving their value to disk is undesirable.
1 parent 3fd1aa7 commit 90d1d8e

File tree

3 files changed

+73
-6
lines changed

3 files changed

+73
-6
lines changed

docs/oauth.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ These are all the possible fields of a *settings.yaml* file:
7474
save_credentials: {{bool}}
7575
save_credentials_backend: {{str}}
7676
save_credentials_file: {{str}}
77+
save_credentials_dict: {{dict}}
78+
save_credentials_key: {{str}}
7779
7880
get_refresh_token: {{bool}}
7981
@@ -91,8 +93,10 @@ Fields explained:
9193
:client_config['redirect_uri'] (str): Redirection endpoint URI. **Default**: 'urn:ietf:wg:oauth:2.0:oob'. **Required**: No.
9294
:client_config['revoke_uri'] (str): Revoke endpoint URI. **Default**: None. **Required**: No.
9395
:save_credentials (bool): True if you want to save credentials. **Default**: False. **Required**: No.
94-
:save_credentials_backend (str): Backend to save credentials to. 'file' is the only valid value for now. **Default**: 'file'. **Required**: No.
96+
:save_credentials_backend (str): Backend to save credentials to. 'file' and 'dictionary' are the only valid values for now. **Default**: 'file'. **Required**: No.
9597
:save_credentials_file (str): Destination of credentials file. **Required**: Yes, only if *save_credentials_backend* is 'file'.
98+
:save_credentials_dict (dict): Dict to use for storing credentials. **Required**: Yes, only if *save_credentials_backend* is 'dictionary'.
99+
:save_credentials_key (str): Key within the *save_credentials_dict* to store the credentials in. **Required**: Yes, only if *save_credentials_backend* is 'dictionary'.
96100
:get_refresh_token (bool): True if you want to retrieve refresh token along with access token. **Default**: False. **Required**: No.
97101
:oauth_scope (list of str): OAuth scope to authenticate. **Default**: ['https://www.googleapis.com/auth/drive']. **Required**: No.
98102

pydrive2/auth.py

+41-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from oauth2client.client import AccessTokenRefreshError
1111
from oauth2client.client import OAuth2WebServerFlow
1212
from oauth2client.client import OOB_CALLBACK_URN
13+
from oauth2client.contrib.dictionary_storage import DictionaryStorage
1314
from oauth2client.file import Storage
1415
from oauth2client.tools import ClientRedirectHandler
1516
from oauth2client.tools import ClientRedirectServer
@@ -197,9 +198,9 @@ def __init__(
197198
self.settings = settings or self.DEFAULT_SETTINGS
198199
ValidateSettings(self.settings)
199200

200-
self._storages = self._InitializeStoragesFromSettings()
201-
# Only one (`file`) backend is supported now
202-
self._default_storage = self._storages["file"]
201+
storages, default = self._InitializeStoragesFromSettings()
202+
self._storages = storages
203+
self._default_storage = default
203204

204205
@property
205206
def access_token_expired(self):
@@ -337,7 +338,7 @@ def ServiceAuth(self):
337338
)
338339

339340
def _InitializeStoragesFromSettings(self):
340-
result = {"file": None}
341+
result = {"file": None, "dictionary": None}
341342
backend = self.settings.get("save_credentials_backend")
342343
save_credentials = self.settings.get("save_credentials")
343344
if backend == "file":
@@ -347,11 +348,16 @@ def _InitializeStoragesFromSettings(self):
347348
"Please specify credentials file to read"
348349
)
349350
result[backend] = Storage(credentials_file)
351+
elif backend == "dictionary":
352+
result[backend] = DictionaryStorage(
353+
self.settings["save_credentials_dict"],
354+
self.settings["save_credentials_key"],
355+
)
350356
elif save_credentials:
351357
raise InvalidConfigError(
352358
"Unknown save_credentials_backend: %s" % backend
353359
)
354-
return result
360+
return result, result.get(backend)
355361

356362
def LoadCredentials(self, backend=None):
357363
"""Loads credentials or create empty credentials if it doesn't exist.
@@ -366,6 +372,8 @@ def LoadCredentials(self, backend=None):
366372
raise InvalidConfigError("Please specify credential backend")
367373
if backend == "file":
368374
self.LoadCredentialsFile()
375+
elif backend == "dictionary":
376+
self._LoadCredentialsDictionary()
369377
else:
370378
raise InvalidConfigError("Unknown save_credentials_backend")
371379

@@ -399,6 +407,19 @@ def LoadCredentialsFile(self, credentials_file=None):
399407
if self.credentials:
400408
self.credentials.set_store(self._default_storage)
401409

410+
def _LoadCredentialsDictionary(self):
411+
self._default_storage = self._storages["dictionary"]
412+
if self._default_storage is None:
413+
raise InvalidConfigError(
414+
"Backend `dictionary` is not configured, specify "
415+
"credentials dict and key to read in the settings file"
416+
)
417+
418+
self.credentials = self._default_storage.get()
419+
420+
if self.credentials:
421+
self.credentials.set_store(self._default_storage)
422+
402423
def SaveCredentials(self, backend=None):
403424
"""Saves credentials according to specified backend.
404425
@@ -415,6 +436,8 @@ def SaveCredentials(self, backend=None):
415436
raise InvalidConfigError("Please specify credential backend")
416437
if backend == "file":
417438
self.SaveCredentialsFile()
439+
elif backend == "dictionary":
440+
self._SaveCredentialsDictionary()
418441
else:
419442
raise InvalidConfigError("Unknown save_credentials_backend")
420443

@@ -446,6 +469,19 @@ def SaveCredentialsFile(self, credentials_file=None):
446469
"Credentials file cannot be symbolic link"
447470
)
448471

472+
def _SaveCredentialsDictionary(self):
473+
if self.credentials is None:
474+
raise InvalidCredentialsError("No credentials to save")
475+
476+
storage = self._storages["dictionary"]
477+
if storage is None:
478+
raise InvalidConfigError(
479+
"Backend `dictionary` is not configured, specify "
480+
"credentials dict and key to write in the settings file"
481+
)
482+
483+
storage.put(self.credentials)
484+
449485
def LoadClientConfig(self, backend=None):
450486
"""Loads client configuration according to specified backend.
451487

pydrive2/test/test_oauth.py

+27
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,33 @@ def test_09_SaveLoadCredentialsUsesDefaultStorage(mocker):
125125
assert spy.call_count == 0
126126

127127

128+
def test_10_ServiceAuthFromSavedCredentialsDictionary():
129+
creds_dict = {}
130+
settings = {
131+
"client_config_backend": "service",
132+
"service_config": {
133+
"client_json_file_path": "/tmp/pydrive2/credentials.json",
134+
},
135+
"oauth_scope": ["https://www.googleapis.com/auth/drive"],
136+
"save_credentials": True,
137+
"save_credentials_backend": "dictionary",
138+
"save_credentials_dict": creds_dict,
139+
"save_credentials_key": "creds",
140+
}
141+
ga = GoogleAuth(settings=settings)
142+
ga.ServiceAuth()
143+
assert not ga.access_token_expired
144+
assert creds_dict
145+
first_creds_dict = creds_dict.copy()
146+
# Secondary auth should be made only using the previously saved
147+
# login info
148+
ga = GoogleAuth(settings=settings)
149+
ga.ServiceAuth()
150+
assert not ga.access_token_expired
151+
assert creds_dict == first_creds_dict
152+
time.sleep(1)
153+
154+
128155
def CheckCredentialsFile(credentials, no_file=False):
129156
ga = GoogleAuth(settings_file_path("test_oauth_default.yaml"))
130157
ga.LoadCredentialsFile(credentials)

0 commit comments

Comments
 (0)