blob: 8d9974ce1555a1a5d9a09dfd372bbf3ad41c158a [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -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
16"""Interfaces for credentials."""
17
18import abc
19
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070020from google.auth import _helpers
21
22
Tres Seaver560cf1e2021-08-03 16:35:54 -040023class Credentials(object, metaclass=abc.ABCMeta):
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070024 """Base class for all credentials.
25
26 All credentials have a :attr:`token` that is used for authentication and
27 may also optionally set an :attr:`expiry` to indicate when the token will
28 no longer be valid.
29
30 Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
31 Credentials can do this automatically before the first HTTP request in
32 :meth:`before_request`.
33
34 Although the token and expiration will change as the credentials are
35 :meth:`refreshed <refresh>` and used, credentials should be considered
36 immutable. Various credentials will accept configuration such as private
37 keys, scopes, and other options. These options are not changeable after
38 construction. Some classes will provide mechanisms to copy the credentials
39 with modifications such as :meth:`ScopedCredentials.with_scopes`.
40 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -070041
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070042 def __init__(self):
43 self.token = None
44 """str: The bearer token that can be used in HTTP headers to make
45 authenticated requests."""
46 self.expiry = None
47 """Optional[datetime]: When the token expires and is no longer valid.
48 If this is None, the token is assumed to never expire."""
Bu Sun Kim3dda7b22020-07-09 10:39:39 -070049 self._quota_project_id = None
50 """Optional[str]: Project to use for quota and billing purposes."""
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070051
52 @property
53 def expired(self):
54 """Checks if the credentials are expired.
55
Craig Citro2f5cb2d2018-05-14 23:29:46 -070056 Note that credentials can be invalid but not expired because
57 Credentials with :attr:`expiry` set to None is considered to never
58 expire.
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070059 """
Jon Wayne Parrott7af9f662017-05-08 09:40:56 -070060 if not self.expiry:
61 return False
62
Bu Sun Kim2d3b8d12020-12-08 14:31:41 -070063 # Remove 10 seconds from expiry to err on the side of reporting
Jon Wayne Parrott7af9f662017-05-08 09:40:56 -070064 # expiration early so that we avoid the 401-refresh-retry loop.
arithmetic1728738611b2021-09-09 17:09:54 -070065 skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
Jon Wayne Parrott7af9f662017-05-08 09:40:56 -070066 return _helpers.utcnow() >= skewed_expiry
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070067
68 @property
69 def valid(self):
70 """Checks the validity of the credentials.
71
72 This is True if the credentials have a :attr:`token` and the token
73 is not :attr:`expired`.
74 """
75 return self.token is not None and not self.expired
76
Bu Sun Kim3dda7b22020-07-09 10:39:39 -070077 @property
78 def quota_project_id(self):
79 """Project to use for quota and billing purposes."""
80 return self._quota_project_id
81
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070082 @abc.abstractmethod
83 def refresh(self, request):
84 """Refreshes the access token.
85
86 Args:
87 request (google.auth.transport.Request): The object used to make
88 HTTP requests.
89
90 Raises:
91 google.auth.exceptions.RefreshError: If the credentials could
92 not be refreshed.
93 """
94 # pylint: disable=missing-raises-doc
95 # (pylint doesn't recognize that this is abstract)
Bu Sun Kim9eec0912019-10-21 17:04:21 -070096 raise NotImplementedError("Refresh must be implemented")
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -070097
98 def apply(self, headers, token=None):
99 """Apply the token to the authentication header.
100
101 Args:
102 headers (Mapping): The HTTP request headers.
103 token (Optional[str]): If specified, overrides the current access
104 token.
105 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700106 headers["authorization"] = "Bearer {}".format(
107 _helpers.from_bytes(token or self.token)
108 )
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700109 if self.quota_project_id:
110 headers["x-goog-user-project"] = self.quota_project_id
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700111
112 def before_request(self, request, method, url, headers):
113 """Performs credential-specific before request logic.
114
115 Refreshes the credentials if necessary, then calls :meth:`apply` to
116 apply the token to the authentication header.
117
118 Args:
Jon Wayne Parrotta0425492016-10-17 10:48:35 -0700119 request (google.auth.transport.Request): The object used to make
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700120 HTTP requests.
Jon Wayne Parrotta2098192017-02-22 09:27:32 -0800121 method (str): The request's HTTP method or the RPC method being
122 invoked.
123 url (str): The request's URI or the RPC service's URI.
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700124 headers (Mapping): The request's headers.
125 """
126 # pylint: disable=unused-argument
127 # (Subclasses may use these arguments to ascertain information about
128 # the http request.)
129 if not self.valid:
130 self.refresh(request)
131 self.apply(headers)
132
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600133
134class CredentialsWithQuotaProject(Credentials):
135 """Abstract base for credentials supporting ``with_quota_project`` factory"""
136
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700137 def with_quota_project(self, quota_project_id):
arithmetic1728d32f7df2020-09-03 09:55:20 -0700138 """Returns a copy of these credentials with a modified quota project.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700139
140 Args:
141 quota_project_id (str): The project to use for quota and
142 billing purposes
143
144 Returns:
145 google.oauth2.credentials.Credentials: A new credentials instance.
146 """
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600147 raise NotImplementedError("This credential does not support quota project.")
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700148
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700149
Tres Seaverb096a3d2017-10-30 16:12:37 -0400150class AnonymousCredentials(Credentials):
151 """Credentials that do not provide any authentication information.
152
153 These are useful in the case of services that support anonymous access or
154 local service emulators that do not use credentials.
155 """
156
157 @property
158 def expired(self):
159 """Returns `False`, anonymous credentials never expire."""
160 return False
161
162 @property
163 def valid(self):
164 """Returns `True`, anonymous credentials are always valid."""
165 return True
166
167 def refresh(self, request):
168 """Raises :class:`ValueError``, anonymous credentials cannot be
169 refreshed."""
170 raise ValueError("Anonymous credentials cannot be refreshed.")
171
172 def apply(self, headers, token=None):
173 """Anonymous credentials do nothing to the request.
174
175 The optional ``token`` argument is not supported.
176
177 Raises:
178 ValueError: If a token was specified.
179 """
180 if token is not None:
181 raise ValueError("Anonymous credentials don't support tokens.")
182
183 def before_request(self, request, method, url, headers):
184 """Anonymous credentials do nothing to the request."""
185
186
Tres Seaver560cf1e2021-08-03 16:35:54 -0400187class ReadOnlyScoped(object, metaclass=abc.ABCMeta):
Tres Seaver42468322017-09-11 15:36:53 -0400188 """Interface for credentials whose scopes can be queried.
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700189
190 OAuth 2.0-based credentials allow limiting access using scopes as described
191 in `RFC6749 Section 3.3`_.
192 If a credential class implements this interface then the credentials either
193 use scopes in their implementation.
194
195 Some credentials require scopes in order to obtain a token. You can check
196 if scoping is necessary with :attr:`requires_scopes`::
197
198 if credentials.requires_scopes:
199 # Scoping is required.
Ondrej Medekf682cb22017-11-27 18:33:46 +0100200 credentials = credentials.with_scopes(scopes=['one', 'two'])
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700201
202 Credentials that require scopes must either be constructed with scopes::
203
204 credentials = SomeScopedCredentials(scopes=['one', 'two'])
205
206 Or must copy an existing instance using :meth:`with_scopes`::
207
208 scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
209
210 Some credentials have scopes but do not allow or require scopes to be set,
211 these credentials can be used as-is.
212
213 .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
214 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700215
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700216 def __init__(self):
Jon Wayne Parrott4460a962017-09-12 10:01:23 -0700217 super(ReadOnlyScoped, self).__init__()
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700218 self._scopes = None
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700219 self._default_scopes = None
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700220
221 @property
222 def scopes(self):
223 """Sequence[str]: the credentials' current set of scopes."""
224 return self._scopes
225
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700226 @property
227 def default_scopes(self):
228 """Sequence[str]: the credentials' current set of default scopes."""
229 return self._default_scopes
230
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700231 @abc.abstractproperty
232 def requires_scopes(self):
233 """True if these credentials require scopes to obtain an access token.
234 """
235 return False
236
Tres Seaver42468322017-09-11 15:36:53 -0400237 def has_scopes(self, scopes):
238 """Checks if the credentials have the given scopes.
239
240 .. warning: This method is not guaranteed to be accurate if the
241 credentials are :attr:`~Credentials.invalid`.
242
Danny Hermes369e2a72017-12-13 12:08:15 -0800243 Args:
244 scopes (Sequence[str]): The list of scopes to check.
245
Tres Seaver42468322017-09-11 15:36:53 -0400246 Returns:
247 bool: True if the credentials have the given scopes.
248 """
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700249 credential_scopes = (
250 self._scopes if self._scopes is not None else self._default_scopes
251 )
252 return set(scopes).issubset(set(credential_scopes or []))
Tres Seaver42468322017-09-11 15:36:53 -0400253
254
Jon Wayne Parrott4460a962017-09-12 10:01:23 -0700255class Scoped(ReadOnlyScoped):
Tres Seaver42468322017-09-11 15:36:53 -0400256 """Interface for credentials whose scopes can be replaced while copying.
257
258 OAuth 2.0-based credentials allow limiting access using scopes as described
259 in `RFC6749 Section 3.3`_.
260 If a credential class implements this interface then the credentials either
261 use scopes in their implementation.
262
263 Some credentials require scopes in order to obtain a token. You can check
264 if scoping is necessary with :attr:`requires_scopes`::
265
266 if credentials.requires_scopes:
267 # Scoping is required.
268 credentials = credentials.create_scoped(['one', 'two'])
269
270 Credentials that require scopes must either be constructed with scopes::
271
272 credentials = SomeScopedCredentials(scopes=['one', 'two'])
273
274 Or must copy an existing instance using :meth:`with_scopes`::
275
276 scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
277
278 Some credentials have scopes but do not allow or require scopes to be set,
279 these credentials can be used as-is.
280
281 .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
282 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700283
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700284 @abc.abstractmethod
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700285 def with_scopes(self, scopes, default_scopes=None):
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700286 """Create a copy of these credentials with the specified scopes.
287
288 Args:
Danny Hermes369e2a72017-12-13 12:08:15 -0800289 scopes (Sequence[str]): The list of scopes to attach to the
290 current credentials.
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700291
292 Raises:
293 NotImplementedError: If the credentials' scopes can not be changed.
294 This can be avoided by checking :attr:`requires_scopes` before
295 calling this method.
296 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700297 raise NotImplementedError("This class does not require scoping.")
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700298
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700299
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700300def with_scopes_if_required(credentials, scopes, default_scopes=None):
Jon Wayne Parrottf89a3cf2016-10-31 10:52:57 -0700301 """Creates a copy of the credentials with scopes if scoping is required.
302
303 This helper function is useful when you do not know (or care to know) the
304 specific type of credentials you are using (such as when you use
305 :func:`google.auth.default`). This function will call
306 :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
307 the credentials require scoping. Otherwise, it will return the credentials
308 as-is.
309
310 Args:
Jon Wayne Parrottbdbf2b12016-11-10 15:00:29 -0800311 credentials (google.auth.credentials.Credentials): The credentials to
Jon Wayne Parrott8c3a10b2016-11-10 12:42:50 -0800312 scope if necessary.
Jon Wayne Parrottf89a3cf2016-10-31 10:52:57 -0700313 scopes (Sequence[str]): The list of scopes to use.
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700314 default_scopes (Sequence[str]): Default scopes passed by a
315 Google client library. Use 'scopes' for user-defined scopes.
Jon Wayne Parrottf89a3cf2016-10-31 10:52:57 -0700316
317 Returns:
Jon Wayne Parrottbdbf2b12016-11-10 15:00:29 -0800318 google.auth.credentials.Credentials: Either a new set of scoped
Jon Wayne Parrott8c3a10b2016-11-10 12:42:50 -0800319 credentials, or the passed in credentials instance if no scoping
320 was required.
Jon Wayne Parrottf89a3cf2016-10-31 10:52:57 -0700321 """
322 if isinstance(credentials, Scoped) and credentials.requires_scopes:
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700323 return credentials.with_scopes(scopes, default_scopes=default_scopes)
Jon Wayne Parrottf89a3cf2016-10-31 10:52:57 -0700324 else:
325 return credentials
326
327
Tres Seaver560cf1e2021-08-03 16:35:54 -0400328class Signing(object, metaclass=abc.ABCMeta):
Jon Wayne Parrott71ce2a02016-10-14 14:08:10 -0700329 """Interface for credentials that can cryptographically sign messages."""
330
331 @abc.abstractmethod
332 def sign_bytes(self, message):
333 """Signs the given message.
334
335 Args:
336 message (bytes): The message to sign.
337
338 Returns:
339 bytes: The message's cryptographic signature.
340 """
341 # pylint: disable=missing-raises-doc,redundant-returns-doc
342 # (pylint doesn't recognize that this is abstract)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700343 raise NotImplementedError("Sign bytes must be implemented.")
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800344
345 @abc.abstractproperty
346 def signer_email(self):
347 """Optional[str]: An email address that identifies the signer."""
348 # pylint: disable=missing-raises-doc
349 # (pylint doesn't recognize that this is abstract)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700350 raise NotImplementedError("Signer email must be implemented.")
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800351
352 @abc.abstractproperty
353 def signer(self):
354 """google.auth.crypt.Signer: The signer used to sign bytes."""
355 # pylint: disable=missing-raises-doc
356 # (pylint doesn't recognize that this is abstract)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700357 raise NotImplementedError("Signer must be implemented.")