blob: a4fc1b6c14f254930b1ba9403a3f675fdd82130f [file] [log] [blame]
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -07001# Copyright 2016 Google Inc.
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"""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
73import datetime
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070074
75from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070076from google.auth import _service_account_info
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070077from google.auth import credentials
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -070078from google.auth import jwt
79from google.oauth2 import _client
80
81_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
82
83
84class Credentials(credentials.Signing,
85 credentials.Scoped,
86 credentials.Credentials):
87 """Service account credentials
88
89 Usually, you'll create these credentials with one of the helper
90 constructors. To create credentials using a Google service account
91 private key JSON file::
92
93 credentials = service_account.Credentials.from_service_account_file(
94 'service-account.json')
95
96 Or if you already have the service account file loaded::
97
98 service_account_info = json.load(open('service_account.json'))
99 credentials = service_account.Credentials.from_service_account_info(
100 service_account_info)
101
102 Both helper methods pass on arguments to the constructor, so you can
103 specify additional scopes and a subject if necessary::
104
105 credentials = service_account.Credentials.from_service_account_file(
106 'service-account.json',
107 scopes=['email'],
108 subject='user@example.com')
109
110 The credentials are considered immutable. If you want to modify the scopes
111 or the subject used for delegation, use :meth:`with_scopes` or
112 :meth:`with_subject`::
113
114 scoped_credentials = credentials.with_scopes(['email'])
115 delegated_credentials = credentials.with_subject(subject)
116 """
117
118 def __init__(self, signer, service_account_email, token_uri, scopes=None,
119 subject=None, additional_claims=None):
120 """
121 Args:
122 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
123 service_account_email (str): The service account's email.
124 scopes (Sequence[str]): Scopes to request during the authorization
125 grant.
126 token_uri (str): The OAuth 2.0 Token URI.
127 subject (str): For domain-wide delegation, the email address of the
128 user to for which to request delegated access.
129 additional_claims (Mapping[str, str]): Any additional claims for
130 the JWT assertion used in the authorization grant.
131
132 .. note:: Typically one of the helper constructors
133 :meth:`from_service_account_file` or
134 :meth:`from_service_account_info` are used instead of calling the
135 constructor directly.
136 """
137 super(Credentials, self).__init__()
138
139 self._scopes = scopes
140 self._signer = signer
141 self._service_account_email = service_account_email
142 self._subject = subject
143 self._token_uri = token_uri
144
145 if additional_claims is not None:
146 self._additional_claims = additional_claims
147 else:
148 self._additional_claims = {}
149
150 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700151 def _from_signer_and_info(cls, signer, info, **kwargs):
152 """Creates a Credentials instance from a signer and service account
153 info.
154
155 Args:
156 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
157 info (Mapping[str, str]): The service account info.
158 kwargs: Additional arguments to pass to the constructor.
159
160 Returns:
161 google.auth.jwt.Credentials: The constructed credentials.
162
163 Raises:
164 ValueError: If the info is not in the expected format.
165 """
166 return cls(
167 signer,
168 service_account_email=info['client_email'],
169 token_uri=info['token_uri'], **kwargs)
170
171 @classmethod
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700172 def from_service_account_info(cls, info, **kwargs):
173 """Creates a Credentials instance from parsed service account info.
174
175 Args:
176 info (Mapping[str, str]): The service account info in Google
177 format.
178 kwargs: Additional arguments to pass to the constructor.
179
180 Returns:
181 google.auth.service_account.Credentials: The constructed
182 credentials.
183
184 Raises:
185 ValueError: If the info is not in the expected format.
186 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700187 signer = _service_account_info.from_dict(
188 info, require=['client_email', 'token_uri'])
189 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700190
191 @classmethod
192 def from_service_account_file(cls, filename, **kwargs):
193 """Creates a Credentials instance from a service account json file.
194
195 Args:
196 filename (str): The path to the service account json file.
197 kwargs: Additional arguments to pass to the constructor.
198
199 Returns:
200 google.auth.service_account.Credentials: The constructed
201 credentials.
202 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700203 info, signer = _service_account_info.from_filename(
204 filename, require=['client_email', 'token_uri'])
205 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700206
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800207 def to_jwt_credentials(self, audience):
Jon Wayne Parrott70995a92016-10-31 12:45:02 -0700208 """Creates a :class:`google.auth.jwt.Credentials` instance from this
Jon Wayne Parrott7e29eda2016-10-21 15:58:00 -0700209 instance.
210
211 The new instance will use the same private key as this instance and
212 will use this instance's service account email as the issuer and
213 subject.
214
215 This is the same as calling
216 :meth:`jwt.Credentials.from_service_account_file` with the same
217 file used to create these credentials::
218
219 svc_creds = service_account.Credentials.from_service_account_file(
220 'service_account.json')
221 jwt_from_svc = svc_credentials.to_jwt_credentials()
222 # is the same as:
223 jwt_creds = jwt.Credentials.from_service_account_file(
224 'service_account.json')
225
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800226 Args:
227 audience (str): the `aud` claim. The intended audience for the
228 credentials.
229
Jon Wayne Parrott7e29eda2016-10-21 15:58:00 -0700230 Returns:
231 google.auth.jwt.Credentials: A new Credentials instance.
232 """
233 return jwt.Credentials(
234 self._signer,
235 issuer=self._service_account_email,
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800236 subject=self._service_account_email,
237 audience=audience)
Jon Wayne Parrott7e29eda2016-10-21 15:58:00 -0700238
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700239 @property
Jon Wayne Parrott61ffb052016-11-08 09:30:30 -0800240 def service_account_email(self):
241 """The service account email."""
242 return self._service_account_email
243
244 @property
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700245 def requires_scopes(self):
246 """Checks if the credentials requires scopes.
247
248 Returns:
249 bool: True if there are no scopes set otherwise False.
250 """
251 return True if not self._scopes else False
252
253 @_helpers.copy_docstring(credentials.Scoped)
254 def with_scopes(self, scopes):
255 return Credentials(
256 self._signer,
257 service_account_email=self._service_account_email,
258 scopes=scopes,
259 token_uri=self._token_uri,
260 subject=self._subject,
261 additional_claims=self._additional_claims.copy())
262
263 def with_subject(self, subject):
264 """Create a copy of these credentials with the specified subject.
265
266 Args:
267 subject (str): The subject claim.
268
269 Returns:
270 google.auth.service_account.Credentials: A new credentials
271 instance.
272 """
273 return Credentials(
274 self._signer,
275 service_account_email=self._service_account_email,
276 scopes=self._scopes,
277 token_uri=self._token_uri,
278 subject=subject,
279 additional_claims=self._additional_claims.copy())
280
281 def _make_authorization_grant_assertion(self):
282 """Create the OAuth 2.0 assertion.
283
284 This assertion is used during the OAuth 2.0 grant to acquire an
285 access token.
286
287 Returns:
288 bytes: The authorization grant assertion.
289 """
290 now = _helpers.utcnow()
291 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
292 expiry = now + lifetime
293
294 payload = {
295 'iat': _helpers.datetime_to_secs(now),
296 'exp': _helpers.datetime_to_secs(expiry),
297 # The issuer must be the service account email.
298 'iss': self._service_account_email,
299 # The audience must be the auth token endpoint's URI
300 'aud': self._token_uri,
301 'scope': _helpers.scopes_to_string(self._scopes or ())
302 }
303
304 payload.update(self._additional_claims)
305
306 # The subject can be a user email for domain-wide delegation.
307 if self._subject:
308 payload.setdefault('sub', self._subject)
309
310 token = jwt.encode(self._signer, payload)
311
312 return token
313
314 @_helpers.copy_docstring(credentials.Credentials)
315 def refresh(self, request):
316 assertion = self._make_authorization_grant_assertion()
317 access_token, expiry, _ = _client.jwt_grant(
318 request, self._token_uri, assertion)
319 self.token = access_token
320 self.expiry = expiry
321
322 @_helpers.copy_docstring(credentials.Signing)
323 def sign_bytes(self, message):
324 return self._signer.sign(message)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800325
326 @property
327 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800328 def signer(self):
329 return self._signer
330
331 @property
332 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800333 def signer_email(self):
334 return self._service_account_email