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

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

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 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 unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2016 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
15 """Decodes and verifies the signature of auth tokens."""
16
17 import datetime
18 import jwkest
19 import time
20
21 from dogpile import cache
22 from jwkest import jws
23 from jwkest import jwt
24
25 from google.api.auth import suppliers
26
27
28 class Authenticator(object): # pylint: disable=too-few-public-methods
29 """Decodes and verifies the signature of auth tokens."""
30
31 def __init__(self, issuers_to_provider_ids, jwks_supplier, cache_capacity=200) :
32 """Construct an instance of AuthTokenDecoder.
33
34 Args:
35 issuers_to_provider_ids: a dictionary mapping from issuers to provider
36 IDs defined in the service configuration.
37 jwks_supplier: an instance of JwksSupplier that supplies JWKS based on
38 issuer.
39 cache_capacity: the cache_capacity with default value of 200.
40 """
41 self._issuers_to_provider_ids = issuers_to_provider_ids
42 self._jwks_supplier = jwks_supplier
43
44 arguments = {"capacity": cache_capacity}
45 expiration_time = datetime.timedelta(minutes=5)
46 self._cache = cache.make_region().configure("lru_cache",
47 arguments=arguments,
48 expiration_time=expiration_time)
49
50 def authenticate(self, auth_token, auth_info, service_name):
51 """Authenticates the current auth token.
52
53 Args:
54 auth_token: the auth token.
55 auth_info: the auth configurations of the API method being called.
56 service_name: the name of this service.
57
58 Returns:
59 A constructed UserInfo object representing the identity of the caller.
60
61 Raises:
62 UnauthenticatedException: When
63 * the issuer is not allowed;
64 * the audiences are not allowed;
65 * the auth token has already expired.
66 """
67 try:
68 jwt_claims = self.get_jwt_claims(auth_token)
69 except Exception as error:
70 raise suppliers.UnauthenticatedException("Cannot decode the auth token",
71 error)
72 _check_jwt_claims(jwt_claims)
73
74 user_info = UserInfo(jwt_claims)
75
76 issuer = user_info.issuer
77 if issuer not in self._issuers_to_provider_ids:
78 raise suppliers.UnauthenticatedException("Unknown issuer: " + issuer)
79 provider_id = self._issuers_to_provider_ids[issuer]
80
81 if not auth_info.is_provider_allowed(provider_id):
82 raise suppliers.UnauthenticatedException("The requested method does not "
83 "allow provider id: " + provider_ id)
84
85 # Check the audiences decoded from the auth token. The auth token is
86 # allowed when 1) an audience is equal to the service name, or 2) at least
87 # one audience is allowed in the method configuration.
88 audiences = user_info.audiences
89 has_service_name = service_name in audiences
90
91 allowed_audiences = auth_info.get_allowed_audiences(provider_id)
92 intersected_audiences = set(allowed_audiences).intersection(audiences)
93 if not has_service_name and not intersected_audiences:
94 raise suppliers.UnauthenticatedException("Audiences not allowed")
95
96 return user_info
97
98 def get_jwt_claims(self, auth_token):
99 """Decodes the auth_token into JWT claims represented as a JSON object.
100
101 This method first tries to look up the cache and returns the result
102 immediately in case of a cache hit. When cache misses, the method tries to
103 decode the given auth token, verify its signature, and check the existence
104 of required JWT claims. When successful, the decoded JWT claims are loaded
105 into the cache and then returned.
106
107 Args:
108 auth_token: the auth token to be decoded.
109
110 Returns:
111 The decoded JWT claims.
112
113 Raises:
114 UnauthenticatedException: When the signature verification fails, or when
115 required claims are missing.
116 """
117
118 def _decode_and_verify():
119 jwt_claims = jwt.JWT().unpack(auth_token).payload()
120 _verify_required_claims_exist(jwt_claims)
121
122 issuer = jwt_claims["iss"]
123 keys = self._jwks_supplier.supply(issuer)
124 try:
125 return jws.JWS().verify_compact(auth_token, keys)
126 except (jwkest.BadSignature, jws.NoSuitableSigningKeys,
127 jws.SignerAlgError) as exception:
128 raise suppliers.UnauthenticatedException("Signature verification failed" ,
129 exception)
130
131 return self._cache.get_or_create(auth_token, _decode_and_verify)
132
133
134 class UserInfo(object):
135 """An object that holds the authentication results."""
136
137 def __init__(self, jwt_claims):
138 audiences = jwt_claims["aud"]
139 if isinstance(audiences, basestring):
140 audiences = [audiences]
141 self._audiences = audiences
142
143 # email is not required
144 self._email = jwt_claims["email"] if "email" in jwt_claims else None
145 self._subject_id = jwt_claims["sub"]
146 self._issuer = jwt_claims["iss"]
147
148 @property
149 def audiences(self):
150 return self._audiences
151
152 @property
153 def email(self):
154 return self._email
155
156 @property
157 def subject_id(self):
158 return self._subject_id
159
160 @property
161 def issuer(self):
162 return self._issuer
163
164
165 def _check_jwt_claims(jwt_claims):
166 """Checks whether the JWT claims should be accepted.
167
168 Specifically, this method checks the "exp" claim and the "nbf" claim (if
169 present), and raises UnauthenticatedException if 1) the current time is
170 before the time identified by the "nbf" claim, or 2) the current time is
171 equal to or after the time identified by the "exp" claim.
172
173 Args:
174 jwt_claims: the JWT claims whose expiratio to be checked.
175
176 Raises:
177 UnauthenticatedException: When the "exp" claim is malformed or the JWT has
178 already expired.
179 """
180 current_time = time.time()
181
182 expiration = jwt_claims["exp"]
183 if not isinstance(expiration, (int, long)):
184 raise suppliers.UnauthenticatedException('Malformed claim: "exp" must be an integer')
185 if current_time >= expiration:
186 raise suppliers.UnauthenticatedException("The auth token has already expired ")
187
188 if "nbf" not in jwt_claims:
189 return
190
191 not_before_time = jwt_claims["nbf"]
192 if not isinstance(not_before_time, (int, long)):
193 raise suppliers.UnauthenticatedException('Malformed claim: "nbf" must be an integer')
194 if current_time < not_before_time:
195 raise suppliers.UnauthenticatedException('Current time is less than the "nbf " time')
196
197 def _verify_required_claims_exist(jwt_claims):
198 """Verifies that the required claims exist.
199
200 Args:
201 jwt_claims: the JWT claims to be verified.
202
203 Raises:
204 UnauthenticatedException: if some claim doesn't exist.
205 """
206 for claim_name in ["aud", "exp", "iss", "sub"]:
207 if claim_name not in jwt_claims:
208 raise suppliers.UnauthenticatedException('Missing "%s" claim' % claim_name )
OLDNEW
« 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