Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Unified Diff: third_party/google-endpoints/google/api/auth/tokens.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/google-endpoints/google/api/auth/tokens.py
diff --git a/third_party/google-endpoints/google/api/auth/tokens.py b/third_party/google-endpoints/google/api/auth/tokens.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdaa214c6aab840ff854cc6f282631c099146a1d
--- /dev/null
+++ b/third_party/google-endpoints/google/api/auth/tokens.py
@@ -0,0 +1,208 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Decodes and verifies the signature of auth tokens."""
+
+import datetime
+import jwkest
+import time
+
+from dogpile import cache
+from jwkest import jws
+from jwkest import jwt
+
+from google.api.auth import suppliers
+
+
+class Authenticator(object): # pylint: disable=too-few-public-methods
+ """Decodes and verifies the signature of auth tokens."""
+
+ def __init__(self, issuers_to_provider_ids, jwks_supplier, cache_capacity=200):
+ """Construct an instance of AuthTokenDecoder.
+
+ Args:
+ issuers_to_provider_ids: a dictionary mapping from issuers to provider
+ IDs defined in the service configuration.
+ jwks_supplier: an instance of JwksSupplier that supplies JWKS based on
+ issuer.
+ cache_capacity: the cache_capacity with default value of 200.
+ """
+ self._issuers_to_provider_ids = issuers_to_provider_ids
+ self._jwks_supplier = jwks_supplier
+
+ arguments = {"capacity": cache_capacity}
+ expiration_time = datetime.timedelta(minutes=5)
+ self._cache = cache.make_region().configure("lru_cache",
+ arguments=arguments,
+ expiration_time=expiration_time)
+
+ def authenticate(self, auth_token, auth_info, service_name):
+ """Authenticates the current auth token.
+
+ Args:
+ auth_token: the auth token.
+ auth_info: the auth configurations of the API method being called.
+ service_name: the name of this service.
+
+ Returns:
+ A constructed UserInfo object representing the identity of the caller.
+
+ Raises:
+ UnauthenticatedException: When
+ * the issuer is not allowed;
+ * the audiences are not allowed;
+ * the auth token has already expired.
+ """
+ try:
+ jwt_claims = self.get_jwt_claims(auth_token)
+ except Exception as error:
+ raise suppliers.UnauthenticatedException("Cannot decode the auth token",
+ error)
+ _check_jwt_claims(jwt_claims)
+
+ user_info = UserInfo(jwt_claims)
+
+ issuer = user_info.issuer
+ if issuer not in self._issuers_to_provider_ids:
+ raise suppliers.UnauthenticatedException("Unknown issuer: " + issuer)
+ provider_id = self._issuers_to_provider_ids[issuer]
+
+ if not auth_info.is_provider_allowed(provider_id):
+ raise suppliers.UnauthenticatedException("The requested method does not "
+ "allow provider id: " + provider_id)
+
+ # Check the audiences decoded from the auth token. The auth token is
+ # allowed when 1) an audience is equal to the service name, or 2) at least
+ # one audience is allowed in the method configuration.
+ audiences = user_info.audiences
+ has_service_name = service_name in audiences
+
+ allowed_audiences = auth_info.get_allowed_audiences(provider_id)
+ intersected_audiences = set(allowed_audiences).intersection(audiences)
+ if not has_service_name and not intersected_audiences:
+ raise suppliers.UnauthenticatedException("Audiences not allowed")
+
+ return user_info
+
+ def get_jwt_claims(self, auth_token):
+ """Decodes the auth_token into JWT claims represented as a JSON object.
+
+ This method first tries to look up the cache and returns the result
+ immediately in case of a cache hit. When cache misses, the method tries to
+ decode the given auth token, verify its signature, and check the existence
+ of required JWT claims. When successful, the decoded JWT claims are loaded
+ into the cache and then returned.
+
+ Args:
+ auth_token: the auth token to be decoded.
+
+ Returns:
+ The decoded JWT claims.
+
+ Raises:
+ UnauthenticatedException: When the signature verification fails, or when
+ required claims are missing.
+ """
+
+ def _decode_and_verify():
+ jwt_claims = jwt.JWT().unpack(auth_token).payload()
+ _verify_required_claims_exist(jwt_claims)
+
+ issuer = jwt_claims["iss"]
+ keys = self._jwks_supplier.supply(issuer)
+ try:
+ return jws.JWS().verify_compact(auth_token, keys)
+ except (jwkest.BadSignature, jws.NoSuitableSigningKeys,
+ jws.SignerAlgError) as exception:
+ raise suppliers.UnauthenticatedException("Signature verification failed",
+ exception)
+
+ return self._cache.get_or_create(auth_token, _decode_and_verify)
+
+
+class UserInfo(object):
+ """An object that holds the authentication results."""
+
+ def __init__(self, jwt_claims):
+ audiences = jwt_claims["aud"]
+ if isinstance(audiences, basestring):
+ audiences = [audiences]
+ self._audiences = audiences
+
+ # email is not required
+ self._email = jwt_claims["email"] if "email" in jwt_claims else None
+ self._subject_id = jwt_claims["sub"]
+ self._issuer = jwt_claims["iss"]
+
+ @property
+ def audiences(self):
+ return self._audiences
+
+ @property
+ def email(self):
+ return self._email
+
+ @property
+ def subject_id(self):
+ return self._subject_id
+
+ @property
+ def issuer(self):
+ return self._issuer
+
+
+def _check_jwt_claims(jwt_claims):
+ """Checks whether the JWT claims should be accepted.
+
+ Specifically, this method checks the "exp" claim and the "nbf" claim (if
+ present), and raises UnauthenticatedException if 1) the current time is
+ before the time identified by the "nbf" claim, or 2) the current time is
+ equal to or after the time identified by the "exp" claim.
+
+ Args:
+ jwt_claims: the JWT claims whose expiratio to be checked.
+
+ Raises:
+ UnauthenticatedException: When the "exp" claim is malformed or the JWT has
+ already expired.
+ """
+ current_time = time.time()
+
+ expiration = jwt_claims["exp"]
+ if not isinstance(expiration, (int, long)):
+ raise suppliers.UnauthenticatedException('Malformed claim: "exp" must be an integer')
+ if current_time >= expiration:
+ raise suppliers.UnauthenticatedException("The auth token has already expired")
+
+ if "nbf" not in jwt_claims:
+ return
+
+ not_before_time = jwt_claims["nbf"]
+ if not isinstance(not_before_time, (int, long)):
+ raise suppliers.UnauthenticatedException('Malformed claim: "nbf" must be an integer')
+ if current_time < not_before_time:
+ raise suppliers.UnauthenticatedException('Current time is less than the "nbf" time')
+
+def _verify_required_claims_exist(jwt_claims):
+ """Verifies that the required claims exist.
+
+ Args:
+ jwt_claims: the JWT claims to be verified.
+
+ Raises:
+ UnauthenticatedException: if some claim doesn't exist.
+ """
+ for claim_name in ["aud", "exp", "iss", "sub"]:
+ if claim_name not in jwt_claims:
+ raise suppliers.UnauthenticatedException('Missing "%s" claim' % claim_name)
« no previous file with comments | « third_party/google-endpoints/google/api/auth/suppliers.py ('k') | third_party/google-endpoints/google/api/config/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698