blob: 8f18f26ea17b8ea48e269e90cf96dc58800cc2c0 [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -07002#
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"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
16
17This module implements the JWT Profile for OAuth 2.0 Authorization Grants
18as defined by `RFC 7523`_ with particular support for how this RFC is
19implemented in Google's infrastructure. Google refers to these credentials
20as *Service Accounts*.
21
22Service accounts are used for server-to-server communication, such as
23interactions between a web application server and a Google service. The
24service account belongs to your application instead of to an individual end
25user. In contrast to other OAuth 2.0 profiles, no users are involved and your
26application "acts" as the service account.
27
28Typically an application uses a service account when the application uses
29Google APIs to work with its own data rather than a user's data. For example,
30an application that uses Google Cloud Datastore for data persistence would use
31a service account to authenticate its calls to the Google Cloud Datastore API.
32However, an application that needs to access a user's Drive documents would
33use the normal OAuth 2.0 profile.
34
35Additionally, Google Apps domain administrators can grant service accounts
36`domain-wide delegation`_ authority to access user data on behalf of users in
37the domain.
38
39This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
40in place of the usual authorization token returned during the standard
41OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
42the acquired access token is used as the bearer token when making requests
43using these credentials.
44
45This profile differs from normal OAuth 2.0 profile because no user consent
46step is required. The use of the private key allows this profile to assert
47identity directly.
48
49This profile also differs from the :mod:`google.auth.jwt` authentication
50because the JWT credentials use the JWT directly as the bearer token. This
51profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
52obtained OAuth 2.0 access token is used as the bearer token.
53
54Domain-wide delegation
55----------------------
56
57Domain-wide delegation allows a service account to access user data on
58behalf of any user in a Google Apps domain without consent from the user.
59For example, an application that uses the Google Calendar API to add events to
60the calendars of all users in a Google Apps domain would use a service account
61to access the Google Calendar API on behalf of users.
62
63The Google Apps administrator must explicitly authorize the service account to
64do this. This authorization step is referred to as "delegating domain-wide
65authority" to a service account.
66
67You can use domain-wise delegation by creating a set of credentials with a
68specific subject using :meth:`~Credentials.with_subject`.
69
70.. _RFC 7523: https://tools.ietf.org/html/rfc7523
71"""
72
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -070073import copy
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070074import datetime
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070075
76from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070077from google.auth import _service_account_info
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070078from google.auth import credentials
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070079from google.auth import jwt
80from google.oauth2 import _client
81
Albert-Jan Nijburgb7b48f12017-11-08 20:15:18 +000082_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Chaoren0e264092021-07-14 11:02:10 -040083_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070084
85
Bu Sun Kim41599ae2020-09-02 12:55:42 -060086class Credentials(
87 credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject
88):
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070089 """Service account credentials
90
91 Usually, you'll create these credentials with one of the helper
92 constructors. To create credentials using a Google service account
93 private key JSON file::
94
95 credentials = service_account.Credentials.from_service_account_file(
96 'service-account.json')
97
98 Or if you already have the service account file loaded::
99
100 service_account_info = json.load(open('service_account.json'))
101 credentials = service_account.Credentials.from_service_account_info(
102 service_account_info)
103
104 Both helper methods pass on arguments to the constructor, so you can
105 specify additional scopes and a subject if necessary::
106
107 credentials = service_account.Credentials.from_service_account_file(
108 'service-account.json',
109 scopes=['email'],
110 subject='user@example.com')
111
112 The credentials are considered immutable. If you want to modify the scopes
113 or the subject used for delegation, use :meth:`with_scopes` or
114 :meth:`with_subject`::
115
116 scoped_credentials = credentials.with_scopes(['email'])
117 delegated_credentials = credentials.with_subject(subject)
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700118
119 To add a quota project, use :meth:`with_quota_project`::
120
121 credentials = credentials.with_quota_project('myproject-123')
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700122 """
123
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700124 def __init__(
125 self,
126 signer,
127 service_account_email,
128 token_uri,
129 scopes=None,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700130 default_scopes=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700131 subject=None,
132 project_id=None,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700133 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700134 additional_claims=None,
arithmetic17282cfe6552021-06-16 15:30:36 -0700135 always_use_jwt_access=False,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700136 ):
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700137 """
138 Args:
139 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
140 service_account_email (str): The service account's email.
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700141 scopes (Sequence[str]): User-defined scopes to request during the
142 authorization grant.
143 default_scopes (Sequence[str]): Default scopes passed by a
144 Google client library. Use 'scopes' for user-defined scopes.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700145 token_uri (str): The OAuth 2.0 Token URI.
146 subject (str): For domain-wide delegation, the email address of the
147 user to for which to request delegated access.
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700148 project_id (str): Project ID associated with the service account
149 credential.
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700150 quota_project_id (Optional[str]): The project ID used for quota and
151 billing.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700152 additional_claims (Mapping[str, str]): Any additional claims for
153 the JWT assertion used in the authorization grant.
arithmetic17282cfe6552021-06-16 15:30:36 -0700154 always_use_jwt_access (Optional[bool]): Whether self signed JWT should
155 be always used.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700156
157 .. note:: Typically one of the helper constructors
158 :meth:`from_service_account_file` or
159 :meth:`from_service_account_info` are used instead of calling the
160 constructor directly.
161 """
162 super(Credentials, self).__init__()
163
164 self._scopes = scopes
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700165 self._default_scopes = default_scopes
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700166 self._signer = signer
167 self._service_account_email = service_account_email
168 self._subject = subject
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700169 self._project_id = project_id
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700170 self._quota_project_id = quota_project_id
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700171 self._token_uri = token_uri
arithmetic17282cfe6552021-06-16 15:30:36 -0700172 self._always_use_jwt_access = always_use_jwt_access
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700173
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700174 self._jwt_credentials = None
175
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700176 if additional_claims is not None:
177 self._additional_claims = additional_claims
178 else:
179 self._additional_claims = {}
180
181 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700182 def _from_signer_and_info(cls, signer, info, **kwargs):
183 """Creates a Credentials instance from a signer and service account
184 info.
185
186 Args:
187 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
188 info (Mapping[str, str]): The service account info.
189 kwargs: Additional arguments to pass to the constructor.
190
191 Returns:
192 google.auth.jwt.Credentials: The constructed credentials.
193
194 Raises:
195 ValueError: If the info is not in the expected format.
196 """
197 return cls(
198 signer,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700199 service_account_email=info["client_email"],
200 token_uri=info["token_uri"],
201 project_id=info.get("project_id"),
202 **kwargs
203 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700204
205 @classmethod
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700206 def from_service_account_info(cls, info, **kwargs):
207 """Creates a Credentials instance from parsed service account info.
208
209 Args:
210 info (Mapping[str, str]): The service account info in Google
211 format.
212 kwargs: Additional arguments to pass to the constructor.
213
214 Returns:
215 google.auth.service_account.Credentials: The constructed
216 credentials.
217
218 Raises:
219 ValueError: If the info is not in the expected format.
220 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700221 signer = _service_account_info.from_dict(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700222 info, require=["client_email", "token_uri"]
223 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700224 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700225
226 @classmethod
227 def from_service_account_file(cls, filename, **kwargs):
228 """Creates a Credentials instance from a service account json file.
229
230 Args:
231 filename (str): The path to the service account json file.
232 kwargs: Additional arguments to pass to the constructor.
233
234 Returns:
235 google.auth.service_account.Credentials: The constructed
236 credentials.
237 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700238 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700239 filename, require=["client_email", "token_uri"]
240 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700241 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700242
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700243 @property
Jon Wayne Parrott61ffb052016-11-08 09:30:30 -0800244 def service_account_email(self):
245 """The service account email."""
246 return self._service_account_email
247
248 @property
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700249 def project_id(self):
250 """Project ID associated with this credential."""
251 return self._project_id
252
253 @property
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700254 def requires_scopes(self):
255 """Checks if the credentials requires scopes.
256
257 Returns:
258 bool: True if there are no scopes set otherwise False.
259 """
260 return True if not self._scopes else False
261
262 @_helpers.copy_docstring(credentials.Scoped)
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700263 def with_scopes(self, scopes, default_scopes=None):
Christophe Tatonb649b432018-02-08 14:12:23 -0800264 return self.__class__(
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700265 self._signer,
266 service_account_email=self._service_account_email,
267 scopes=scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700268 default_scopes=default_scopes,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700269 token_uri=self._token_uri,
270 subject=self._subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700271 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700272 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700273 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700274 always_use_jwt_access=self._always_use_jwt_access,
275 )
276
277 def with_always_use_jwt_access(self, always_use_jwt_access):
278 """Create a copy of these credentials with the specified always_use_jwt_access value.
279
280 Args:
281 always_use_jwt_access (bool): Whether always use self signed JWT or not.
282
283 Returns:
284 google.auth.service_account.Credentials: A new credentials
285 instance.
286 """
287 return self.__class__(
288 self._signer,
289 service_account_email=self._service_account_email,
290 scopes=self._scopes,
291 default_scopes=self._default_scopes,
292 token_uri=self._token_uri,
293 subject=self._subject,
294 project_id=self._project_id,
295 quota_project_id=self._quota_project_id,
296 additional_claims=self._additional_claims.copy(),
297 always_use_jwt_access=always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700298 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700299
300 def with_subject(self, subject):
301 """Create a copy of these credentials with the specified subject.
302
303 Args:
304 subject (str): The subject claim.
305
306 Returns:
307 google.auth.service_account.Credentials: A new credentials
308 instance.
309 """
Christophe Tatonb649b432018-02-08 14:12:23 -0800310 return self.__class__(
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700311 self._signer,
312 service_account_email=self._service_account_email,
313 scopes=self._scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700314 default_scopes=self._default_scopes,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700315 token_uri=self._token_uri,
316 subject=subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700317 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700318 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700319 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700320 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700321 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700322
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700323 def with_claims(self, additional_claims):
324 """Returns a copy of these credentials with modified claims.
325
326 Args:
327 additional_claims (Mapping[str, str]): Any additional claims for
328 the JWT payload. This will be merged with the current
329 additional claims.
330
331 Returns:
332 google.auth.service_account.Credentials: A new credentials
333 instance.
334 """
335 new_additional_claims = copy.deepcopy(self._additional_claims)
336 new_additional_claims.update(additional_claims or {})
337
Christophe Tatonb649b432018-02-08 14:12:23 -0800338 return self.__class__(
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700339 self._signer,
340 service_account_email=self._service_account_email,
341 scopes=self._scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700342 default_scopes=self._default_scopes,
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700343 token_uri=self._token_uri,
344 subject=self._subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700345 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700346 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700347 additional_claims=new_additional_claims,
arithmetic17282cfe6552021-06-16 15:30:36 -0700348 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700349 )
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700350
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600351 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700352 def with_quota_project(self, quota_project_id):
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700353
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700354 return self.__class__(
355 self._signer,
356 service_account_email=self._service_account_email,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700357 default_scopes=self._default_scopes,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700358 scopes=self._scopes,
359 token_uri=self._token_uri,
360 subject=self._subject,
361 project_id=self._project_id,
362 quota_project_id=quota_project_id,
363 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700364 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700365 )
366
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700367 def _make_authorization_grant_assertion(self):
368 """Create the OAuth 2.0 assertion.
369
370 This assertion is used during the OAuth 2.0 grant to acquire an
371 access token.
372
373 Returns:
374 bytes: The authorization grant assertion.
375 """
376 now = _helpers.utcnow()
377 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
378 expiry = now + lifetime
379
380 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700381 "iat": _helpers.datetime_to_secs(now),
382 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700383 # The issuer must be the service account email.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700384 "iss": self._service_account_email,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700385 # The audience must be the auth token endpoint's URI
Chaoren0e264092021-07-14 11:02:10 -0400386 "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700387 "scope": _helpers.scopes_to_string(self._scopes or ()),
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700388 }
389
390 payload.update(self._additional_claims)
391
392 # The subject can be a user email for domain-wide delegation.
393 if self._subject:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700394 payload.setdefault("sub", self._subject)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700395
396 token = jwt.encode(self._signer, payload)
397
398 return token
399
400 @_helpers.copy_docstring(credentials.Credentials)
401 def refresh(self, request):
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700402 if self._jwt_credentials is not None:
403 self._jwt_credentials.refresh(request)
404 self.token = self._jwt_credentials.token
405 self.expiry = self._jwt_credentials.expiry
406 else:
407 assertion = self._make_authorization_grant_assertion()
408 access_token, expiry, _ = _client.jwt_grant(
409 request, self._token_uri, assertion
410 )
411 self.token = access_token
412 self.expiry = expiry
413
414 def _create_self_signed_jwt(self, audience):
415 """Create a self-signed JWT from the credentials if requirements are met.
416
417 Args:
418 audience (str): The service URL. ``https://[API_ENDPOINT]/``
419 """
420 # https://google.aip.dev/auth/4111
arithmetic17282cfe6552021-06-16 15:30:36 -0700421 if self._always_use_jwt_access:
422 if self._scopes:
423 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
424 self, None, additional_claims={"scope": " ".join(self._scopes)}
425 )
426 elif audience:
427 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
428 self, audience
429 )
430 elif self._default_scopes:
431 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
432 self,
433 None,
434 additional_claims={"scope": " ".join(self._default_scopes)},
435 )
436 elif not self._scopes and audience:
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700437 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
438 self, audience
439 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700440
441 @_helpers.copy_docstring(credentials.Signing)
442 def sign_bytes(self, message):
443 return self._signer.sign(message)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800444
445 @property
446 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800447 def signer(self):
448 return self._signer
449
450 @property
451 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800452 def signer_email(self):
453 return self._service_account_email
Christophe Tatonb649b432018-02-08 14:12:23 -0800454
455
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600456class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject):
Christophe Tatonb649b432018-02-08 14:12:23 -0800457 """Open ID Connect ID Token-based service account credentials.
458
459 These credentials are largely similar to :class:`.Credentials`, but instead
460 of using an OAuth 2.0 Access Token as the bearer token, they use an Open
461 ID Connect ID Token as the bearer token. These credentials are useful when
462 communicating to services that require ID Tokens and can not accept access
463 tokens.
464
465 Usually, you'll create these credentials with one of the helper
466 constructors. To create credentials using a Google service account
467 private key JSON file::
468
469 credentials = (
470 service_account.IDTokenCredentials.from_service_account_file(
471 'service-account.json'))
472
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700473
Christophe Tatonb649b432018-02-08 14:12:23 -0800474 Or if you already have the service account file loaded::
475
476 service_account_info = json.load(open('service_account.json'))
477 credentials = (
478 service_account.IDTokenCredentials.from_service_account_info(
479 service_account_info))
480
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700481
Christophe Tatonb649b432018-02-08 14:12:23 -0800482 Both helper methods pass on arguments to the constructor, so you can
483 specify additional scopes and a subject if necessary::
484
485 credentials = (
486 service_account.IDTokenCredentials.from_service_account_file(
487 'service-account.json',
488 scopes=['email'],
489 subject='user@example.com'))
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700490
491
Christophe Tatonb649b432018-02-08 14:12:23 -0800492 The credentials are considered immutable. If you want to modify the scopes
493 or the subject used for delegation, use :meth:`with_scopes` or
494 :meth:`with_subject`::
495
496 scoped_credentials = credentials.with_scopes(['email'])
497 delegated_credentials = credentials.with_subject(subject)
498
499 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700500
501 def __init__(
502 self,
503 signer,
504 service_account_email,
505 token_uri,
506 target_audience,
507 additional_claims=None,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700508 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700509 ):
Christophe Tatonb649b432018-02-08 14:12:23 -0800510 """
511 Args:
512 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
513 service_account_email (str): The service account's email.
514 token_uri (str): The OAuth 2.0 Token URI.
515 target_audience (str): The intended audience for these credentials,
516 used when requesting the ID Token. The ID Token's ``aud`` claim
517 will be set to this string.
518 additional_claims (Mapping[str, str]): Any additional claims for
519 the JWT assertion used in the authorization grant.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700520 quota_project_id (Optional[str]): The project ID used for quota and billing.
Christophe Tatonb649b432018-02-08 14:12:23 -0800521 .. note:: Typically one of the helper constructors
522 :meth:`from_service_account_file` or
523 :meth:`from_service_account_info` are used instead of calling the
524 constructor directly.
525 """
526 super(IDTokenCredentials, self).__init__()
527 self._signer = signer
528 self._service_account_email = service_account_email
529 self._token_uri = token_uri
530 self._target_audience = target_audience
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700531 self._quota_project_id = quota_project_id
Christophe Tatonb649b432018-02-08 14:12:23 -0800532
533 if additional_claims is not None:
534 self._additional_claims = additional_claims
535 else:
536 self._additional_claims = {}
537
538 @classmethod
539 def _from_signer_and_info(cls, signer, info, **kwargs):
540 """Creates a credentials instance from a signer and service account
541 info.
542
543 Args:
544 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
545 info (Mapping[str, str]): The service account info.
546 kwargs: Additional arguments to pass to the constructor.
547
548 Returns:
549 google.auth.jwt.IDTokenCredentials: The constructed credentials.
550
551 Raises:
552 ValueError: If the info is not in the expected format.
553 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700554 kwargs.setdefault("service_account_email", info["client_email"])
555 kwargs.setdefault("token_uri", info["token_uri"])
Christophe Tatonb649b432018-02-08 14:12:23 -0800556 return cls(signer, **kwargs)
557
558 @classmethod
559 def from_service_account_info(cls, info, **kwargs):
560 """Creates a credentials instance from parsed service account info.
561
562 Args:
563 info (Mapping[str, str]): The service account info in Google
564 format.
565 kwargs: Additional arguments to pass to the constructor.
566
567 Returns:
568 google.auth.service_account.IDTokenCredentials: The constructed
569 credentials.
570
571 Raises:
572 ValueError: If the info is not in the expected format.
573 """
574 signer = _service_account_info.from_dict(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700575 info, require=["client_email", "token_uri"]
576 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800577 return cls._from_signer_and_info(signer, info, **kwargs)
578
579 @classmethod
580 def from_service_account_file(cls, filename, **kwargs):
581 """Creates a credentials instance from a service account json file.
582
583 Args:
584 filename (str): The path to the service account json file.
585 kwargs: Additional arguments to pass to the constructor.
586
587 Returns:
588 google.auth.service_account.IDTokenCredentials: The constructed
589 credentials.
590 """
591 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700592 filename, require=["client_email", "token_uri"]
593 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800594 return cls._from_signer_and_info(signer, info, **kwargs)
595
596 def with_target_audience(self, target_audience):
597 """Create a copy of these credentials with the specified target
598 audience.
599
600 Args:
601 target_audience (str): The intended audience for these credentials,
602 used when requesting the ID Token.
603
604 Returns:
605 google.auth.service_account.IDTokenCredentials: A new credentials
606 instance.
607 """
608 return self.__class__(
609 self._signer,
610 service_account_email=self._service_account_email,
611 token_uri=self._token_uri,
612 target_audience=target_audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700613 additional_claims=self._additional_claims.copy(),
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700614 quota_project_id=self.quota_project_id,
615 )
616
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600617 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700618 def with_quota_project(self, quota_project_id):
619 return self.__class__(
620 self._signer,
621 service_account_email=self._service_account_email,
622 token_uri=self._token_uri,
623 target_audience=self._target_audience,
624 additional_claims=self._additional_claims.copy(),
625 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700626 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800627
628 def _make_authorization_grant_assertion(self):
629 """Create the OAuth 2.0 assertion.
630
631 This assertion is used during the OAuth 2.0 grant to acquire an
632 ID token.
633
634 Returns:
635 bytes: The authorization grant assertion.
636 """
637 now = _helpers.utcnow()
638 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
639 expiry = now + lifetime
640
641 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700642 "iat": _helpers.datetime_to_secs(now),
643 "exp": _helpers.datetime_to_secs(expiry),
Christophe Tatonb649b432018-02-08 14:12:23 -0800644 # The issuer must be the service account email.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700645 "iss": self.service_account_email,
Christophe Tatonb649b432018-02-08 14:12:23 -0800646 # The audience must be the auth token endpoint's URI
Chaoren0e264092021-07-14 11:02:10 -0400647 "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
Christophe Tatonb649b432018-02-08 14:12:23 -0800648 # The target audience specifies which service the ID token is
649 # intended for.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700650 "target_audience": self._target_audience,
Christophe Tatonb649b432018-02-08 14:12:23 -0800651 }
652
653 payload.update(self._additional_claims)
654
655 token = jwt.encode(self._signer, payload)
656
657 return token
658
659 @_helpers.copy_docstring(credentials.Credentials)
660 def refresh(self, request):
661 assertion = self._make_authorization_grant_assertion()
662 access_token, expiry, _ = _client.id_token_jwt_grant(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700663 request, self._token_uri, assertion
664 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800665 self.token = access_token
666 self.expiry = expiry
667
668 @property
669 def service_account_email(self):
670 """The service account email."""
671 return self._service_account_email
672
673 @_helpers.copy_docstring(credentials.Signing)
674 def sign_bytes(self, message):
675 return self._signer.sign(message)
676
677 @property
678 @_helpers.copy_docstring(credentials.Signing)
679 def signer(self):
680 return self._signer
681
682 @property
683 @_helpers.copy_docstring(credentials.Signing)
684 def signer_email(self):
685 return self._service_account_email