app_engine.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # Copyright 2016 Google LLC
  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. """Google App Engine standard environment support.
  15. This module provides authentication and signing for applications running on App
  16. Engine in the standard environment using the `App Identity API`_.
  17. .. _App Identity API:
  18. https://cloud.google.com/appengine/docs/python/appidentity/
  19. """
  20. import datetime
  21. from google.auth import _helpers
  22. from google.auth import credentials
  23. from google.auth import crypt
  24. from google.auth import exceptions
  25. # pytype: disable=import-error
  26. try:
  27. from google.appengine.api import app_identity # type: ignore
  28. except ImportError:
  29. app_identity = None # type: ignore
  30. # pytype: enable=import-error
  31. class Signer(crypt.Signer):
  32. """Signs messages using the App Engine App Identity service.
  33. This can be used in place of :class:`google.auth.crypt.Signer` when
  34. running in the App Engine standard environment.
  35. """
  36. @property
  37. def key_id(self):
  38. """Optional[str]: The key ID used to identify this private key.
  39. .. warning::
  40. This is always ``None``. The key ID used by App Engine can not
  41. be reliably determined ahead of time.
  42. """
  43. return None
  44. @_helpers.copy_docstring(crypt.Signer)
  45. def sign(self, message):
  46. message = _helpers.to_bytes(message)
  47. _, signature = app_identity.sign_blob(message)
  48. return signature
  49. def get_project_id():
  50. """Gets the project ID for the current App Engine application.
  51. Returns:
  52. str: The project ID
  53. Raises:
  54. google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
  55. """
  56. # pylint: disable=missing-raises-doc
  57. # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
  58. # realize it's a valid alias.
  59. if app_identity is None:
  60. raise exceptions.OSError("The App Engine APIs are not available.")
  61. return app_identity.get_application_id()
  62. class Credentials(
  63. credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject
  64. ):
  65. """App Engine standard environment credentials.
  66. These credentials use the App Engine App Identity API to obtain access
  67. tokens.
  68. """
  69. def __init__(
  70. self,
  71. scopes=None,
  72. default_scopes=None,
  73. service_account_id=None,
  74. quota_project_id=None,
  75. ):
  76. """
  77. Args:
  78. scopes (Sequence[str]): Scopes to request from the App Identity
  79. API.
  80. default_scopes (Sequence[str]): Default scopes passed by a
  81. Google client library. Use 'scopes' for user-defined scopes.
  82. service_account_id (str): The service account ID passed into
  83. :func:`google.appengine.api.app_identity.get_access_token`.
  84. If not specified, the default application service account
  85. ID will be used.
  86. quota_project_id (Optional[str]): The project ID used for quota
  87. and billing.
  88. Raises:
  89. google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
  90. """
  91. # pylint: disable=missing-raises-doc
  92. # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
  93. # realize it's a valid alias.
  94. if app_identity is None:
  95. raise exceptions.OSError("The App Engine APIs are not available.")
  96. super(Credentials, self).__init__()
  97. self._scopes = scopes
  98. self._default_scopes = default_scopes
  99. self._service_account_id = service_account_id
  100. self._signer = Signer()
  101. self._quota_project_id = quota_project_id
  102. @_helpers.copy_docstring(credentials.Credentials)
  103. def refresh(self, request):
  104. scopes = self._scopes if self._scopes is not None else self._default_scopes
  105. # pylint: disable=unused-argument
  106. token, ttl = app_identity.get_access_token(scopes, self._service_account_id)
  107. expiry = datetime.datetime.utcfromtimestamp(ttl)
  108. self.token, self.expiry = token, expiry
  109. @property
  110. def service_account_email(self):
  111. """The service account email."""
  112. if self._service_account_id is None:
  113. self._service_account_id = app_identity.get_service_account_name()
  114. return self._service_account_id
  115. @property
  116. def requires_scopes(self):
  117. """Checks if the credentials requires scopes.
  118. Returns:
  119. bool: True if there are no scopes set otherwise False.
  120. """
  121. return not self._scopes and not self._default_scopes
  122. @_helpers.copy_docstring(credentials.Scoped)
  123. def with_scopes(self, scopes, default_scopes=None):
  124. return self.__class__(
  125. scopes=scopes,
  126. default_scopes=default_scopes,
  127. service_account_id=self._service_account_id,
  128. quota_project_id=self.quota_project_id,
  129. )
  130. @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
  131. def with_quota_project(self, quota_project_id):
  132. return self.__class__(
  133. scopes=self._scopes,
  134. service_account_id=self._service_account_id,
  135. quota_project_id=quota_project_id,
  136. )
  137. @_helpers.copy_docstring(credentials.Signing)
  138. def sign_bytes(self, message):
  139. return self._signer.sign(message)
  140. @property # type: ignore
  141. @_helpers.copy_docstring(credentials.Signing)
  142. def signer_email(self):
  143. return self.service_account_email
  144. @property # type: ignore
  145. @_helpers.copy_docstring(credentials.Signing)
  146. def signer(self):
  147. return self._signer