| OLD | NEW | 
|---|
| 1 # Copyright (C) 2011 Google Inc. | 1 # Copyright 2014 Google Inc. All rights reserved. | 
| 2 # | 2 # | 
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 
| 4 # you may not use this file except in compliance with 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 | 5 # You may obtain a copy of the License at | 
| 6 # | 6 # | 
| 7 #      http://www.apache.org/licenses/LICENSE-2.0 | 7 #      http://www.apache.org/licenses/LICENSE-2.0 | 
| 8 # | 8 # | 
| 9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software | 
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, | 
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
| 12 # See the License for the specific language governing permissions and | 12 # See the License for the specific language governing permissions and | 
| 13 # limitations under the License. | 13 # limitations under the License. | 
| 14 | 14 | 
| 15 """Utilities for reading OAuth 2.0 client secret files. | 15 """Utilities for reading OAuth 2.0 client secret files. | 
| 16 | 16 | 
| 17 A client_secrets.json file contains all the information needed to interact with | 17 A client_secrets.json file contains all the information needed to interact with | 
| 18 an OAuth 2.0 protected service. | 18 an OAuth 2.0 protected service. | 
| 19 """ | 19 """ | 
| 20 | 20 | 
| 21 __author__ = 'jcgregorio@google.com (Joe Gregorio)' | 21 __author__ = 'jcgregorio@google.com (Joe Gregorio)' | 
| 22 | 22 | 
|  | 23 import json | 
|  | 24 import six | 
| 23 | 25 | 
| 24 from anyjson import simplejson |  | 
| 25 | 26 | 
| 26 # Properties that make a client_secrets.json file valid. | 27 # Properties that make a client_secrets.json file valid. | 
| 27 TYPE_WEB = 'web' | 28 TYPE_WEB = 'web' | 
| 28 TYPE_INSTALLED = 'installed' | 29 TYPE_INSTALLED = 'installed' | 
| 29 | 30 | 
| 30 VALID_CLIENT = { | 31 VALID_CLIENT = { | 
| 31     TYPE_WEB: { | 32     TYPE_WEB: { | 
| 32         'required': [ | 33         'required': [ | 
| 33             'client_id', | 34             'client_id', | 
| 34             'client_secret', | 35             'client_secret', | 
| 35             'redirect_uris', | 36             'redirect_uris', | 
| 36             'auth_uri', | 37             'auth_uri', | 
| 37             'token_uri'], | 38             'token_uri', | 
|  | 39         ], | 
| 38         'string': [ | 40         'string': [ | 
| 39             'client_id', | 41             'client_id', | 
| 40             'client_secret' | 42             'client_secret', | 
| 41             ] | 43         ], | 
| 42         }, | 44     }, | 
| 43     TYPE_INSTALLED: { | 45     TYPE_INSTALLED: { | 
| 44         'required': [ | 46         'required': [ | 
| 45             'client_id', | 47             'client_id', | 
| 46             'client_secret', | 48             'client_secret', | 
| 47             'redirect_uris', | 49             'redirect_uris', | 
| 48             'auth_uri', | 50             'auth_uri', | 
| 49             'token_uri'], | 51             'token_uri', | 
|  | 52         ], | 
| 50         'string': [ | 53         'string': [ | 
| 51             'client_id', | 54             'client_id', | 
| 52             'client_secret' | 55             'client_secret', | 
| 53             ] | 56         ], | 
| 54       } | 57     }, | 
| 55     } | 58 } | 
|  | 59 | 
| 56 | 60 | 
| 57 class Error(Exception): | 61 class Error(Exception): | 
| 58   """Base error for this module.""" | 62   """Base error for this module.""" | 
| 59   pass | 63   pass | 
| 60 | 64 | 
| 61 | 65 | 
| 62 class InvalidClientSecretsError(Error): | 66 class InvalidClientSecretsError(Error): | 
| 63   """Format of ClientSecrets file is invalid.""" | 67   """Format of ClientSecrets file is invalid.""" | 
| 64   pass | 68   pass | 
| 65 | 69 | 
| 66 | 70 | 
| 67 def _validate_clientsecrets(obj): | 71 def _validate_clientsecrets(obj): | 
| 68   if obj is None or len(obj) != 1: | 72   _INVALID_FILE_FORMAT_MSG = ( | 
| 69     raise InvalidClientSecretsError('Invalid file format.') | 73     'Invalid file format. See ' | 
| 70   client_type = obj.keys()[0] | 74     'https://developers.google.com/api-client-library/' | 
| 71   if client_type not in VALID_CLIENT.keys(): | 75     'python/guide/aaa_client_secrets') | 
| 72     raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) | 76 | 
|  | 77   if obj is None: | 
|  | 78     raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG) | 
|  | 79   if len(obj) != 1: | 
|  | 80     raise InvalidClientSecretsError( | 
|  | 81       _INVALID_FILE_FORMAT_MSG + ' ' | 
|  | 82       'Expected a JSON object with a single property for a "web" or ' | 
|  | 83       '"installed" application') | 
|  | 84   client_type = tuple(obj)[0] | 
|  | 85   if client_type not in VALID_CLIENT: | 
|  | 86     raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,)) | 
| 73   client_info = obj[client_type] | 87   client_info = obj[client_type] | 
| 74   for prop_name in VALID_CLIENT[client_type]['required']: | 88   for prop_name in VALID_CLIENT[client_type]['required']: | 
| 75     if prop_name not in client_info: | 89     if prop_name not in client_info: | 
| 76       raise InvalidClientSecretsError( | 90       raise InvalidClientSecretsError( | 
| 77         'Missing property "%s" in a client type of "%s".' % (prop_name, | 91         'Missing property "%s" in a client type of "%s".' % (prop_name, | 
| 78                                                            client_type)) | 92                                                            client_type)) | 
| 79   for prop_name in VALID_CLIENT[client_type]['string']: | 93   for prop_name in VALID_CLIENT[client_type]['string']: | 
| 80     if client_info[prop_name].startswith('[['): | 94     if client_info[prop_name].startswith('[['): | 
| 81       raise InvalidClientSecretsError( | 95       raise InvalidClientSecretsError( | 
| 82         'Property "%s" is not configured.' % prop_name) | 96         'Property "%s" is not configured.' % prop_name) | 
| 83   return client_type, client_info | 97   return client_type, client_info | 
| 84 | 98 | 
| 85 | 99 | 
| 86 def load(fp): | 100 def load(fp): | 
| 87   obj = simplejson.load(fp) | 101   obj = json.load(fp) | 
| 88   return _validate_clientsecrets(obj) | 102   return _validate_clientsecrets(obj) | 
| 89 | 103 | 
| 90 | 104 | 
| 91 def loads(s): | 105 def loads(s): | 
| 92   obj = simplejson.loads(s) | 106   obj = json.loads(s) | 
| 93   return _validate_clientsecrets(obj) | 107   return _validate_clientsecrets(obj) | 
| 94 | 108 | 
| 95 | 109 | 
| 96 def loadfile(filename): | 110 def _loadfile(filename): | 
| 97   try: | 111   try: | 
| 98     fp = file(filename, 'r') | 112     with open(filename, 'r') as fp: | 
| 99     try: | 113       obj = json.load(fp) | 
| 100       obj = simplejson.load(fp) |  | 
| 101     finally: |  | 
| 102       fp.close() |  | 
| 103   except IOError: | 114   except IOError: | 
| 104     raise InvalidClientSecretsError('File not found: "%s"' % filename) | 115     raise InvalidClientSecretsError('File not found: "%s"' % filename) | 
| 105   return _validate_clientsecrets(obj) | 116   return _validate_clientsecrets(obj) | 
|  | 117 | 
|  | 118 | 
|  | 119 def loadfile(filename, cache=None): | 
|  | 120   """Loading of client_secrets JSON file, optionally backed by a cache. | 
|  | 121 | 
|  | 122   Typical cache storage would be App Engine memcache service, | 
|  | 123   but you can pass in any other cache client that implements | 
|  | 124   these methods: | 
|  | 125 | 
|  | 126   * ``get(key, namespace=ns)`` | 
|  | 127   * ``set(key, value, namespace=ns)`` | 
|  | 128 | 
|  | 129   Usage:: | 
|  | 130 | 
|  | 131     # without caching | 
|  | 132     client_type, client_info = loadfile('secrets.json') | 
|  | 133     # using App Engine memcache service | 
|  | 134     from google.appengine.api import memcache | 
|  | 135     client_type, client_info = loadfile('secrets.json', cache=memcache) | 
|  | 136 | 
|  | 137   Args: | 
|  | 138     filename: string, Path to a client_secrets.json file on a filesystem. | 
|  | 139     cache: An optional cache service client that implements get() and set() | 
|  | 140       methods. If not specified, the file is always being loaded from | 
|  | 141       a filesystem. | 
|  | 142 | 
|  | 143   Raises: | 
|  | 144     InvalidClientSecretsError: In case of a validation error or some | 
|  | 145       I/O failure. Can happen only on cache miss. | 
|  | 146 | 
|  | 147   Returns: | 
|  | 148     (client_type, client_info) tuple, as _loadfile() normally would. | 
|  | 149     JSON contents is validated only during first load. Cache hits are not | 
|  | 150     validated. | 
|  | 151   """ | 
|  | 152   _SECRET_NAMESPACE = 'oauth2client:secrets#ns' | 
|  | 153 | 
|  | 154   if not cache: | 
|  | 155     return _loadfile(filename) | 
|  | 156 | 
|  | 157   obj = cache.get(filename, namespace=_SECRET_NAMESPACE) | 
|  | 158   if obj is None: | 
|  | 159     client_type, client_info = _loadfile(filename) | 
|  | 160     obj = {client_type: client_info} | 
|  | 161     cache.set(filename, obj, namespace=_SECRET_NAMESPACE) | 
|  | 162 | 
|  | 163   return next(six.iteritems(obj)) | 
| OLD | NEW | 
|---|