blob: dd365899433601308881f2fc4100e18bab40a904 [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
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070083
84
Bu Sun Kim41599ae2020-09-02 12:55:42 -060085class Credentials(
86 credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject
87):
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070088 """Service account credentials
89
90 Usually, you'll create these credentials with one of the helper
91 constructors. To create credentials using a Google service account
92 private key JSON file::
93
94 credentials = service_account.Credentials.from_service_account_file(
95 'service-account.json')
96
97 Or if you already have the service account file loaded::
98
99 service_account_info = json.load(open('service_account.json'))
100 credentials = service_account.Credentials.from_service_account_info(
101 service_account_info)
102
103 Both helper methods pass on arguments to the constructor, so you can
104 specify additional scopes and a subject if necessary::
105
106 credentials = service_account.Credentials.from_service_account_file(
107 'service-account.json',
108 scopes=['email'],
109 subject='user@example.com')
110
111 The credentials are considered immutable. If you want to modify the scopes
112 or the subject used for delegation, use :meth:`with_scopes` or
113 :meth:`with_subject`::
114
115 scoped_credentials = credentials.with_scopes(['email'])
116 delegated_credentials = credentials.with_subject(subject)
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700117
118 To add a quota project, use :meth:`with_quota_project`::
119
120 credentials = credentials.with_quota_project('myproject-123')
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700121 """
122
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700123 def __init__(
124 self,
125 signer,
126 service_account_email,
127 token_uri,
128 scopes=None,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700129 default_scopes=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700130 subject=None,
131 project_id=None,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700132 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700133 additional_claims=None,
arithmetic17282cfe6552021-06-16 15:30:36 -0700134 always_use_jwt_access=False,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700135 ):
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700136 """
137 Args:
138 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
139 service_account_email (str): The service account's email.
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700140 scopes (Sequence[str]): User-defined scopes to request during the
141 authorization grant.
142 default_scopes (Sequence[str]): Default scopes passed by a
143 Google client library. Use 'scopes' for user-defined scopes.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700144 token_uri (str): The OAuth 2.0 Token URI.
145 subject (str): For domain-wide delegation, the email address of the
146 user to for which to request delegated access.
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700147 project_id (str): Project ID associated with the service account
148 credential.
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700149 quota_project_id (Optional[str]): The project ID used for quota and
150 billing.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700151 additional_claims (Mapping[str, str]): Any additional claims for
152 the JWT assertion used in the authorization grant.
arithmetic17282cfe6552021-06-16 15:30:36 -0700153 always_use_jwt_access (Optional[bool]): Whether self signed JWT should
154 be always used.
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700155
156 .. note:: Typically one of the helper constructors
157 :meth:`from_service_account_file` or
158 :meth:`from_service_account_info` are used instead of calling the
159 constructor directly.
160 """
161 super(Credentials, self).__init__()
162
163 self._scopes = scopes
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700164 self._default_scopes = default_scopes
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700165 self._signer = signer
166 self._service_account_email = service_account_email
167 self._subject = subject
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700168 self._project_id = project_id
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700169 self._quota_project_id = quota_project_id
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700170 self._token_uri = token_uri
arithmetic17282cfe6552021-06-16 15:30:36 -0700171 self._always_use_jwt_access = always_use_jwt_access
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700172
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700173 self._jwt_credentials = None
174
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700175 if additional_claims is not None:
176 self._additional_claims = additional_claims
177 else:
178 self._additional_claims = {}
179
180 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700181 def _from_signer_and_info(cls, signer, info, **kwargs):
182 """Creates a Credentials instance from a signer and service account
183 info.
184
185 Args:
186 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
187 info (Mapping[str, str]): The service account info.
188 kwargs: Additional arguments to pass to the constructor.
189
190 Returns:
191 google.auth.jwt.Credentials: The constructed credentials.
192
193 Raises:
194 ValueError: If the info is not in the expected format.
195 """
196 return cls(
197 signer,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700198 service_account_email=info["client_email"],
199 token_uri=info["token_uri"],
200 project_id=info.get("project_id"),
201 **kwargs
202 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700203
204 @classmethod
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700205 def from_service_account_info(cls, info, **kwargs):
206 """Creates a Credentials instance from parsed service account info.
207
208 Args:
209 info (Mapping[str, str]): The service account info in Google
210 format.
211 kwargs: Additional arguments to pass to the constructor.
212
213 Returns:
214 google.auth.service_account.Credentials: The constructed
215 credentials.
216
217 Raises:
218 ValueError: If the info is not in the expected format.
219 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700220 signer = _service_account_info.from_dict(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700221 info, require=["client_email", "token_uri"]
222 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700223 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700224
225 @classmethod
226 def from_service_account_file(cls, filename, **kwargs):
227 """Creates a Credentials instance from a service account json file.
228
229 Args:
230 filename (str): The path to the service account json file.
231 kwargs: Additional arguments to pass to the constructor.
232
233 Returns:
234 google.auth.service_account.Credentials: The constructed
235 credentials.
236 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700237 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700238 filename, require=["client_email", "token_uri"]
239 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700240 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700241
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700242 @property
Jon Wayne Parrott61ffb052016-11-08 09:30:30 -0800243 def service_account_email(self):
244 """The service account email."""
245 return self._service_account_email
246
247 @property
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700248 def project_id(self):
249 """Project ID associated with this credential."""
250 return self._project_id
251
252 @property
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700253 def requires_scopes(self):
254 """Checks if the credentials requires scopes.
255
256 Returns:
257 bool: True if there are no scopes set otherwise False.
258 """
259 return True if not self._scopes else False
260
261 @_helpers.copy_docstring(credentials.Scoped)
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700262 def with_scopes(self, scopes, default_scopes=None):
Christophe Tatonb649b432018-02-08 14:12:23 -0800263 return self.__class__(
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700264 self._signer,
265 service_account_email=self._service_account_email,
266 scopes=scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700267 default_scopes=default_scopes,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700268 token_uri=self._token_uri,
269 subject=self._subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700270 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700271 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700272 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700273 always_use_jwt_access=self._always_use_jwt_access,
274 )
275
276 def with_always_use_jwt_access(self, always_use_jwt_access):
277 """Create a copy of these credentials with the specified always_use_jwt_access value.
278
279 Args:
280 always_use_jwt_access (bool): Whether always use self signed JWT or not.
281
282 Returns:
283 google.auth.service_account.Credentials: A new credentials
284 instance.
285 """
286 return self.__class__(
287 self._signer,
288 service_account_email=self._service_account_email,
289 scopes=self._scopes,
290 default_scopes=self._default_scopes,
291 token_uri=self._token_uri,
292 subject=self._subject,
293 project_id=self._project_id,
294 quota_project_id=self._quota_project_id,
295 additional_claims=self._additional_claims.copy(),
296 always_use_jwt_access=always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700297 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700298
299 def with_subject(self, subject):
300 """Create a copy of these credentials with the specified subject.
301
302 Args:
303 subject (str): The subject claim.
304
305 Returns:
306 google.auth.service_account.Credentials: A new credentials
307 instance.
308 """
Christophe Tatonb649b432018-02-08 14:12:23 -0800309 return self.__class__(
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700310 self._signer,
311 service_account_email=self._service_account_email,
312 scopes=self._scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700313 default_scopes=self._default_scopes,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700314 token_uri=self._token_uri,
315 subject=subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700316 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700317 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700318 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700319 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700320 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700321
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700322 def with_claims(self, additional_claims):
323 """Returns a copy of these credentials with modified claims.
324
325 Args:
326 additional_claims (Mapping[str, str]): Any additional claims for
327 the JWT payload. This will be merged with the current
328 additional claims.
329
330 Returns:
331 google.auth.service_account.Credentials: A new credentials
332 instance.
333 """
334 new_additional_claims = copy.deepcopy(self._additional_claims)
335 new_additional_claims.update(additional_claims or {})
336
Christophe Tatonb649b432018-02-08 14:12:23 -0800337 return self.__class__(
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700338 self._signer,
339 service_account_email=self._service_account_email,
340 scopes=self._scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700341 default_scopes=self._default_scopes,
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700342 token_uri=self._token_uri,
343 subject=self._subject,
Hiranya Jayathilaka6a3f0ec2017-08-10 09:11:02 -0700344 project_id=self._project_id,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700345 quota_project_id=self._quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700346 additional_claims=new_additional_claims,
arithmetic17282cfe6552021-06-16 15:30:36 -0700347 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700348 )
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700349
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600350 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700351 def with_quota_project(self, quota_project_id):
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700352
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700353 return self.__class__(
354 self._signer,
355 service_account_email=self._service_account_email,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700356 default_scopes=self._default_scopes,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700357 scopes=self._scopes,
358 token_uri=self._token_uri,
359 subject=self._subject,
360 project_id=self._project_id,
361 quota_project_id=quota_project_id,
362 additional_claims=self._additional_claims.copy(),
arithmetic17282cfe6552021-06-16 15:30:36 -0700363 always_use_jwt_access=self._always_use_jwt_access,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700364 )
365
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700366 def _make_authorization_grant_assertion(self):
367 """Create the OAuth 2.0 assertion.
368
369 This assertion is used during the OAuth 2.0 grant to acquire an
370 access token.
371
372 Returns:
373 bytes: The authorization grant assertion.
374 """
375 now = _helpers.utcnow()
376 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
377 expiry = now + lifetime
378
379 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700380 "iat": _helpers.datetime_to_secs(now),
381 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700382 # The issuer must be the service account email.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700383 "iss": self._service_account_email,
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700384 # The audience must be the auth token endpoint's URI
arithmetic1728d94e65c2021-07-20 12:09:57 -0700385 "aud": self._token_uri,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700386 "scope": _helpers.scopes_to_string(self._scopes or ()),
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700387 }
388
389 payload.update(self._additional_claims)
390
391 # The subject can be a user email for domain-wide delegation.
392 if self._subject:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700393 payload.setdefault("sub", self._subject)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700394
395 token = jwt.encode(self._signer, payload)
396
397 return token
398
399 @_helpers.copy_docstring(credentials.Credentials)
400 def refresh(self, request):
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700401 if self._jwt_credentials is not None:
402 self._jwt_credentials.refresh(request)
403 self.token = self._jwt_credentials.token
404 self.expiry = self._jwt_credentials.expiry
405 else:
406 assertion = self._make_authorization_grant_assertion()
407 access_token, expiry, _ = _client.jwt_grant(
408 request, self._token_uri, assertion
409 )
410 self.token = access_token
411 self.expiry = expiry
412
413 def _create_self_signed_jwt(self, audience):
414 """Create a self-signed JWT from the credentials if requirements are met.
415
416 Args:
417 audience (str): The service URL. ``https://[API_ENDPOINT]/``
418 """
419 # https://google.aip.dev/auth/4111
arithmetic17282cfe6552021-06-16 15:30:36 -0700420 if self._always_use_jwt_access:
421 if self._scopes:
422 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
423 self, None, additional_claims={"scope": " ".join(self._scopes)}
424 )
425 elif audience:
426 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
427 self, audience
428 )
429 elif self._default_scopes:
430 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
431 self,
432 None,
433 additional_claims={"scope": " ".join(self._default_scopes)},
434 )
435 elif not self._scopes and audience:
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700436 self._jwt_credentials = jwt.Credentials.from_signing_credentials(
437 self, audience
438 )
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700439
440 @_helpers.copy_docstring(credentials.Signing)
441 def sign_bytes(self, message):
442 return self._signer.sign(message)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800443
444 @property
445 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800446 def signer(self):
447 return self._signer
448
449 @property
450 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800451 def signer_email(self):
452 return self._service_account_email
Christophe Tatonb649b432018-02-08 14:12:23 -0800453
454
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600455class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject):
Christophe Tatonb649b432018-02-08 14:12:23 -0800456 """Open ID Connect ID Token-based service account credentials.
457
458 These credentials are largely similar to :class:`.Credentials`, but instead
459 of using an OAuth 2.0 Access Token as the bearer token, they use an Open
460 ID Connect ID Token as the bearer token. These credentials are useful when
461 communicating to services that require ID Tokens and can not accept access
462 tokens.
463
464 Usually, you'll create these credentials with one of the helper
465 constructors. To create credentials using a Google service account
466 private key JSON file::
467
468 credentials = (
469 service_account.IDTokenCredentials.from_service_account_file(
470 'service-account.json'))
471
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700472
Christophe Tatonb649b432018-02-08 14:12:23 -0800473 Or if you already have the service account file loaded::
474
475 service_account_info = json.load(open('service_account.json'))
476 credentials = (
477 service_account.IDTokenCredentials.from_service_account_info(
478 service_account_info))
479
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700480
Christophe Tatonb649b432018-02-08 14:12:23 -0800481 Both helper methods pass on arguments to the constructor, so you can
482 specify additional scopes and a subject if necessary::
483
484 credentials = (
485 service_account.IDTokenCredentials.from_service_account_file(
486 'service-account.json',
487 scopes=['email'],
488 subject='user@example.com'))
Bu Sun Kimb1a12d22021-02-26 11:50:02 -0700489
490
Christophe Tatonb649b432018-02-08 14:12:23 -0800491 The credentials are considered immutable. If you want to modify the scopes
492 or the subject used for delegation, use :meth:`with_scopes` or
493 :meth:`with_subject`::
494
495 scoped_credentials = credentials.with_scopes(['email'])
496 delegated_credentials = credentials.with_subject(subject)
497
498 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700499
500 def __init__(
501 self,
502 signer,
503 service_account_email,
504 token_uri,
505 target_audience,
506 additional_claims=None,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700507 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700508 ):
Christophe Tatonb649b432018-02-08 14:12:23 -0800509 """
510 Args:
511 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
512 service_account_email (str): The service account's email.
513 token_uri (str): The OAuth 2.0 Token URI.
514 target_audience (str): The intended audience for these credentials,
515 used when requesting the ID Token. The ID Token's ``aud`` claim
516 will be set to this string.
517 additional_claims (Mapping[str, str]): Any additional claims for
518 the JWT assertion used in the authorization grant.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700519 quota_project_id (Optional[str]): The project ID used for quota and billing.
Christophe Tatonb649b432018-02-08 14:12:23 -0800520 .. note:: Typically one of the helper constructors
521 :meth:`from_service_account_file` or
522 :meth:`from_service_account_info` are used instead of calling the
523 constructor directly.
524 """
525 super(IDTokenCredentials, self).__init__()
526 self._signer = signer
527 self._service_account_email = service_account_email
528 self._token_uri = token_uri
529 self._target_audience = target_audience
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700530 self._quota_project_id = quota_project_id
Christophe Tatonb649b432018-02-08 14:12:23 -0800531
532 if additional_claims is not None:
533 self._additional_claims = additional_claims
534 else:
535 self._additional_claims = {}
536
537 @classmethod
538 def _from_signer_and_info(cls, signer, info, **kwargs):
539 """Creates a credentials instance from a signer and service account
540 info.
541
542 Args:
543 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
544 info (Mapping[str, str]): The service account info.
545 kwargs: Additional arguments to pass to the constructor.
546
547 Returns:
548 google.auth.jwt.IDTokenCredentials: The constructed credentials.
549
550 Raises:
551 ValueError: If the info is not in the expected format.
552 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700553 kwargs.setdefault("service_account_email", info["client_email"])
554 kwargs.setdefault("token_uri", info["token_uri"])
Christophe Tatonb649b432018-02-08 14:12:23 -0800555 return cls(signer, **kwargs)
556
557 @classmethod
558 def from_service_account_info(cls, info, **kwargs):
559 """Creates a credentials instance from parsed service account info.
560
561 Args:
562 info (Mapping[str, str]): The service account info in Google
563 format.
564 kwargs: Additional arguments to pass to the constructor.
565
566 Returns:
567 google.auth.service_account.IDTokenCredentials: The constructed
568 credentials.
569
570 Raises:
571 ValueError: If the info is not in the expected format.
572 """
573 signer = _service_account_info.from_dict(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700574 info, require=["client_email", "token_uri"]
575 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800576 return cls._from_signer_and_info(signer, info, **kwargs)
577
578 @classmethod
579 def from_service_account_file(cls, filename, **kwargs):
580 """Creates a credentials instance from a service account json file.
581
582 Args:
583 filename (str): The path to the service account json file.
584 kwargs: Additional arguments to pass to the constructor.
585
586 Returns:
587 google.auth.service_account.IDTokenCredentials: The constructed
588 credentials.
589 """
590 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700591 filename, require=["client_email", "token_uri"]
592 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800593 return cls._from_signer_and_info(signer, info, **kwargs)
594
595 def with_target_audience(self, target_audience):
596 """Create a copy of these credentials with the specified target
597 audience.
598
599 Args:
600 target_audience (str): The intended audience for these credentials,
601 used when requesting the ID Token.
602
603 Returns:
604 google.auth.service_account.IDTokenCredentials: A new credentials
605 instance.
606 """
607 return self.__class__(
608 self._signer,
609 service_account_email=self._service_account_email,
610 token_uri=self._token_uri,
611 target_audience=target_audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700612 additional_claims=self._additional_claims.copy(),
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700613 quota_project_id=self.quota_project_id,
614 )
615
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600616 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700617 def with_quota_project(self, quota_project_id):
618 return self.__class__(
619 self._signer,
620 service_account_email=self._service_account_email,
621 token_uri=self._token_uri,
622 target_audience=self._target_audience,
623 additional_claims=self._additional_claims.copy(),
624 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700625 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800626
627 def _make_authorization_grant_assertion(self):
628 """Create the OAuth 2.0 assertion.
629
630 This assertion is used during the OAuth 2.0 grant to acquire an
631 ID token.
632
633 Returns:
634 bytes: The authorization grant assertion.
635 """
636 now = _helpers.utcnow()
637 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
638 expiry = now + lifetime
639
640 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700641 "iat": _helpers.datetime_to_secs(now),
642 "exp": _helpers.datetime_to_secs(expiry),
Christophe Tatonb649b432018-02-08 14:12:23 -0800643 # The issuer must be the service account email.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700644 "iss": self.service_account_email,
Christophe Tatonb649b432018-02-08 14:12:23 -0800645 # The audience must be the auth token endpoint's URI
arithmetic1728d94e65c2021-07-20 12:09:57 -0700646 "aud": self._token_uri,
Christophe Tatonb649b432018-02-08 14:12:23 -0800647 # The target audience specifies which service the ID token is
648 # intended for.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700649 "target_audience": self._target_audience,
Christophe Tatonb649b432018-02-08 14:12:23 -0800650 }
651
652 payload.update(self._additional_claims)
653
654 token = jwt.encode(self._signer, payload)
655
656 return token
657
658 @_helpers.copy_docstring(credentials.Credentials)
659 def refresh(self, request):
660 assertion = self._make_authorization_grant_assertion()
661 access_token, expiry, _ = _client.id_token_jwt_grant(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700662 request, self._token_uri, assertion
663 )
Christophe Tatonb649b432018-02-08 14:12:23 -0800664 self.token = access_token
665 self.expiry = expiry
666
667 @property
668 def service_account_email(self):
669 """The service account email."""
670 return self._service_account_email
671
672 @_helpers.copy_docstring(credentials.Signing)
673 def sign_bytes(self, message):
674 return self._signer.sign(message)
675
676 @property
677 @_helpers.copy_docstring(credentials.Signing)
678 def signer(self):
679 return self._signer
680
681 @property
682 @_helpers.copy_docstring(credentials.Signing)
683 def signer_email(self):
684 return self._service_account_email