Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(317)

Side by Side Diff: appengine/chromium_build_logs/third_party/oauth2client/multistore_file.py

Issue 1260293009: make version of ts_mon compatible with appengine (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: clean up code Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2011 Google Inc. All Rights Reserved. 1 # Copyright 2014 Google Inc. All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
2 14
3 """Multi-credential file store with lock support. 15 """Multi-credential file store with lock support.
4 16
5 This module implements a JSON credential store where multiple 17 This module implements a JSON credential store where multiple
6 credentials can be stored in one file. That file supports locking 18 credentials can be stored in one file. That file supports locking
7 both in a single process and across processes. 19 both in a single process and across processes.
8 20
9 The credential themselves are keyed off of: 21 The credential themselves are keyed off of:
22
10 * client_id 23 * client_id
11 * user_agent 24 * user_agent
12 * scope 25 * scope
13 26
14 The format of the stored data is like so: 27 The format of the stored data is like so::
15 { 28
16 'file_version': 1, 29 {
17 'data': [ 30 'file_version': 1,
18 { 31 'data': [
19 'key': { 32 {
20 'clientId': '<client id>', 33 'key': {
21 'userAgent': '<user agent>', 34 'clientId': '<client id>',
22 'scope': '<scope>' 35 'userAgent': '<user agent>',
23 }, 36 'scope': '<scope>'
24 'credential': { 37 },
25 # JSON serialized Credentials. 38 'credential': {
39 # JSON serialized Credentials.
40 }
26 } 41 }
27 } 42 ]
28 ] 43 }
29 } 44
30 """ 45 """
31 46
32 __author__ = 'jbeda@google.com (Joe Beda)' 47 __author__ = 'jbeda@google.com (Joe Beda)'
33 48
34 import base64
35 import errno 49 import errno
36 import fcntl 50 import json
37 import logging 51 import logging
38 import os 52 import os
39 import threading 53 import threading
40 54
41 from anyjson import simplejson 55 from oauth2client.client import Credentials
42 from client import Storage as BaseStorage 56 from oauth2client.client import Storage as BaseStorage
43 from client import Credentials 57 from oauth2client import util
58 from oauth2client.locked_file import LockedFile
44 59
45 logger = logging.getLogger(__name__) 60 logger = logging.getLogger(__name__)
46 61
47 # A dict from 'filename'->_MultiStore instances 62 # A dict from 'filename'->_MultiStore instances
48 _multistores = {} 63 _multistores = {}
49 _multistores_lock = threading.Lock() 64 _multistores_lock = threading.Lock()
50 65
51 66
52 class Error(Exception): 67 class Error(Exception):
53 """Base error for this module.""" 68 """Base error for this module."""
54 pass
55 69
56 70
57 class NewerCredentialStoreError(Error): 71 class NewerCredentialStoreError(Error):
58 """The credential store is a newer version that supported.""" 72 """The credential store is a newer version than supported."""
59 pass
60 73
61 74
75 @util.positional(4)
62 def get_credential_storage(filename, client_id, user_agent, scope, 76 def get_credential_storage(filename, client_id, user_agent, scope,
63 warn_on_readonly=True): 77 warn_on_readonly=True):
64 """Get a Storage instance for a credential. 78 """Get a Storage instance for a credential.
65 79
66 Args: 80 Args:
67 filename: The JSON file storing a set of credentials 81 filename: The JSON file storing a set of credentials
68 client_id: The client_id for the credential 82 client_id: The client_id for the credential
69 user_agent: The user agent for the credential 83 user_agent: The user agent for the credential
70 scope: string or list of strings, Scope(s) being requested 84 scope: string or iterable of strings, Scope(s) being requested
71 warn_on_readonly: if True, log a warning if the store is readonly 85 warn_on_readonly: if True, log a warning if the store is readonly
72 86
73 Returns: 87 Returns:
74 An object derived from client.Storage for getting/setting the 88 An object derived from client.Storage for getting/setting the
75 credential. 89 credential.
76 """ 90 """
77 filename = os.path.realpath(os.path.expanduser(filename)) 91 # Recreate the legacy key with these specific parameters
92 key = {'clientId': client_id, 'userAgent': user_agent,
93 'scope': util.scopes_to_string(scope)}
94 return get_credential_storage_custom_key(
95 filename, key, warn_on_readonly=warn_on_readonly)
96
97
98 @util.positional(2)
99 def get_credential_storage_custom_string_key(
100 filename, key_string, warn_on_readonly=True):
101 """Get a Storage instance for a credential using a single string as a key.
102
103 Allows you to provide a string as a custom key that will be used for
104 credential storage and retrieval.
105
106 Args:
107 filename: The JSON file storing a set of credentials
108 key_string: A string to use as the key for storing this credential.
109 warn_on_readonly: if True, log a warning if the store is readonly
110
111 Returns:
112 An object derived from client.Storage for getting/setting the
113 credential.
114 """
115 # Create a key dictionary that can be used
116 key_dict = {'key': key_string}
117 return get_credential_storage_custom_key(
118 filename, key_dict, warn_on_readonly=warn_on_readonly)
119
120
121 @util.positional(2)
122 def get_credential_storage_custom_key(
123 filename, key_dict, warn_on_readonly=True):
124 """Get a Storage instance for a credential using a dictionary as a key.
125
126 Allows you to provide a dictionary as a custom key that will be used for
127 credential storage and retrieval.
128
129 Args:
130 filename: The JSON file storing a set of credentials
131 key_dict: A dictionary to use as the key for storing this credential. There
132 is no ordering of the keys in the dictionary. Logically equivalent
133 dictionaries will produce equivalent storage keys.
134 warn_on_readonly: if True, log a warning if the store is readonly
135
136 Returns:
137 An object derived from client.Storage for getting/setting the
138 credential.
139 """
140 multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
141 key = util.dict_to_tuple_key(key_dict)
142 return multistore._get_storage(key)
143
144
145 @util.positional(1)
146 def get_all_credential_keys(filename, warn_on_readonly=True):
147 """Gets all the registered credential keys in the given Multistore.
148
149 Args:
150 filename: The JSON file storing a set of credentials
151 warn_on_readonly: if True, log a warning if the store is readonly
152
153 Returns:
154 A list of the credential keys present in the file. They are returned as
155 dictionaries that can be passed into get_credential_storage_custom_key to
156 get the actual credentials.
157 """
158 multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
159 multistore._lock()
160 try:
161 return multistore._get_all_credential_keys()
162 finally:
163 multistore._unlock()
164
165
166 @util.positional(1)
167 def _get_multistore(filename, warn_on_readonly=True):
168 """A helper method to initialize the multistore with proper locking.
169
170 Args:
171 filename: The JSON file storing a set of credentials
172 warn_on_readonly: if True, log a warning if the store is readonly
173
174 Returns:
175 A multistore object
176 """
177 filename = os.path.expanduser(filename)
78 _multistores_lock.acquire() 178 _multistores_lock.acquire()
79 try: 179 try:
80 multistore = _multistores.setdefault( 180 multistore = _multistores.setdefault(
81 filename, _MultiStore(filename, warn_on_readonly)) 181 filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
82 finally: 182 finally:
83 _multistores_lock.release() 183 _multistores_lock.release()
84 if type(scope) is list: 184 return multistore
85 scope = ' '.join(scope)
86 return multistore._get_storage(client_id, user_agent, scope)
87 185
88 186
89 class _MultiStore(object): 187 class _MultiStore(object):
90 """A file backed store for multiple credentials.""" 188 """A file backed store for multiple credentials."""
91 189
190 @util.positional(2)
92 def __init__(self, filename, warn_on_readonly=True): 191 def __init__(self, filename, warn_on_readonly=True):
93 """Initialize the class. 192 """Initialize the class.
94 193
95 This will create the file if necessary. 194 This will create the file if necessary.
96 """ 195 """
97 self._filename = filename 196 self._file = LockedFile(filename, 'r+', 'r')
98 self._thread_lock = threading.Lock() 197 self._thread_lock = threading.Lock()
99 self._file_handle = None
100 self._read_only = False 198 self._read_only = False
101 self._warn_on_readonly = warn_on_readonly 199 self._warn_on_readonly = warn_on_readonly
102 200
103 self._create_file_if_needed() 201 self._create_file_if_needed()
104 202
105 # Cache of deserialized store. This is only valid after the 203 # Cache of deserialized store. This is only valid after the
106 # _MultiStore is locked or _refresh_data_cache is called. This is 204 # _MultiStore is locked or _refresh_data_cache is called. This is
107 # of the form of: 205 # of the form of:
108 # 206 #
109 # (client_id, user_agent, scope) -> OAuth2Credential 207 # ((key, value), (key, value)...) -> OAuth2Credential
110 # 208 #
111 # If this is None, then the store hasn't been read yet. 209 # If this is None, then the store hasn't been read yet.
112 self._data = None 210 self._data = None
113 211
114 class _Storage(BaseStorage): 212 class _Storage(BaseStorage):
115 """A Storage object that knows how to read/write a single credential.""" 213 """A Storage object that knows how to read/write a single credential."""
116 214
117 def __init__(self, multistore, client_id, user_agent, scope): 215 def __init__(self, multistore, key):
118 self._multistore = multistore 216 self._multistore = multistore
119 self._client_id = client_id 217 self._key = key
120 self._user_agent = user_agent
121 self._scope = scope
122 218
123 def acquire_lock(self): 219 def acquire_lock(self):
124 """Acquires any lock necessary to access this Storage. 220 """Acquires any lock necessary to access this Storage.
125 221
126 This lock is not reentrant. 222 This lock is not reentrant.
127 """ 223 """
128 self._multistore._lock() 224 self._multistore._lock()
129 225
130 def release_lock(self): 226 def release_lock(self):
131 """Release the Storage lock. 227 """Release the Storage lock.
132 228
133 Trying to release a lock that isn't held will result in a 229 Trying to release a lock that isn't held will result in a
134 RuntimeError. 230 RuntimeError.
135 """ 231 """
136 self._multistore._unlock() 232 self._multistore._unlock()
137 233
138 def locked_get(self): 234 def locked_get(self):
139 """Retrieve credential. 235 """Retrieve credential.
140 236
141 The Storage lock must be held when this is called. 237 The Storage lock must be held when this is called.
142 238
143 Returns: 239 Returns:
144 oauth2client.client.Credentials 240 oauth2client.client.Credentials
145 """ 241 """
146 credential = self._multistore._get_credential( 242 credential = self._multistore._get_credential(self._key)
147 self._client_id, self._user_agent, self._scope)
148 if credential: 243 if credential:
149 credential.set_store(self) 244 credential.set_store(self)
150 return credential 245 return credential
151 246
152 def locked_put(self, credentials): 247 def locked_put(self, credentials):
153 """Write a credential. 248 """Write a credential.
154 249
155 The Storage lock must be held when this is called. 250 The Storage lock must be held when this is called.
156 251
157 Args: 252 Args:
158 credentials: Credentials, the credentials to store. 253 credentials: Credentials, the credentials to store.
159 """ 254 """
160 self._multistore._update_credential(credentials, self._scope) 255 self._multistore._update_credential(self._key, credentials)
256
257 def locked_delete(self):
258 """Delete a credential.
259
260 The Storage lock must be held when this is called.
261
262 Args:
263 credentials: Credentials, the credentials to store.
264 """
265 self._multistore._delete_credential(self._key)
161 266
162 def _create_file_if_needed(self): 267 def _create_file_if_needed(self):
163 """Create an empty file if necessary. 268 """Create an empty file if necessary.
164 269
165 This method will not initialize the file. Instead it implements a 270 This method will not initialize the file. Instead it implements a
166 simple version of "touch" to ensure the file has been created. 271 simple version of "touch" to ensure the file has been created.
167 """ 272 """
168 if not os.path.exists(self._filename): 273 if not os.path.exists(self._file.filename()):
169 old_umask = os.umask(0177) 274 old_umask = os.umask(0o177)
170 try: 275 try:
171 open(self._filename, 'a+b').close() 276 open(self._file.filename(), 'a+b').close()
172 finally: 277 finally:
173 os.umask(old_umask) 278 os.umask(old_umask)
174 279
175 def _lock(self): 280 def _lock(self):
176 """Lock the entire multistore.""" 281 """Lock the entire multistore."""
177 self._thread_lock.acquire() 282 self._thread_lock.acquire()
178 # Check to see if the file is writeable.
179 try: 283 try:
180 self._file_handle = open(self._filename, 'r+b') 284 self._file.open_and_lock()
181 fcntl.lockf(self._file_handle.fileno(), fcntl.LOCK_EX) 285 except IOError as e:
182 except IOError, e: 286 if e.errno == errno.ENOSYS:
183 if e.errno != errno.EACCES: 287 logger.warn('File system does not support locking the credentials '
184 raise e 288 'file.')
185 self._file_handle = open(self._filename, 'rb') 289 elif e.errno == errno.ENOLCK:
290 logger.warn('File system is out of resources for writing the '
291 'credentials file (is your disk full?).')
292 else:
293 raise
294 if not self._file.is_locked():
186 self._read_only = True 295 self._read_only = True
187 if self._warn_on_readonly: 296 if self._warn_on_readonly:
188 logger.warn('The credentials file (%s) is not writable. Opening in ' 297 logger.warn('The credentials file (%s) is not writable. Opening in '
189 'read-only mode. Any refreshed credentials will only be ' 298 'read-only mode. Any refreshed credentials will only be '
190 'valid for this run.' % self._filename) 299 'valid for this run.', self._file.filename())
191 if os.path.getsize(self._filename) == 0: 300 if os.path.getsize(self._file.filename()) == 0:
192 logger.debug('Initializing empty multistore file') 301 logger.debug('Initializing empty multistore file')
193 # The multistore is empty so write out an empty file. 302 # The multistore is empty so write out an empty file.
194 self._data = {} 303 self._data = {}
195 self._write() 304 self._write()
196 elif not self._read_only or self._data is None: 305 elif not self._read_only or self._data is None:
197 # Only refresh the data if we are read/write or we haven't 306 # Only refresh the data if we are read/write or we haven't
198 # cached the data yet. If we are readonly, we assume is isn't 307 # cached the data yet. If we are readonly, we assume is isn't
199 # changing out from under us and that we only have to read it 308 # changing out from under us and that we only have to read it
200 # once. This prevents us from whacking any new access keys that 309 # once. This prevents us from whacking any new access keys that
201 # we have cached in memory but were unable to write out. 310 # we have cached in memory but were unable to write out.
202 self._refresh_data_cache() 311 self._refresh_data_cache()
203 312
204 def _unlock(self): 313 def _unlock(self):
205 """Release the lock on the multistore.""" 314 """Release the lock on the multistore."""
206 if not self._read_only: 315 self._file.unlock_and_close()
207 fcntl.lockf(self._file_handle.fileno(), fcntl.LOCK_UN)
208 self._file_handle.close()
209 self._thread_lock.release() 316 self._thread_lock.release()
210 317
211 def _locked_json_read(self): 318 def _locked_json_read(self):
212 """Get the raw content of the multistore file. 319 """Get the raw content of the multistore file.
213 320
214 The multistore must be locked when this is called. 321 The multistore must be locked when this is called.
215 322
216 Returns: 323 Returns:
217 The contents of the multistore decoded as JSON. 324 The contents of the multistore decoded as JSON.
218 """ 325 """
219 assert self._thread_lock.locked() 326 assert self._thread_lock.locked()
220 self._file_handle.seek(0) 327 self._file.file_handle().seek(0)
221 return simplejson.load(self._file_handle) 328 return json.load(self._file.file_handle())
222 329
223 def _locked_json_write(self, data): 330 def _locked_json_write(self, data):
224 """Write a JSON serializable data structure to the multistore. 331 """Write a JSON serializable data structure to the multistore.
225 332
226 The multistore must be locked when this is called. 333 The multistore must be locked when this is called.
227 334
228 Args: 335 Args:
229 data: The data to be serialized and written. 336 data: The data to be serialized and written.
230 """ 337 """
231 assert self._thread_lock.locked() 338 assert self._thread_lock.locked()
232 if self._read_only: 339 if self._read_only:
233 return 340 return
234 self._file_handle.seek(0) 341 self._file.file_handle().seek(0)
235 simplejson.dump(data, self._file_handle, sort_keys=True, indent=2) 342 json.dump(data, self._file.file_handle(), sort_keys=True, indent=2, separato rs=(',', ': '))
236 self._file_handle.truncate() 343 self._file.file_handle().truncate()
237 344
238 def _refresh_data_cache(self): 345 def _refresh_data_cache(self):
239 """Refresh the contents of the multistore. 346 """Refresh the contents of the multistore.
240 347
241 The multistore must be locked when this is called. 348 The multistore must be locked when this is called.
242 349
243 Raises: 350 Raises:
244 NewerCredentialStoreError: Raised when a newer client has written the 351 NewerCredentialStoreError: Raised when a newer client has written the
245 store. 352 store.
246 """ 353 """
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
281 """Load a credential from our JSON serialization. 388 """Load a credential from our JSON serialization.
282 389
283 Args: 390 Args:
284 cred_entry: A dict entry from the data member of our format 391 cred_entry: A dict entry from the data member of our format
285 392
286 Returns: 393 Returns:
287 (key, cred) where the key is the key tuple and the cred is the 394 (key, cred) where the key is the key tuple and the cred is the
288 OAuth2Credential object. 395 OAuth2Credential object.
289 """ 396 """
290 raw_key = cred_entry['key'] 397 raw_key = cred_entry['key']
291 client_id = raw_key['clientId'] 398 key = util.dict_to_tuple_key(raw_key)
292 user_agent = raw_key['userAgent']
293 scope = raw_key['scope']
294 key = (client_id, user_agent, scope)
295 credential = None 399 credential = None
296 credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credenti al'])) 400 credential = Credentials.new_from_json(json.dumps(cred_entry['credential']))
297 return (key, credential) 401 return (key, credential)
298 402
299 def _write(self): 403 def _write(self):
300 """Write the cached data back out. 404 """Write the cached data back out.
301 405
302 The multistore must be locked. 406 The multistore must be locked.
303 """ 407 """
304 raw_data = {'file_version': 1} 408 raw_data = {'file_version': 1}
305 raw_creds = [] 409 raw_creds = []
306 raw_data['data'] = raw_creds 410 raw_data['data'] = raw_creds
307 for (cred_key, cred) in self._data.items(): 411 for (cred_key, cred) in self._data.items():
308 raw_key = { 412 raw_key = dict(cred_key)
309 'clientId': cred_key[0], 413 raw_cred = json.loads(cred.to_json())
310 'userAgent': cred_key[1],
311 'scope': cred_key[2]
312 }
313 raw_cred = simplejson.loads(cred.to_json())
314 raw_creds.append({'key': raw_key, 'credential': raw_cred}) 414 raw_creds.append({'key': raw_key, 'credential': raw_cred})
315 self._locked_json_write(raw_data) 415 self._locked_json_write(raw_data)
316 416
317 def _get_credential(self, client_id, user_agent, scope): 417 def _get_all_credential_keys(self):
418 """Gets all the registered credential keys in the multistore.
419
420 Returns:
421 A list of dictionaries corresponding to all the keys currently registered
422 """
423 return [dict(key) for key in self._data.keys()]
424
425 def _get_credential(self, key):
318 """Get a credential from the multistore. 426 """Get a credential from the multistore.
319 427
320 The multistore must be locked. 428 The multistore must be locked.
321 429
322 Args: 430 Args:
323 client_id: The client_id for the credential 431 key: The key used to retrieve the credential
324 user_agent: The user agent for the credential
325 scope: A string for the scope(s) being requested
326 432
327 Returns: 433 Returns:
328 The credential specified or None if not present 434 The credential specified or None if not present
329 """ 435 """
330 key = (client_id, user_agent, scope)
331
332 return self._data.get(key, None) 436 return self._data.get(key, None)
333 437
334 def _update_credential(self, cred, scope): 438 def _update_credential(self, key, cred):
335 """Update a credential and write the multistore. 439 """Update a credential and write the multistore.
336 440
337 This must be called when the multistore is locked. 441 This must be called when the multistore is locked.
338 442
339 Args: 443 Args:
444 key: The key used to retrieve the credential
340 cred: The OAuth2Credential to update/set 445 cred: The OAuth2Credential to update/set
341 scope: The scope(s) that this credential covers
342 """ 446 """
343 key = (cred.client_id, cred.user_agent, scope)
344 self._data[key] = cred 447 self._data[key] = cred
345 self._write() 448 self._write()
346 449
347 def _get_storage(self, client_id, user_agent, scope): 450 def _delete_credential(self, key):
451 """Delete a credential and write the multistore.
452
453 This must be called when the multistore is locked.
454
455 Args:
456 key: The key used to retrieve the credential
457 """
458 try:
459 del self._data[key]
460 except KeyError:
461 pass
462 self._write()
463
464 def _get_storage(self, key):
348 """Get a Storage object to get/set a credential. 465 """Get a Storage object to get/set a credential.
349 466
350 This Storage is a 'view' into the multistore. 467 This Storage is a 'view' into the multistore.
351 468
352 Args: 469 Args:
353 client_id: The client_id for the credential 470 key: The key used to retrieve the credential
354 user_agent: The user agent for the credential
355 scope: A string for the scope(s) being requested
356 471
357 Returns: 472 Returns:
358 A Storage object that can be used to get/set this cred 473 A Storage object that can be used to get/set this cred
359 """ 474 """
360 return self._Storage(self, client_id, user_agent, scope) 475 return self._Storage(self, key)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698