clientsecrets.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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.
  14. """Utilities for reading OAuth 2.0 client secret files.
  15. A client_secrets.json file contains all the information needed to interact with
  16. an OAuth 2.0 protected service.
  17. """
  18. import json
  19. import six
  20. # Properties that make a client_secrets.json file valid.
  21. TYPE_WEB = 'web'
  22. TYPE_INSTALLED = 'installed'
  23. VALID_CLIENT = {
  24. TYPE_WEB: {
  25. 'required': [
  26. 'client_id',
  27. 'client_secret',
  28. 'redirect_uris',
  29. 'auth_uri',
  30. 'token_uri',
  31. ],
  32. 'string': [
  33. 'client_id',
  34. 'client_secret',
  35. ],
  36. },
  37. TYPE_INSTALLED: {
  38. 'required': [
  39. 'client_id',
  40. 'client_secret',
  41. 'redirect_uris',
  42. 'auth_uri',
  43. 'token_uri',
  44. ],
  45. 'string': [
  46. 'client_id',
  47. 'client_secret',
  48. ],
  49. },
  50. }
  51. class Error(Exception):
  52. """Base error for this module."""
  53. class InvalidClientSecretsError(Error):
  54. """Format of ClientSecrets file is invalid."""
  55. def _validate_clientsecrets(clientsecrets_dict):
  56. """Validate parsed client secrets from a file.
  57. Args:
  58. clientsecrets_dict: dict, a dictionary holding the client secrets.
  59. Returns:
  60. tuple, a string of the client type and the information parsed
  61. from the file.
  62. """
  63. _INVALID_FILE_FORMAT_MSG = (
  64. 'Invalid file format. See '
  65. 'https://developers.google.com/api-client-library/'
  66. 'python/guide/aaa_client_secrets')
  67. if clientsecrets_dict is None:
  68. raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG)
  69. try:
  70. (client_type, client_info), = clientsecrets_dict.items()
  71. except (ValueError, AttributeError):
  72. raise InvalidClientSecretsError(
  73. _INVALID_FILE_FORMAT_MSG + ' '
  74. 'Expected a JSON object with a single property for a "web" or '
  75. '"installed" application')
  76. if client_type not in VALID_CLIENT:
  77. raise InvalidClientSecretsError(
  78. 'Unknown client type: {0}.'.format(client_type))
  79. for prop_name in VALID_CLIENT[client_type]['required']:
  80. if prop_name not in client_info:
  81. raise InvalidClientSecretsError(
  82. 'Missing property "{0}" in a client type of "{1}".'.format(
  83. prop_name, client_type))
  84. for prop_name in VALID_CLIENT[client_type]['string']:
  85. if client_info[prop_name].startswith('[['):
  86. raise InvalidClientSecretsError(
  87. 'Property "{0}" is not configured.'.format(prop_name))
  88. return client_type, client_info
  89. def load(fp):
  90. obj = json.load(fp)
  91. return _validate_clientsecrets(obj)
  92. def loads(s):
  93. obj = json.loads(s)
  94. return _validate_clientsecrets(obj)
  95. def _loadfile(filename):
  96. try:
  97. with open(filename, 'r') as fp:
  98. obj = json.load(fp)
  99. except IOError as exc:
  100. raise InvalidClientSecretsError('Error opening file', exc.filename,
  101. exc.strerror, exc.errno)
  102. return _validate_clientsecrets(obj)
  103. def loadfile(filename, cache=None):
  104. """Loading of client_secrets JSON file, optionally backed by a cache.
  105. Typical cache storage would be App Engine memcache service,
  106. but you can pass in any other cache client that implements
  107. these methods:
  108. * ``get(key, namespace=ns)``
  109. * ``set(key, value, namespace=ns)``
  110. Usage::
  111. # without caching
  112. client_type, client_info = loadfile('secrets.json')
  113. # using App Engine memcache service
  114. from google.appengine.api import memcache
  115. client_type, client_info = loadfile('secrets.json', cache=memcache)
  116. Args:
  117. filename: string, Path to a client_secrets.json file on a filesystem.
  118. cache: An optional cache service client that implements get() and set()
  119. methods. If not specified, the file is always being loaded from
  120. a filesystem.
  121. Raises:
  122. InvalidClientSecretsError: In case of a validation error or some
  123. I/O failure. Can happen only on cache miss.
  124. Returns:
  125. (client_type, client_info) tuple, as _loadfile() normally would.
  126. JSON contents is validated only during first load. Cache hits are not
  127. validated.
  128. """
  129. _SECRET_NAMESPACE = 'oauth2client:secrets#ns'
  130. if not cache:
  131. return _loadfile(filename)
  132. obj = cache.get(filename, namespace=_SECRET_NAMESPACE)
  133. if obj is None:
  134. client_type, client_info = _loadfile(filename)
  135. obj = {client_type: client_info}
  136. cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
  137. return next(six.iteritems(obj))