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