blob: f8a27bfa7ba7170e553aac74888a48f5b9c09f32 [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
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
82_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
83
84
85class Credentials(credentials.Signing,
86 credentials.Scoped,
87 credentials.Credentials):
88 """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)
117 """
118
119 def __init__(self, signer, service_account_email, token_uri, scopes=None,
120 subject=None, additional_claims=None):
121 """
122 Args:
123 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
124 service_account_email (str): The service account's email.
125 scopes (Sequence[str]): Scopes to request during the authorization
126 grant.
127 token_uri (str): The OAuth 2.0 Token URI.
128 subject (str): For domain-wide delegation, the email address of the
129 user to for which to request delegated access.
130 additional_claims (Mapping[str, str]): Any additional claims for
131 the JWT assertion used in the authorization grant.
132
133 .. note:: Typically one of the helper constructors
134 :meth:`from_service_account_file` or
135 :meth:`from_service_account_info` are used instead of calling the
136 constructor directly.
137 """
138 super(Credentials, self).__init__()
139
140 self._scopes = scopes
141 self._signer = signer
142 self._service_account_email = service_account_email
143 self._subject = subject
144 self._token_uri = token_uri
145
146 if additional_claims is not None:
147 self._additional_claims = additional_claims
148 else:
149 self._additional_claims = {}
150
151 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700152 def _from_signer_and_info(cls, signer, info, **kwargs):
153 """Creates a Credentials instance from a signer and service account
154 info.
155
156 Args:
157 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
158 info (Mapping[str, str]): The service account info.
159 kwargs: Additional arguments to pass to the constructor.
160
161 Returns:
162 google.auth.jwt.Credentials: The constructed credentials.
163
164 Raises:
165 ValueError: If the info is not in the expected format.
166 """
167 return cls(
168 signer,
169 service_account_email=info['client_email'],
170 token_uri=info['token_uri'], **kwargs)
171
172 @classmethod
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700173 def from_service_account_info(cls, info, **kwargs):
174 """Creates a Credentials instance from parsed service account info.
175
176 Args:
177 info (Mapping[str, str]): The service account info in Google
178 format.
179 kwargs: Additional arguments to pass to the constructor.
180
181 Returns:
182 google.auth.service_account.Credentials: The constructed
183 credentials.
184
185 Raises:
186 ValueError: If the info is not in the expected format.
187 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700188 signer = _service_account_info.from_dict(
189 info, require=['client_email', 'token_uri'])
190 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700191
192 @classmethod
193 def from_service_account_file(cls, filename, **kwargs):
194 """Creates a Credentials instance from a service account json file.
195
196 Args:
197 filename (str): The path to the service account json file.
198 kwargs: Additional arguments to pass to the constructor.
199
200 Returns:
201 google.auth.service_account.Credentials: The constructed
202 credentials.
203 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700204 info, signer = _service_account_info.from_filename(
205 filename, require=['client_email', 'token_uri'])
206 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700207
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700208 @property
Jon Wayne Parrott61ffb052016-11-08 09:30:30 -0800209 def service_account_email(self):
210 """The service account email."""
211 return self._service_account_email
212
213 @property
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700214 def requires_scopes(self):
215 """Checks if the credentials requires scopes.
216
217 Returns:
218 bool: True if there are no scopes set otherwise False.
219 """
220 return True if not self._scopes else False
221
222 @_helpers.copy_docstring(credentials.Scoped)
223 def with_scopes(self, scopes):
224 return Credentials(
225 self._signer,
226 service_account_email=self._service_account_email,
227 scopes=scopes,
228 token_uri=self._token_uri,
229 subject=self._subject,
230 additional_claims=self._additional_claims.copy())
231
232 def with_subject(self, subject):
233 """Create a copy of these credentials with the specified subject.
234
235 Args:
236 subject (str): The subject claim.
237
238 Returns:
239 google.auth.service_account.Credentials: A new credentials
240 instance.
241 """
242 return Credentials(
243 self._signer,
244 service_account_email=self._service_account_email,
245 scopes=self._scopes,
246 token_uri=self._token_uri,
247 subject=subject,
248 additional_claims=self._additional_claims.copy())
249
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700250 def with_claims(self, additional_claims):
251 """Returns a copy of these credentials with modified claims.
252
253 Args:
254 additional_claims (Mapping[str, str]): Any additional claims for
255 the JWT payload. This will be merged with the current
256 additional claims.
257
258 Returns:
259 google.auth.service_account.Credentials: A new credentials
260 instance.
261 """
262 new_additional_claims = copy.deepcopy(self._additional_claims)
263 new_additional_claims.update(additional_claims or {})
264
265 return Credentials(
266 self._signer,
267 service_account_email=self._service_account_email,
268 scopes=self._scopes,
269 token_uri=self._token_uri,
270 subject=self._subject,
271 additional_claims=new_additional_claims)
272
Jon Wayne Parrottab9eba32016-10-17 10:49:57 -0700273 def _make_authorization_grant_assertion(self):
274 """Create the OAuth 2.0 assertion.
275
276 This assertion is used during the OAuth 2.0 grant to acquire an
277 access token.
278
279 Returns:
280 bytes: The authorization grant assertion.
281 """
282 now = _helpers.utcnow()
283 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
284 expiry = now + lifetime
285
286 payload = {
287 'iat': _helpers.datetime_to_secs(now),
288 'exp': _helpers.datetime_to_secs(expiry),
289 # The issuer must be the service account email.
290 'iss': self._service_account_email,
291 # The audience must be the auth token endpoint's URI
292 'aud': self._token_uri,
293 'scope': _helpers.scopes_to_string(self._scopes or ())
294 }
295
296 payload.update(self._additional_claims)
297
298 # The subject can be a user email for domain-wide delegation.
299 if self._subject:
300 payload.setdefault('sub', self._subject)
301
302 token = jwt.encode(self._signer, payload)
303
304 return token
305
306 @_helpers.copy_docstring(credentials.Credentials)
307 def refresh(self, request):
308 assertion = self._make_authorization_grant_assertion()
309 access_token, expiry, _ = _client.jwt_grant(
310 request, self._token_uri, assertion)
311 self.token = access_token
312 self.expiry = expiry
313
314 @_helpers.copy_docstring(credentials.Signing)
315 def sign_bytes(self, message):
316 return self._signer.sign(message)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800317
318 @property
319 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800320 def signer(self):
321 return self._signer
322
323 @property
324 @_helpers.copy_docstring(credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800325 def signer_email(self):
326 return self._service_account_email