blob: 4c4c1c03aede500b74c463696d089e5f86e5bcf9 [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
74import io
75import json
76
77from google.auth import _helpers
78from google.auth import credentials
79from google.auth import crypt
80from google.auth import jwt
81from google.oauth2 import _client
82
83_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
84
85
86class Credentials(credentials.Signing,
87 credentials.Scoped,
88 credentials.Credentials):
89 """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)
118 """
119
120 def __init__(self, signer, service_account_email, token_uri, scopes=None,
121 subject=None, additional_claims=None):
122 """
123 Args:
124 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
125 service_account_email (str): The service account's email.
126 scopes (Sequence[str]): Scopes to request during the authorization
127 grant.
128 token_uri (str): The OAuth 2.0 Token URI.
129 subject (str): For domain-wide delegation, the email address of the
130 user to for which to request delegated access.
131 additional_claims (Mapping[str, str]): Any additional claims for
132 the JWT assertion used in the authorization grant.
133
134 .. note:: Typically one of the helper constructors
135 :meth:`from_service_account_file` or
136 :meth:`from_service_account_info` are used instead of calling the
137 constructor directly.
138 """
139 super(Credentials, self).__init__()
140
141 self._scopes = scopes
142 self._signer = signer
143 self._service_account_email = service_account_email
144 self._subject = subject
145 self._token_uri = token_uri
146
147 if additional_claims is not None:
148 self._additional_claims = additional_claims
149 else:
150 self._additional_claims = {}
151
152 @classmethod
153 def from_service_account_info(cls, info, **kwargs):
154 """Creates a Credentials instance from parsed service account info.
155
156 Args:
157 info (Mapping[str, str]): The service account info in Google
158 format.
159 kwargs: Additional arguments to pass to the constructor.
160
161 Returns:
162 google.auth.service_account.Credentials: The constructed
163 credentials.
164
165 Raises:
166 ValueError: If the info is not in the expected format.
167 """
168 try:
169 email = info['client_email']
170 key_id = info['private_key_id']
171 private_key = info['private_key']
172 token_uri = info['token_uri']
173 except KeyError:
174 raise ValueError(
175 'Service account info was not in the expected format.')
176
177 signer = crypt.Signer.from_string(private_key, key_id)
178
179 return cls(
180 signer, service_account_email=email, token_uri=token_uri, **kwargs)
181
182 @classmethod
183 def from_service_account_file(cls, filename, **kwargs):
184 """Creates a Credentials instance from a service account json file.
185
186 Args:
187 filename (str): The path to the service account json file.
188 kwargs: Additional arguments to pass to the constructor.
189
190 Returns:
191 google.auth.service_account.Credentials: The constructed
192 credentials.
193 """
194 with io.open(filename, 'r', encoding='utf-8') as json_file:
195 info = json.load(json_file)
196 return cls.from_service_account_info(info, **kwargs)
197
198 @property
199 def requires_scopes(self):
200 """Checks if the credentials requires scopes.
201
202 Returns:
203 bool: True if there are no scopes set otherwise False.
204 """
205 return True if not self._scopes else False
206
207 @_helpers.copy_docstring(credentials.Scoped)
208 def with_scopes(self, scopes):
209 return Credentials(
210 self._signer,
211 service_account_email=self._service_account_email,
212 scopes=scopes,
213 token_uri=self._token_uri,
214 subject=self._subject,
215 additional_claims=self._additional_claims.copy())
216
217 def with_subject(self, subject):
218 """Create a copy of these credentials with the specified subject.
219
220 Args:
221 subject (str): The subject claim.
222
223 Returns:
224 google.auth.service_account.Credentials: A new credentials
225 instance.
226 """
227 return Credentials(
228 self._signer,
229 service_account_email=self._service_account_email,
230 scopes=self._scopes,
231 token_uri=self._token_uri,
232 subject=subject,
233 additional_claims=self._additional_claims.copy())
234
235 def _make_authorization_grant_assertion(self):
236 """Create the OAuth 2.0 assertion.
237
238 This assertion is used during the OAuth 2.0 grant to acquire an
239 access token.
240
241 Returns:
242 bytes: The authorization grant assertion.
243 """
244 now = _helpers.utcnow()
245 lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
246 expiry = now + lifetime
247
248 payload = {
249 'iat': _helpers.datetime_to_secs(now),
250 'exp': _helpers.datetime_to_secs(expiry),
251 # The issuer must be the service account email.
252 'iss': self._service_account_email,
253 # The audience must be the auth token endpoint's URI
254 'aud': self._token_uri,
255 'scope': _helpers.scopes_to_string(self._scopes or ())
256 }
257
258 payload.update(self._additional_claims)
259
260 # The subject can be a user email for domain-wide delegation.
261 if self._subject:
262 payload.setdefault('sub', self._subject)
263
264 token = jwt.encode(self._signer, payload)
265
266 return token
267
268 @_helpers.copy_docstring(credentials.Credentials)
269 def refresh(self, request):
270 assertion = self._make_authorization_grant_assertion()
271 access_token, expiry, _ = _client.jwt_grant(
272 request, self._token_uri, assertion)
273 self.token = access_token
274 self.expiry = expiry
275
276 @_helpers.copy_docstring(credentials.Signing)
277 def sign_bytes(self, message):
278 return self._signer.sign(message)