codekingpro/portable-devtools
114k
1# Copyright 2016 Google LLC2#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 at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# 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 and13# limitations under the License.14 15"""Google ID Token helpers.16 17Provides support for verifying `OpenID Connect ID Tokens`_, especially ones18generated by Google infrastructure.19 20To parse and verify an ID Token issued by Google's OAuth 2.0 authorization21server use :func:`verify_oauth2_token`. To verify an ID Token issued by22Firebase, use :func:`verify_firebase_token`.23 24A general purpose ID Token verifier is available as :func:`verify_token`.25 26Example::27 28 from google.oauth2 import id_token29 from google.auth.transport import requests30 31 request = requests.Request()32 33 id_info = id_token.verify_oauth2_token(34 token, request, 'my-client-id.example.com')35 36 userid = id_info['sub']37 38By default, this will re-fetch certificates for each verification. Because39Google's public keys are only changed infrequently (on the order of once per40day), you may wish to take advantage of caching to reduce latency and the41potential for network errors. This can be accomplished using an external42library like `CacheControl`_ to create a cache-aware43:class:`google.auth.transport.Request`::44 45 import cachecontrol46 import google.auth.transport.requests47 import requests48 49 session = requests.session()50 cached_session = cachecontrol.CacheControl(session)51 request = google.auth.transport.requests.Request(session=cached_session)52 53.. _OpenID Connect ID Tokens:54 http://openid.net/specs/openid-connect-core-1_0.html#IDToken55.. _CacheControl: https://cachecontrol.readthedocs.io56"""57 58import http.client as http_client59import json60import os61 62from google.auth import environment_vars63from google.auth import exceptions64from google.auth import jwt65 66 67# The URL that provides public certificates for verifying ID tokens issued68# by Google's OAuth 2.0 authorization server.69_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"70 71# The URL that provides public certificates for verifying ID tokens issued72# by Firebase and the Google APIs infrastructure73_GOOGLE_APIS_CERTS_URL = (74 "https://www.googleapis.com/robot/v1/metadata/x509"75 "/securetoken@system.gserviceaccount.com"76)77 78_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]79 80 81def _fetch_certs(request, certs_url):82 """Fetches certificates.83 84 Google-style cerificate endpoints return JSON in the format of85 ``{'key id': 'x509 certificate'}``.86 87 Args:88 request (google.auth.transport.Request): The object used to make89 HTTP requests.90 certs_url (str): The certificate endpoint URL.91 92 Returns:93 Mapping[str, str]: A mapping of public key ID to x.509 certificate94 data.95 """96 response = request(certs_url, method="GET")97 98 if response.status != http_client.OK:99 raise exceptions.TransportError(100 "Could not fetch certificates at {}".format(certs_url)101 )102 103 return json.loads(response.data.decode("utf-8"))104 105 106def verify_token(107 id_token,108 request,109 audience=None,110 certs_url=_GOOGLE_OAUTH2_CERTS_URL,111 clock_skew_in_seconds=0,112):113 """Verifies an ID token and returns the decoded token.114 115 Args:116 id_token (Union[str, bytes]): The encoded token.117 request (google.auth.transport.Request): The object used to make118 HTTP requests.119 audience (str or list): The audience or audiences that this token is120 intended for. If None then the audience is not verified.121 certs_url (str): The URL that specifies the certificates to use to122 verify the token. This URL should return JSON in the format of123 ``{'key id': 'x509 certificate'}``.124 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`125 validation.126 127 Returns:128 Mapping[str, Any]: The decoded token.129 """130 certs = _fetch_certs(request, certs_url)131 132 return jwt.decode(133 id_token,134 certs=certs,135 audience=audience,136 clock_skew_in_seconds=clock_skew_in_seconds,137 )138 139 140def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):141 """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.142 143 Args:144 id_token (Union[str, bytes]): The encoded token.145 request (google.auth.transport.Request): The object used to make146 HTTP requests.147 audience (str): The audience that this token is intended for. This is148 typically your application's OAuth 2.0 client ID. If None then the149 audience is not verified.150 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`151 validation.152 153 Returns:154 Mapping[str, Any]: The decoded token.155 156 Raises:157 exceptions.GoogleAuthError: If the issuer is invalid.158 ValueError: If token verification fails159 """160 idinfo = verify_token(161 id_token,162 request,163 audience=audience,164 certs_url=_GOOGLE_OAUTH2_CERTS_URL,165 clock_skew_in_seconds=clock_skew_in_seconds,166 )167 168 if idinfo["iss"] not in _GOOGLE_ISSUERS:169 raise exceptions.GoogleAuthError(170 "Wrong issuer. 'iss' should be one of the following: {}".format(171 _GOOGLE_ISSUERS172 )173 )174 175 return idinfo176 177 178def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):179 """Verifies an ID Token issued by Firebase Authentication.180 181 Args:182 id_token (Union[str, bytes]): The encoded token.183 request (google.auth.transport.Request): The object used to make184 HTTP requests.185 audience (str): The audience that this token is intended for. This is186 typically your Firebase application ID. If None then the audience187 is not verified.188 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`189 validation.190 191 Returns:192 Mapping[str, Any]: The decoded token.193 """194 return verify_token(195 id_token,196 request,197 audience=audience,198 certs_url=_GOOGLE_APIS_CERTS_URL,199 clock_skew_in_seconds=clock_skew_in_seconds,200 )201 202 203def fetch_id_token_credentials(audience, request=None):204 """Create the ID Token credentials from the current environment.205 206 This function acquires ID token from the environment in the following order.207 See https://google.aip.dev/auth/4110.208 209 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set210 to the path of a valid service account JSON file, then ID token is211 acquired using this service account credentials.212 2. If the application is running in Compute Engine, App Engine or Cloud Run,213 then the ID token are obtained from the metadata server.214 3. If metadata server doesn't exist and no valid service account credentials215 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will216 be raised.217 218 Example::219 220 import google.oauth2.id_token221 import google.auth.transport.requests222 223 request = google.auth.transport.requests.Request()224 target_audience = "https://pubsub.googleapis.com"225 226 # Create ID token credentials.227 credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request)228 229 # Refresh the credential to obtain an ID token.230 credentials.refresh(request)231 232 id_token = credentials.token233 id_token_expiry = credentials.expiry234 235 Args:236 audience (str): The audience that this ID token is intended for.237 request (Optional[google.auth.transport.Request]): A callable used to make238 HTTP requests. A request object will be created if not provided.239 240 Returns:241 google.auth.credentials.Credentials: The ID token credentials.242 243 Raises:244 ~google.auth.exceptions.DefaultCredentialsError:245 If metadata server doesn't exist and no valid service account246 credentials are found.247 """248 # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment249 # variable.250 credentials_filename = os.environ.get(environment_vars.CREDENTIALS)251 if credentials_filename:252 if not (253 os.path.exists(credentials_filename)254 and os.path.isfile(credentials_filename)255 ):256 raise exceptions.DefaultCredentialsError(257 "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."258 )259 260 try:261 with open(credentials_filename, "r") as f:262 from google.oauth2 import service_account263 264 info = json.load(f)265 if info.get("type") == "service_account":266 return service_account.IDTokenCredentials.from_service_account_info(267 info, target_audience=audience268 )269 except ValueError as caught_exc:270 new_exc = exceptions.DefaultCredentialsError(271 "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",272 caught_exc,273 )274 raise new_exc from caught_exc275 276 # 2. Try to fetch ID token from metada server if it exists. The code277 # works for GAE and Cloud Run metadata server as well.278 try:279 from google.auth import compute_engine280 from google.auth.compute_engine import _metadata281 282 # Create a request object if not provided.283 if not request:284 import google.auth.transport.requests285 286 request = google.auth.transport.requests.Request()287 288 if _metadata.ping(request):289 return compute_engine.IDTokenCredentials(290 request, audience, use_metadata_identity_endpoint=True291 )292 except (ImportError, exceptions.TransportError):293 pass294 295 raise exceptions.DefaultCredentialsError(296 "Neither metadata server or valid service account credentials are found."297 )298 299 300def fetch_id_token(request, audience):301 """Fetch the ID Token from the current environment.302 303 This function acquires ID token from the environment in the following order.304 See https://google.aip.dev/auth/4110.305 306 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set307 to the path of a valid service account JSON file, then ID token is308 acquired using this service account credentials.309 2. If the application is running in Compute Engine, App Engine or Cloud Run,310 then the ID token are obtained from the metadata server.311 3. If metadata server doesn't exist and no valid service account credentials312 are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will313 be raised.314 315 Example::316 317 import google.oauth2.id_token318 import google.auth.transport.requests319 320 request = google.auth.transport.requests.Request()321 target_audience = "https://pubsub.googleapis.com"322 323 id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)324 325 Args:326 request (google.auth.transport.Request): A callable used to make327 HTTP requests.328 audience (str): The audience that this ID token is intended for.329 330 Returns:331 str: The ID token.332 333 Raises:334 ~google.auth.exceptions.DefaultCredentialsError:335 If metadata server doesn't exist and no valid service account336 credentials are found.337 """338 id_token_credentials = fetch_id_token_credentials(audience, request=request)339 id_token_credentials.refresh(request)340 return id_token_credentials.token341 