OLD | NEW |
(Empty) | |
| 1 # 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 |
| 16 """Interfaces for credentials.""" |
| 17 |
| 18 import abc |
| 19 |
| 20 import six |
| 21 |
| 22 from google.auth import _helpers |
| 23 |
| 24 |
| 25 @six.add_metaclass(abc.ABCMeta) |
| 26 class Credentials(object): |
| 27 """Base class for all credentials. |
| 28 |
| 29 All credentials have a :attr:`token` that is used for authentication and |
| 30 may also optionally set an :attr:`expiry` to indicate when the token will |
| 31 no longer be valid. |
| 32 |
| 33 Most credentials will be :attr:`invalid` until :meth:`refresh` is called. |
| 34 Credentials can do this automatically before the first HTTP request in |
| 35 :meth:`before_request`. |
| 36 |
| 37 Although the token and expiration will change as the credentials are |
| 38 :meth:`refreshed <refresh>` and used, credentials should be considered |
| 39 immutable. Various credentials will accept configuration such as private |
| 40 keys, scopes, and other options. These options are not changeable after |
| 41 construction. Some classes will provide mechanisms to copy the credentials |
| 42 with modifications such as :meth:`ScopedCredentials.with_scopes`. |
| 43 """ |
| 44 def __init__(self): |
| 45 self.token = None |
| 46 """str: The bearer token that can be used in HTTP headers to make |
| 47 authenticated requests.""" |
| 48 self.expiry = None |
| 49 """Optional[datetime]: When the token expires and is no longer valid. |
| 50 If this is None, the token is assumed to never expire.""" |
| 51 |
| 52 @property |
| 53 def expired(self): |
| 54 """Checks if the credentials are expired. |
| 55 |
| 56 Note that credentials can be invalid but not expired becaue Credentials |
| 57 with :attr:`expiry` set to None is considered to never expire. |
| 58 """ |
| 59 if not self.expiry: |
| 60 return False |
| 61 |
| 62 # Remove 5 minutes from expiry to err on the side of reporting |
| 63 # expiration early so that we avoid the 401-refresh-retry loop. |
| 64 skewed_expiry = self.expiry - _helpers.CLOCK_SKEW |
| 65 return _helpers.utcnow() >= skewed_expiry |
| 66 |
| 67 @property |
| 68 def valid(self): |
| 69 """Checks the validity of the credentials. |
| 70 |
| 71 This is True if the credentials have a :attr:`token` and the token |
| 72 is not :attr:`expired`. |
| 73 """ |
| 74 return self.token is not None and not self.expired |
| 75 |
| 76 @abc.abstractmethod |
| 77 def refresh(self, request): |
| 78 """Refreshes the access token. |
| 79 |
| 80 Args: |
| 81 request (google.auth.transport.Request): The object used to make |
| 82 HTTP requests. |
| 83 |
| 84 Raises: |
| 85 google.auth.exceptions.RefreshError: If the credentials could |
| 86 not be refreshed. |
| 87 """ |
| 88 # pylint: disable=missing-raises-doc |
| 89 # (pylint doesn't recognize that this is abstract) |
| 90 raise NotImplementedError('Refresh must be implemented') |
| 91 |
| 92 def apply(self, headers, token=None): |
| 93 """Apply the token to the authentication header. |
| 94 |
| 95 Args: |
| 96 headers (Mapping): The HTTP request headers. |
| 97 token (Optional[str]): If specified, overrides the current access |
| 98 token. |
| 99 """ |
| 100 headers['authorization'] = 'Bearer {}'.format( |
| 101 _helpers.from_bytes(token or self.token)) |
| 102 |
| 103 def before_request(self, request, method, url, headers): |
| 104 """Performs credential-specific before request logic. |
| 105 |
| 106 Refreshes the credentials if necessary, then calls :meth:`apply` to |
| 107 apply the token to the authentication header. |
| 108 |
| 109 Args: |
| 110 request (google.auth.transport.Request): The object used to make |
| 111 HTTP requests. |
| 112 method (str): The request's HTTP method or the RPC method being |
| 113 invoked. |
| 114 url (str): The request's URI or the RPC service's URI. |
| 115 headers (Mapping): The request's headers. |
| 116 """ |
| 117 # pylint: disable=unused-argument |
| 118 # (Subclasses may use these arguments to ascertain information about |
| 119 # the http request.) |
| 120 if not self.valid: |
| 121 self.refresh(request) |
| 122 self.apply(headers) |
| 123 |
| 124 |
| 125 @six.add_metaclass(abc.ABCMeta) |
| 126 class Scoped(object): |
| 127 """Interface for scoped credentials. |
| 128 |
| 129 OAuth 2.0-based credentials allow limiting access using scopes as described |
| 130 in `RFC6749 Section 3.3`_. |
| 131 If a credential class implements this interface then the credentials either |
| 132 use scopes in their implementation. |
| 133 |
| 134 Some credentials require scopes in order to obtain a token. You can check |
| 135 if scoping is necessary with :attr:`requires_scopes`:: |
| 136 |
| 137 if credentials.requires_scopes: |
| 138 # Scoping is required. |
| 139 credentials = credentials.create_scoped(['one', 'two']) |
| 140 |
| 141 Credentials that require scopes must either be constructed with scopes:: |
| 142 |
| 143 credentials = SomeScopedCredentials(scopes=['one', 'two']) |
| 144 |
| 145 Or must copy an existing instance using :meth:`with_scopes`:: |
| 146 |
| 147 scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) |
| 148 |
| 149 Some credentials have scopes but do not allow or require scopes to be set, |
| 150 these credentials can be used as-is. |
| 151 |
| 152 .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 |
| 153 """ |
| 154 def __init__(self): |
| 155 super(Scoped, self).__init__() |
| 156 self._scopes = None |
| 157 |
| 158 @property |
| 159 def scopes(self): |
| 160 """Sequence[str]: the credentials' current set of scopes.""" |
| 161 return self._scopes |
| 162 |
| 163 @abc.abstractproperty |
| 164 def requires_scopes(self): |
| 165 """True if these credentials require scopes to obtain an access token. |
| 166 """ |
| 167 return False |
| 168 |
| 169 @abc.abstractmethod |
| 170 def with_scopes(self, scopes): |
| 171 """Create a copy of these credentials with the specified scopes. |
| 172 |
| 173 Args: |
| 174 scopes (Sequence[str]): The list of scopes to request. |
| 175 |
| 176 Raises: |
| 177 NotImplementedError: If the credentials' scopes can not be changed. |
| 178 This can be avoided by checking :attr:`requires_scopes` before |
| 179 calling this method. |
| 180 """ |
| 181 raise NotImplementedError('This class does not require scoping.') |
| 182 |
| 183 def has_scopes(self, scopes): |
| 184 """Checks if the credentials have the given scopes. |
| 185 |
| 186 .. warning: This method is not guaranteed to be accurate if the |
| 187 credentials are :attr:`~Credentials.invalid`. |
| 188 |
| 189 Returns: |
| 190 bool: True if the credentials have the given scopes. |
| 191 """ |
| 192 return set(scopes).issubset(set(self._scopes or [])) |
| 193 |
| 194 |
| 195 def with_scopes_if_required(credentials, scopes): |
| 196 """Creates a copy of the credentials with scopes if scoping is required. |
| 197 |
| 198 This helper function is useful when you do not know (or care to know) the |
| 199 specific type of credentials you are using (such as when you use |
| 200 :func:`google.auth.default`). This function will call |
| 201 :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if |
| 202 the credentials require scoping. Otherwise, it will return the credentials |
| 203 as-is. |
| 204 |
| 205 Args: |
| 206 credentials (google.auth.credentials.Credentials): The credentials to |
| 207 scope if necessary. |
| 208 scopes (Sequence[str]): The list of scopes to use. |
| 209 |
| 210 Returns: |
| 211 google.auth.credentials.Credentials: Either a new set of scoped |
| 212 credentials, or the passed in credentials instance if no scoping |
| 213 was required. |
| 214 """ |
| 215 if isinstance(credentials, Scoped) and credentials.requires_scopes: |
| 216 return credentials.with_scopes(scopes) |
| 217 else: |
| 218 return credentials |
| 219 |
| 220 |
| 221 @six.add_metaclass(abc.ABCMeta) |
| 222 class Signing(object): |
| 223 """Interface for credentials that can cryptographically sign messages.""" |
| 224 |
| 225 @abc.abstractmethod |
| 226 def sign_bytes(self, message): |
| 227 """Signs the given message. |
| 228 |
| 229 Args: |
| 230 message (bytes): The message to sign. |
| 231 |
| 232 Returns: |
| 233 bytes: The message's cryptographic signature. |
| 234 """ |
| 235 # pylint: disable=missing-raises-doc,redundant-returns-doc |
| 236 # (pylint doesn't recognize that this is abstract) |
| 237 raise NotImplementedError('Sign bytes must be implemented.') |
| 238 |
| 239 @abc.abstractproperty |
| 240 def signer_email(self): |
| 241 """Optional[str]: An email address that identifies the signer.""" |
| 242 # pylint: disable=missing-raises-doc |
| 243 # (pylint doesn't recognize that this is abstract) |
| 244 raise NotImplementedError('Signer email must be implemented.') |
| 245 |
| 246 @abc.abstractproperty |
| 247 def signer(self): |
| 248 """google.auth.crypt.Signer: The signer used to sign bytes.""" |
| 249 # pylint: disable=missing-raises-doc |
| 250 # (pylint doesn't recognize that this is abstract) |
| 251 raise NotImplementedError('Signer must be implemented.') |
OLD | NEW |