blob: 98fd71b04a10bd6bf786b643a04c374baece51fe [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -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"""OAuth 2.0 Credentials.
16
17This module provides credentials based on OAuth 2.0 access and refresh tokens.
18These credentials usually access resources on behalf of a user (resource
19owner).
20
21Specifically, this is intended to use access tokens acquired using the
22`Authorization Code grant`_ and can refresh those tokens using a
23optional `refresh token`_.
24
25Obtaining the initial access and refresh token is outside of the scope of this
26module. Consult `rfc6749 section 4.1`_ for complete details on the
27Authorization Code grant flow.
28
29.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
30.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
31.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
32"""
33
wesley chund0e0aba2020-09-17 09:18:55 -070034from datetime import datetime
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080035import io
36import json
37
arithmetic1728772dac62020-03-27 14:34:13 -070038from google.auth import _cloud_sdk
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070039from google.auth import _helpers
40from google.auth import credentials
Thea Flowers118c0482018-05-24 13:34:07 -070041from google.auth import exceptions
arithmetic172882293fe2021-04-14 11:22:13 -070042from google.oauth2 import reauth
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070043
44
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080045# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
Bu Sun Kim9eec0912019-10-21 17:04:21 -070046_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080047
48
Bu Sun Kim41599ae2020-09-02 12:55:42 -060049class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
Bu Sun Kimb12488c2020-06-10 13:44:07 -070050 """Credentials using OAuth 2.0 access and refresh tokens.
51
52 The credentials are considered immutable. If you want to modify the
53 quota project, use :meth:`with_quota_project` or ::
54
55 credentials = credentials.with_quota_project('myproject-123)
arithmetic172882293fe2021-04-14 11:22:13 -070056
57 If reauth is enabled, `pyu2f` dependency has to be installed in order to use security
58 key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install
59 google-auth[reauth]`.
Bu Sun Kimb12488c2020-06-10 13:44:07 -070060 """
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070061
Bu Sun Kim9eec0912019-10-21 17:04:21 -070062 def __init__(
63 self,
64 token,
65 refresh_token=None,
66 id_token=None,
67 token_uri=None,
68 client_id=None,
69 client_secret=None,
70 scopes=None,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -070071 default_scopes=None,
Bu Sun Kim32d71a52019-12-18 11:30:46 -080072 quota_project_id=None,
wesley chund0e0aba2020-09-17 09:18:55 -070073 expiry=None,
arithmetic172882293fe2021-04-14 11:22:13 -070074 rapt_token=None,
bojeil-googleec2fb182021-07-22 10:01:31 -070075 refresh_handler=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -070076 ):
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070077 """
78 Args:
79 token (Optional(str)): The OAuth 2.0 access token. Can be None
80 if refresh information is provided.
81 refresh_token (str): The OAuth 2.0 refresh token. If specified,
82 credentials can be refreshed.
Jon Wayne Parrott26a16372017-03-28 13:03:33 -070083 id_token (str): The Open ID Connect ID Token.
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070084 token_uri (str): The OAuth 2.0 authorization server's token
85 endpoint URI. Must be specified for refresh, can be left as
86 None if the token can not be refreshed.
87 client_id (str): The OAuth 2.0 client ID. Must be specified for
88 refresh, can be left as None if the token can not be refreshed.
89 client_secret(str): The OAuth 2.0 client secret. Must be specified
90 for refresh, can be left as None if the token can not be
91 refreshed.
Eugene W. Foley49a18c42019-05-22 13:50:38 -040092 scopes (Sequence[str]): The scopes used to obtain authorization.
93 This parameter is used by :meth:`has_scopes`. OAuth 2.0
94 credentials can not request additional scopes after
95 authorization. The scopes must be derivable from the refresh
96 token if refresh information is provided (e.g. The refresh
97 token scopes are a superset of this or contain a wild card
98 scope like 'https://www.googleapis.com/auth/any-api').
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -070099 default_scopes (Sequence[str]): Default scopes passed by a
100 Google client library. Use 'scopes' for user-defined scopes.
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800101 quota_project_id (Optional[str]): The project ID used for quota and billing.
102 This project may be different from the project used to
103 create the credentials.
arithmetic172882293fe2021-04-14 11:22:13 -0700104 rapt_token (Optional[str]): The reauth Proof Token.
bojeil-googleec2fb182021-07-22 10:01:31 -0700105 refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
106 A callable which takes in the HTTP request callable and the list of
107 OAuth scopes and when called returns an access token string for the
108 requested scopes and its expiry datetime. This is useful when no
109 refresh tokens are provided and tokens are obtained by calling
110 some external process on demand. It is particularly useful for
111 retrieving downscoped tokens from a token broker.
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700112 """
113 super(Credentials, self).__init__()
114 self.token = token
wesley chund0e0aba2020-09-17 09:18:55 -0700115 self.expiry = expiry
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700116 self._refresh_token = refresh_token
Jon Wayne Parrott26a16372017-03-28 13:03:33 -0700117 self._id_token = id_token
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700118 self._scopes = scopes
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700119 self._default_scopes = default_scopes
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700120 self._token_uri = token_uri
121 self._client_id = client_id
122 self._client_secret = client_secret
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800123 self._quota_project_id = quota_project_id
arithmetic172882293fe2021-04-14 11:22:13 -0700124 self._rapt_token = rapt_token
bojeil-googleec2fb182021-07-22 10:01:31 -0700125 self.refresh_handler = refresh_handler
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800126
127 def __getstate__(self):
128 """A __getstate__ method must exist for the __setstate__ to be called
129 This is identical to the default implementation.
130 See https://docs.python.org/3.7/library/pickle.html#object.__setstate__
131 """
bojeil-googleec2fb182021-07-22 10:01:31 -0700132 state_dict = self.__dict__.copy()
133 # Remove _refresh_handler function as there are limitations pickling and
134 # unpickling certain callables (lambda, functools.partial instances)
135 # because they need to be importable.
136 # Instead, the refresh_handler setter should be used to repopulate this.
137 del state_dict["_refresh_handler"]
138 return state_dict
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800139
140 def __setstate__(self, d):
141 """Credentials pickled with older versions of the class do not have
142 all the attributes."""
143 self.token = d.get("token")
144 self.expiry = d.get("expiry")
145 self._refresh_token = d.get("_refresh_token")
146 self._id_token = d.get("_id_token")
147 self._scopes = d.get("_scopes")
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700148 self._default_scopes = d.get("_default_scopes")
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800149 self._token_uri = d.get("_token_uri")
150 self._client_id = d.get("_client_id")
151 self._client_secret = d.get("_client_secret")
152 self._quota_project_id = d.get("_quota_project_id")
arithmetic172882293fe2021-04-14 11:22:13 -0700153 self._rapt_token = d.get("_rapt_token")
bojeil-googleec2fb182021-07-22 10:01:31 -0700154 # The refresh_handler setter should be used to repopulate this.
155 self._refresh_handler = None
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700156
157 @property
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -0800158 def refresh_token(self):
159 """Optional[str]: The OAuth 2.0 refresh token."""
160 return self._refresh_token
161
162 @property
wesley chund0e0aba2020-09-17 09:18:55 -0700163 def scopes(self):
164 """Optional[str]: The OAuth 2.0 permission scopes."""
165 return self._scopes
166
167 @property
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -0800168 def token_uri(self):
169 """Optional[str]: The OAuth 2.0 authorization server's token endpoint
170 URI."""
171 return self._token_uri
172
173 @property
Jon Wayne Parrott26a16372017-03-28 13:03:33 -0700174 def id_token(self):
175 """Optional[str]: The Open ID Connect ID Token.
176
177 Depending on the authorization server and the scopes requested, this
178 may be populated when credentials are obtained and updated when
179 :meth:`refresh` is called. This token is a JWT. It can be verified
180 and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
181 """
182 return self._id_token
183
184 @property
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -0800185 def client_id(self):
186 """Optional[str]: The OAuth 2.0 client ID."""
187 return self._client_id
188
189 @property
190 def client_secret(self):
191 """Optional[str]: The OAuth 2.0 client secret."""
192 return self._client_secret
193
194 @property
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700195 def requires_scopes(self):
196 """False: OAuth 2.0 credentials have their scopes set when
197 the initial token is requested and can not be changed."""
198 return False
199
arithmetic172882293fe2021-04-14 11:22:13 -0700200 @property
201 def rapt_token(self):
202 """Optional[str]: The reauth Proof Token."""
203 return self._rapt_token
204
bojeil-googleec2fb182021-07-22 10:01:31 -0700205 @property
206 def refresh_handler(self):
207 """Returns the refresh handler if available.
208
209 Returns:
210 Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:
211 The current refresh handler.
212 """
213 return self._refresh_handler
214
215 @refresh_handler.setter
216 def refresh_handler(self, value):
217 """Updates the current refresh handler.
218
219 Args:
220 value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
221 The updated value of the refresh handler.
222
223 Raises:
224 TypeError: If the value is not a callable or None.
225 """
226 if not callable(value) and value is not None:
227 raise TypeError("The provided refresh_handler is not a callable or None.")
228 self._refresh_handler = value
229
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600230 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700231 def with_quota_project(self, quota_project_id):
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700232
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700233 return self.__class__(
234 self.token,
235 refresh_token=self.refresh_token,
236 id_token=self.id_token,
237 token_uri=self.token_uri,
238 client_id=self.client_id,
239 client_secret=self.client_secret,
240 scopes=self.scopes,
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700241 default_scopes=self.default_scopes,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700242 quota_project_id=quota_project_id,
arithmetic172882293fe2021-04-14 11:22:13 -0700243 rapt_token=self.rapt_token,
Bu Sun Kimb12488c2020-06-10 13:44:07 -0700244 )
245
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700246 @_helpers.copy_docstring(credentials.Credentials)
247 def refresh(self, request):
bojeil-googleec2fb182021-07-22 10:01:31 -0700248 scopes = self._scopes if self._scopes is not None else self._default_scopes
249 # Use refresh handler if available and no refresh token is
250 # available. This is useful in general when tokens are obtained by calling
251 # some external process on demand. It is particularly useful for retrieving
252 # downscoped tokens from a token broker.
253 if self._refresh_token is None and self.refresh_handler:
254 token, expiry = self.refresh_handler(request, scopes=scopes)
255 # Validate returned data.
256 if not isinstance(token, str):
257 raise exceptions.RefreshError(
258 "The refresh_handler returned token is not a string."
259 )
260 if not isinstance(expiry, datetime):
261 raise exceptions.RefreshError(
262 "The refresh_handler returned expiry is not a datetime object."
263 )
264 if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW:
265 raise exceptions.RefreshError(
266 "The credentials returned by the refresh_handler are "
267 "already expired."
268 )
269 self.token = token
270 self.expiry = expiry
271 return
272
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700273 if (
274 self._refresh_token is None
275 or self._token_uri is None
276 or self._client_id is None
277 or self._client_secret is None
278 ):
Thea Flowers118c0482018-05-24 13:34:07 -0700279 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700280 "The credentials do not contain the necessary fields need to "
281 "refresh the access token. You must specify refresh_token, "
282 "token_uri, client_id, and client_secret."
283 )
Thea Flowers118c0482018-05-24 13:34:07 -0700284
arithmetic172882293fe2021-04-14 11:22:13 -0700285 (
286 access_token,
287 refresh_token,
288 expiry,
289 grant_response,
290 rapt_token,
291 ) = reauth.refresh_grant(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700292 request,
293 self._token_uri,
294 self._refresh_token,
295 self._client_id,
296 self._client_secret,
arithmetic172882293fe2021-04-14 11:22:13 -0700297 scopes=scopes,
298 rapt_token=self._rapt_token,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700299 )
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700300
301 self.token = access_token
302 self.expiry = expiry
303 self._refresh_token = refresh_token
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700304 self._id_token = grant_response.get("id_token")
arithmetic172882293fe2021-04-14 11:22:13 -0700305 self._rapt_token = rapt_token
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800306
arithmetic172882293fe2021-04-14 11:22:13 -0700307 if scopes and "scope" in grant_response:
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700308 requested_scopes = frozenset(scopes)
arithmetic172882293fe2021-04-14 11:22:13 -0700309 granted_scopes = frozenset(grant_response["scope"].split())
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700310 scopes_requested_but_not_granted = requested_scopes - granted_scopes
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400311 if scopes_requested_but_not_granted:
312 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700313 "Not all requested scopes were granted by the "
314 "authorization server, missing scopes {}.".format(
315 ", ".join(scopes_requested_but_not_granted)
316 )
317 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400318
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800319 @classmethod
320 def from_authorized_user_info(cls, info, scopes=None):
321 """Creates a Credentials instance from parsed authorized user info.
322
323 Args:
324 info (Mapping[str, str]): The authorized user info in Google
325 format.
326 scopes (Sequence[str]): Optional list of scopes to include in the
327 credentials.
328
329 Returns:
330 google.oauth2.credentials.Credentials: The constructed
331 credentials.
332
333 Raises:
334 ValueError: If the info is not in the expected format.
335 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700336 keys_needed = set(("refresh_token", "client_id", "client_secret"))
Tres Seaver560cf1e2021-08-03 16:35:54 -0400337 missing = keys_needed.difference(info)
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800338
339 if missing:
340 raise ValueError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700341 "Authorized user info was not in the expected format, missing "
342 "fields {}.".format(", ".join(missing))
343 )
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800344
wesley chund0e0aba2020-09-17 09:18:55 -0700345 # access token expiry (datetime obj); auto-expire if not saved
346 expiry = info.get("expiry")
347 if expiry:
348 expiry = datetime.strptime(
349 expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
350 )
351 else:
352 expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW
353
354 # process scopes, which needs to be a seq
355 if scopes is None and "scopes" in info:
356 scopes = info.get("scopes")
357 if isinstance(scopes, str):
358 scopes = scopes.split(" ")
359
Emile Caron530f5f92019-07-26 01:23:25 +0200360 return cls(
wesley chund0e0aba2020-09-17 09:18:55 -0700361 token=info.get("token"),
362 refresh_token=info.get("refresh_token"),
363 token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800364 scopes=scopes,
wesley chund0e0aba2020-09-17 09:18:55 -0700365 client_id=info.get("client_id"),
366 client_secret=info.get("client_secret"),
367 quota_project_id=info.get("quota_project_id"), # may not exist
368 expiry=expiry,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700369 )
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800370
371 @classmethod
372 def from_authorized_user_file(cls, filename, scopes=None):
373 """Creates a Credentials instance from an authorized user json file.
374
375 Args:
376 filename (str): The path to the authorized user json file.
377 scopes (Sequence[str]): Optional list of scopes to include in the
378 credentials.
379
380 Returns:
381 google.oauth2.credentials.Credentials: The constructed
382 credentials.
383
384 Raises:
385 ValueError: If the file is not in the expected format.
386 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700387 with io.open(filename, "r", encoding="utf-8") as json_file:
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800388 data = json.load(json_file)
389 return cls.from_authorized_user_info(data, scopes)
patkasperbfb1f8c2019-12-05 22:03:44 +0100390
391 def to_json(self, strip=None):
392 """Utility function that creates a JSON representation of a Credentials
393 object.
394
395 Args:
396 strip (Sequence[str]): Optional list of members to exclude from the
397 generated JSON.
398
399 Returns:
alvyjudy67d38d82020-06-10 22:23:31 -0400400 str: A JSON representation of this instance. When converted into
401 a dictionary, it can be passed to from_authorized_user_info()
402 to create a new credential instance.
patkasperbfb1f8c2019-12-05 22:03:44 +0100403 """
404 prep = {
405 "token": self.token,
406 "refresh_token": self.refresh_token,
407 "token_uri": self.token_uri,
408 "client_id": self.client_id,
409 "client_secret": self.client_secret,
410 "scopes": self.scopes,
arithmetic172882293fe2021-04-14 11:22:13 -0700411 "rapt_token": self.rapt_token,
patkasperbfb1f8c2019-12-05 22:03:44 +0100412 }
wesley chund0e0aba2020-09-17 09:18:55 -0700413 if self.expiry: # flatten expiry timestamp
414 prep["expiry"] = self.expiry.isoformat() + "Z"
patkasperbfb1f8c2019-12-05 22:03:44 +0100415
wesley chund0e0aba2020-09-17 09:18:55 -0700416 # Remove empty entries (those which are None)
patkasperbfb1f8c2019-12-05 22:03:44 +0100417 prep = {k: v for k, v in prep.items() if v is not None}
418
419 # Remove entries that explicitely need to be removed
420 if strip is not None:
421 prep = {k: v for k, v in prep.items() if k not in strip}
422
423 return json.dumps(prep)
arithmetic1728772dac62020-03-27 14:34:13 -0700424
425
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600426class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
arithmetic1728772dac62020-03-27 14:34:13 -0700427 """Access token credentials for user account.
428
429 Obtain the access token for a given user account or the current active
430 user account with the ``gcloud auth print-access-token`` command.
431
432 Args:
433 account (Optional[str]): Account to get the access token for. If not
434 specified, the current active account will be used.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700435 quota_project_id (Optional[str]): The project ID used for quota
436 and billing.
arithmetic1728772dac62020-03-27 14:34:13 -0700437 """
438
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700439 def __init__(self, account=None, quota_project_id=None):
arithmetic1728772dac62020-03-27 14:34:13 -0700440 super(UserAccessTokenCredentials, self).__init__()
441 self._account = account
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700442 self._quota_project_id = quota_project_id
arithmetic1728772dac62020-03-27 14:34:13 -0700443
444 def with_account(self, account):
445 """Create a new instance with the given account.
446
447 Args:
448 account (str): Account to get the access token for.
449
450 Returns:
451 google.oauth2.credentials.UserAccessTokenCredentials: The created
452 credentials with the given account.
453 """
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700454 return self.__class__(account=account, quota_project_id=self._quota_project_id)
455
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600456 @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700457 def with_quota_project(self, quota_project_id):
458 return self.__class__(account=self._account, quota_project_id=quota_project_id)
arithmetic1728772dac62020-03-27 14:34:13 -0700459
460 def refresh(self, request):
461 """Refreshes the access token.
462
463 Args:
464 request (google.auth.transport.Request): This argument is required
465 by the base class interface but not used in this implementation,
466 so just set it to `None`.
467
468 Raises:
469 google.auth.exceptions.UserAccessTokenError: If the access token
470 refresh failed.
471 """
472 self.token = _cloud_sdk.get_auth_access_token(self._account)
473
474 @_helpers.copy_docstring(credentials.Credentials)
475 def before_request(self, request, method, url, headers):
476 self.refresh(request)
477 self.apply(headers)