1# -*- coding: utf-8 -*- 
    2""" 
    3oauthlib.oauth2.rfc6749 
    4~~~~~~~~~~~~~~~~~~~~~~~ 
    5 
    6This module is an implementation of various logic needed 
    7for consuming OAuth 2.0 RFC6749. 
    8""" 
    9import base64 
    10import hashlib 
    11import time 
    12import warnings 
    13 
    14from oauthlib.common import UNICODE_ASCII_CHARACTER_SET, generate_token 
    15from oauthlib.oauth2.rfc6749 import tokens 
    16from oauthlib.oauth2.rfc6749.errors import ( 
    17    InsecureTransportError, TokenExpiredError, 
    18) 
    19from oauthlib.oauth2.rfc6749.parameters import ( 
    20    parse_expires, 
    21    parse_token_response, prepare_token_request, 
    22    prepare_token_revocation_request, 
    23) 
    24from oauthlib.oauth2.rfc6749.utils import is_secure_transport 
    25 
    26AUTH_HEADER = 'auth_header' 
    27URI_QUERY = 'query' 
    28BODY = 'body' 
    29 
    30FORM_ENC_HEADERS = { 
    31    'Content-Type': 'application/x-www-form-urlencoded' 
    32} 
    33 
    34 
    35class Client: 
    36    """Base OAuth2 client responsible for access token management. 
    37 
    38    This class also acts as a generic interface providing methods common to all 
    39    client types such as ``prepare_authorization_request`` and 
    40    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are 
    41    the recommended way of interacting with clients (as opposed to the abstract 
    42    prepare uri/body/etc methods). They are recommended over the older set 
    43    because they are easier to use (more consistent) and add a few additional 
    44    security checks, such as HTTPS and state checking. 
    45 
    46    Some of these methods require further implementation only provided by the 
    47    specific purpose clients such as 
    48    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always 
    49    seek to use the client class matching the OAuth workflow you need. For 
    50    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`. 
    51 
    52    """ 
    53    refresh_token_key = 'refresh_token' 
    54 
    55    def __init__(self, client_id, 
    56                 default_token_placement=AUTH_HEADER, 
    57                 token_type='Bearer', 
    58                 access_token=None, 
    59                 refresh_token=None, 
    60                 mac_key=None, 
    61                 mac_algorithm=None, 
    62                 token=None, 
    63                 scope=None, 
    64                 state=None, 
    65                 redirect_url=None, 
    66                 state_generator=generate_token, 
    67                 code_verifier=None, 
    68                 code_challenge=None, 
    69                 code_challenge_method=None, 
    70                 **kwargs): 
    71        """Initialize a client with commonly used attributes. 
    72 
    73        :param client_id: Client identifier given by the OAuth provider upon 
    74        registration. 
    75 
    76        :param default_token_placement: Tokens can be supplied in the Authorization 
    77        header (default), the URL query component (``query``) or the request 
    78        body (``body``). 
    79 
    80        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this 
    81        if you specify the ``access_token`` parameter and know it is of a 
    82        different token type, such as a MAC, JWT or SAML token. Can 
    83        also be supplied as ``token_type`` inside the ``token`` dict parameter. 
    84 
    85        :param access_token: An access token (string) used to authenticate 
    86        requests to protected resources. Can also be supplied inside the 
    87        ``token`` dict parameter. 
    88 
    89        :param refresh_token: A refresh token (string) used to refresh expired 
    90        tokens. Can also be supplied inside the ``token`` dict parameter. 
    91 
    92        :param mac_key: Encryption key used with MAC tokens. 
    93 
    94        :param mac_algorithm:  Hashing algorithm for MAC tokens. 
    95 
    96        :param token: A dict of token attributes such as ``access_token``, 
    97        ``token_type`` and ``expires_at``. 
    98 
    99        :param scope: A list of default scopes to request authorization for. 
    100 
    101        :param state: A CSRF protection string used during authorization. 
    102 
    103        :param redirect_url: The redirection endpoint on the client side to which 
    104        the user returns after authorization. 
    105 
    106        :param state_generator: A no argument state generation callable. Defaults 
    107        to :py:meth:`oauthlib.common.generate_token`. 
    108 
    109        :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the 
    110        authorization request to the token request. 
    111 
    112        :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the 
    113        authorization request, to be verified against later. 
    114 
    115        :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge. 
    116        Defaults to "plain" if not present in the request. 
    117        """ 
    118 
    119        self.client_id = client_id 
    120        self.default_token_placement = default_token_placement 
    121        self.token_type = token_type 
    122        self.access_token = access_token 
    123        self.refresh_token = refresh_token 
    124        self.mac_key = mac_key 
    125        self.mac_algorithm = mac_algorithm 
    126        self.token = token or {} 
    127        self.scope = scope 
    128        self.state_generator = state_generator 
    129        self.state = state 
    130        self.redirect_url = redirect_url 
    131        self.code_verifier = code_verifier 
    132        self.code_challenge = code_challenge 
    133        self.code_challenge_method = code_challenge_method 
    134        self.code = None 
    135        self.expires_in = None 
    136        self._expires_at = None 
    137        self.populate_token_attributes(self.token) 
    138 
    139    @property 
    140    def token_types(self): 
    141        """Supported token types and their respective methods 
    142 
    143        Additional tokens can be supported by extending this dictionary. 
    144 
    145        The Bearer token spec is stable and safe to use. 
    146 
    147        The MAC token spec is not yet stable and support for MAC tokens 
    148        is experimental and currently matching version 00 of the spec. 
    149        """ 
    150        return { 
    151            'Bearer': self._add_bearer_token, 
    152            'MAC': self._add_mac_token 
    153        } 
    154 
    155    def prepare_request_uri(self, *args, **kwargs): 
    156        """Abstract method used to create request URIs.""" 
    157        raise NotImplementedError("Must be implemented by inheriting classes.") 
    158 
    159    def prepare_request_body(self, *args, **kwargs): 
    160        """Abstract method used to create request bodies.""" 
    161        raise NotImplementedError("Must be implemented by inheriting classes.") 
    162 
    163    def parse_request_uri_response(self, *args, **kwargs): 
    164        """Abstract method used to parse redirection responses.""" 
    165        raise NotImplementedError("Must be implemented by inheriting classes.") 
    166 
    167    def add_token(self, uri, http_method='GET', body=None, headers=None, 
    168                  token_placement=None, **kwargs): 
    169        """Add token to the request uri, body or authorization header. 
    170 
    171        The access token type provides the client with the information 
    172        required to successfully utilize the access token to make a protected 
    173        resource request (along with type-specific attributes).  The client 
    174        MUST NOT use an access token if it does not understand the token 
    175        type. 
    176 
    177        For example, the "bearer" token type defined in 
    178        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access 
    179        token string in the request: 
    180 
    181        .. code-block:: http 
    182 
    183            GET /resource/1 HTTP/1.1 
    184            Host: example.com 
    185            Authorization: Bearer mF_9.B5f-4.1JqM 
    186 
    187        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is 
    188        utilized by issuing a MAC key together with the access token which is 
    189        used to sign certain components of the HTTP requests: 
    190 
    191        .. code-block:: http 
    192 
    193            GET /resource/1 HTTP/1.1 
    194            Host: example.com 
    195            Authorization: MAC id="h480djs93hd8", 
    196                                nonce="274312:dj83hs9s", 
    197                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE=" 
    198 
    199        .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2 
    200        .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2 
    201        """ 
    202        if not is_secure_transport(uri): 
    203            raise InsecureTransportError() 
    204 
    205        token_placement = token_placement or self.default_token_placement 
    206 
    207        case_insensitive_token_types = { 
    208            k.lower(): v for k, v in self.token_types.items()} 
    209        if self.token_type.lower() not in case_insensitive_token_types: 
    210            raise ValueError("Unsupported token type: %s" % self.token_type) 
    211 
    212        if not (self.access_token or self.token.get('access_token')): 
    213            raise ValueError("Missing access token.") 
    214 
    215        if self._expires_at and self._expires_at < time.time(): 
    216            raise TokenExpiredError() 
    217 
    218        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body, 
    219                                                                     headers, token_placement, **kwargs) 
    220 
    221    def prepare_authorization_request(self, authorization_url, state=None, 
    222                                      redirect_url=None, scope=None, **kwargs): 
    223        """Prepare the authorization request. 
    224 
    225        This is the first step in many OAuth flows in which the user is 
    226        redirected to a certain authorization URL. This method adds 
    227        required parameters to the authorization URL. 
    228 
    229        :param authorization_url: Provider authorization endpoint URL. 
    230        :param state: CSRF protection string. Will be automatically created if 
    231            not provided. The generated state is available via the ``state`` 
    232            attribute. Clients should verify that the state is unchanged and 
    233            present in the authorization response. This verification is done 
    234            automatically if using the ``authorization_response`` parameter 
    235            with ``prepare_token_request``. 
    236        :param redirect_url: Redirect URL to which the user will be returned 
    237            after authorization. Must be provided unless previously setup with 
    238            the provider. If provided then it must also be provided in the 
    239            token request. 
    240        :param scope: List of scopes to request. Must be equal to 
    241            or a subset of the scopes granted when obtaining the refresh 
    242            token. If none is provided, the ones provided in the constructor are 
    243            used. 
    244        :param kwargs: Additional parameters to included in the request. 
    245        :returns: The prepared request tuple with (url, headers, body). 
    246        """ 
    247        if not is_secure_transport(authorization_url): 
    248            raise InsecureTransportError() 
    249 
    250        self.state = state or self.state_generator() 
    251        self.redirect_url = redirect_url or self.redirect_url 
    252        # do not assign scope to self automatically anymore 
    253        scope = self.scope if scope is None else scope 
    254        auth_url = self.prepare_request_uri( 
    255            authorization_url, redirect_uri=self.redirect_url, 
    256            scope=scope, state=self.state, **kwargs) 
    257        return auth_url, FORM_ENC_HEADERS, '' 
    258 
    259    def prepare_token_request(self, token_url, authorization_response=None, 
    260                              redirect_url=None, state=None, body='', **kwargs): 
    261        """Prepare a token creation request. 
    262 
    263        Note that these requests usually require client authentication, either 
    264        by including client_id or a set of provider specific authentication 
    265        credentials. 
    266 
    267        :param token_url: Provider token creation endpoint URL. 
    268        :param authorization_response: The full redirection URL string, i.e. 
    269            the location to which the user was redirected after successful 
    270            authorization. Used to mine credentials needed to obtain a token 
    271            in this step, such as authorization code. 
    272        :param redirect_url: The redirect_url supplied with the authorization 
    273            request (if there was one). 
    274        :param state: 
    275        :param body: Existing request body (URL encoded string) to embed parameters 
    276                     into. This may contain extra parameters. Default ''. 
    277        :param kwargs: Additional parameters to included in the request. 
    278        :returns: The prepared request tuple with (url, headers, body). 
    279        """ 
    280        if not is_secure_transport(token_url): 
    281            raise InsecureTransportError() 
    282 
    283        state = state or self.state 
    284        if authorization_response: 
    285            self.parse_request_uri_response( 
    286                authorization_response, state=state) 
    287        self.redirect_url = redirect_url or self.redirect_url 
    288        body = self.prepare_request_body(body=body, 
    289                                         redirect_uri=self.redirect_url, **kwargs) 
    290 
    291        return token_url, FORM_ENC_HEADERS, body 
    292 
    293    def prepare_refresh_token_request(self, token_url, refresh_token=None, 
    294                                      body='', scope=None, **kwargs): 
    295        """Prepare an access token refresh request. 
    296 
    297        Expired access tokens can be replaced by new access tokens without 
    298        going through the OAuth dance if the client obtained a refresh token. 
    299        This refresh token and authentication credentials can be used to 
    300        obtain a new access token, and possibly a new refresh token. 
    301 
    302        :param token_url: Provider token refresh endpoint URL. 
    303        :param refresh_token: Refresh token string. 
    304        :param body: Existing request body (URL encoded string) to embed parameters 
    305            into. This may contain extra parameters. Default ''. 
    306        :param scope: List of scopes to request. Must be equal to 
    307            or a subset of the scopes granted when obtaining the refresh 
    308            token. If none is provided, the ones provided in the constructor are 
    309            used. 
    310        :param kwargs: Additional parameters to included in the request. 
    311        :returns: The prepared request tuple with (url, headers, body). 
    312        """ 
    313        if not is_secure_transport(token_url): 
    314            raise InsecureTransportError() 
    315 
    316        # do not assign scope to self automatically anymore 
    317        scope = self.scope if scope is None else scope 
    318        body = self.prepare_refresh_body(body=body, 
    319                                         refresh_token=refresh_token, scope=scope, **kwargs) 
    320        return token_url, FORM_ENC_HEADERS, body 
    321 
    322    def prepare_token_revocation_request(self, revocation_url, token, 
    323                                         token_type_hint="access_token", body='', callback=None, **kwargs): 
    324        """Prepare a token revocation request. 
    325 
    326        :param revocation_url: Provider token revocation endpoint URL. 
    327        :param token: The access or refresh token to be revoked (string). 
    328        :param token_type_hint: ``"access_token"`` (default) or 
    329            ``"refresh_token"``. This is optional and if you wish to not pass it you 
    330            must provide ``token_type_hint=None``. 
    331        :param body: 
    332        :param callback: A jsonp callback such as ``package.callback`` to be invoked 
    333            upon receiving the response. Not that it should not include a () suffix. 
    334        :param kwargs: Additional parameters to included in the request. 
    335        :returns: The prepared request tuple with (url, headers, body). 
    336 
    337        Note that JSONP request may use GET requests as the parameters will 
    338        be added to the request URL query as opposed to the request body. 
    339 
    340        An example of a revocation request 
    341 
    342        .. code-block:: http 
    343 
    344            POST /revoke HTTP/1.1 
    345            Host: server.example.com 
    346            Content-Type: application/x-www-form-urlencoded 
    347            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 
    348 
    349            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token 
    350 
    351        An example of a jsonp revocation request 
    352 
    353        .. code-block:: http 
    354 
    355            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1 
    356            Host: server.example.com 
    357            Content-Type: application/x-www-form-urlencoded 
    358            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 
    359 
    360        and an error response 
    361 
    362        .. code-block:: javascript 
    363 
    364            package.myCallback({"error":"unsupported_token_type"}); 
    365 
    366        Note that these requests usually require client credentials, client_id in 
    367        the case for public clients and provider specific authentication 
    368        credentials for confidential clients. 
    369        """ 
    370        if not is_secure_transport(revocation_url): 
    371            raise InsecureTransportError() 
    372 
    373        return prepare_token_revocation_request(revocation_url, token, 
    374                                                token_type_hint=token_type_hint, body=body, callback=callback, 
    375                                                **kwargs) 
    376 
    377    def parse_request_body_response(self, body, scope=None, **kwargs): 
    378        """Parse the JSON response body. 
    379 
    380        If the access token request is valid and authorized, the 
    381        authorization server issues an access token as described in 
    382        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request 
    383        failed client authentication or is invalid, the authorization server 
    384        returns an error response as described in `Section 5.2`_. 
    385 
    386        :param body: The response body from the token request. 
    387        :param scope: Scopes originally requested. If none is provided, the ones 
    388            provided in the constructor are used. 
    389        :return: Dictionary of token parameters. 
    390        :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error` 
    391            if response is invalid. 
    392 
    393        These response are json encoded and could easily be parsed without 
    394        the assistance of OAuthLib. However, there are a few subtle issues 
    395        to be aware of regarding the response which are helpfully addressed 
    396        through the raising of various errors. 
    397 
    398        A successful response should always contain 
    399 
    400        **access_token** 
    401                The access token issued by the authorization server. Often 
    402                a random string. 
    403 
    404        **token_type** 
    405            The type of the token issued as described in `Section 7.1`_. 
    406            Commonly ``Bearer``. 
    407 
    408        While it is not mandated it is recommended that the provider include 
    409 
    410        **expires_in** 
    411            The lifetime in seconds of the access token.  For 
    412            example, the value "3600" denotes that the access token will 
    413            expire in one hour from the time the response was generated. 
    414            If omitted, the authorization server SHOULD provide the 
    415            expiration time via other means or document the default value. 
    416 
    417         **scope** 
    418            Providers may supply this in all responses but are required to only 
    419            if it has changed since the authorization request. 
    420 
    421        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1 
    422        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2 
    423        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1 
    424        """ 
    425        scope = self.scope if scope is None else scope 
    426        self.token = parse_token_response(body, scope=scope) 
    427        self.populate_token_attributes(self.token) 
    428        return self.token 
    429 
    430    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs): 
    431        """Prepare an access token request, using a refresh token. 
    432 
    433        If the authorization server issued a refresh token to the client, the 
    434        client makes a refresh request to the token endpoint by adding the 
    435        following parameters using the `application/x-www-form-urlencoded` 
    436        format in the HTTP request entity-body: 
    437 
    438        :param refresh_token: REQUIRED.  The refresh token issued to the client. 
    439        :param scope:  OPTIONAL.  The scope of the access request as described by 
    440            Section 3.3.  The requested scope MUST NOT include any scope 
    441            not originally granted by the resource owner, and if omitted is 
    442            treated as equal to the scope originally granted by the 
    443            resource owner. Note that if none is provided, the ones provided 
    444            in the constructor are used if any. 
    445        """ 
    446        refresh_token = refresh_token or self.refresh_token 
    447        scope = self.scope if scope is None else scope 
    448        return prepare_token_request(self.refresh_token_key, body=body, scope=scope, 
    449                                     refresh_token=refresh_token, **kwargs) 
    450 
    451    def _add_bearer_token(self, uri, http_method='GET', body=None, 
    452                          headers=None, token_placement=None): 
    453        """Add a bearer token to the request uri, body or authorization header.""" 
    454        if token_placement == AUTH_HEADER: 
    455            headers = tokens.prepare_bearer_headers(self.access_token, headers) 
    456 
    457        elif token_placement == URI_QUERY: 
    458            uri = tokens.prepare_bearer_uri(self.access_token, uri) 
    459 
    460        elif token_placement == BODY: 
    461            body = tokens.prepare_bearer_body(self.access_token, body) 
    462 
    463        else: 
    464            raise ValueError("Invalid token placement.") 
    465        return uri, headers, body 
    466 
    467    def create_code_verifier(self, length): 
    468        """Create PKCE **code_verifier** used in computing **code_challenge**. 
    469        See `RFC7636 Section 4.1`_ 
    470 
    471        :param length: REQUIRED. The length of the code_verifier. 
    472 
    473        The client first creates a code verifier, "code_verifier", for each 
    474        OAuth 2.0 [RFC6749] Authorization Request, in the following manner: 
    475 
    476        .. code-block:: text 
    477 
    478               code_verifier = high-entropy cryptographic random STRING using the 
    479               unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" 
    480               from Section 2.3 of [RFC3986], with a minimum length of 43 characters 
    481               and a maximum length of 128 characters. 
    482 
    483        .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1 
    484        """ 
    485        code_verifier = None 
    486 
    487        if not length >= 43: 
    488            raise ValueError("Length must be greater than or equal to 43") 
    489 
    490        if not length <= 128: 
    491            raise ValueError("Length must be less than or equal to 128") 
    492 
    493        code_verifier = generate_token(length, UNICODE_ASCII_CHARACTER_SET + "-._~") 
    494 
    495        self.code_verifier = code_verifier 
    496 
    497        return code_verifier 
    498 
    499    def create_code_challenge(self, code_verifier, code_challenge_method=None): 
    500        """Create PKCE **code_challenge** derived from the  **code_verifier**. 
    501        See `RFC7636 Section 4.2`_ 
    502 
    503        :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`. 
    504        :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`. 
    505 
    506               The client then creates a code challenge derived from the code 
    507               verifier by using one of the following transformations on the code 
    508               verifier:: 
    509 
    510                   plain 
    511                      code_challenge = code_verifier 
    512                   S256 
    513                      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) 
    514 
    515               If the client is capable of using `S256`, it MUST use `S256`, as 
    516               `S256` is Mandatory To Implement (MTI) on the server.  Clients are 
    517               permitted to use `plain` only if they cannot support `S256` for some 
    518               technical reason and know via out-of-band configuration that the 
    519               server supports `plain`. 
    520 
    521               The plain transformation is for compatibility with existing 
    522               deployments and for constrained environments that can't use the S256 transformation. 
    523 
    524        .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2 
    525        """ 
    526        code_challenge = None 
    527 
    528        if code_verifier is None: 
    529            raise ValueError("Invalid code_verifier") 
    530 
    531        if code_challenge_method is None: 
    532            code_challenge_method = "plain" 
    533            self.code_challenge_method = code_challenge_method 
    534            code_challenge = code_verifier 
    535            self.code_challenge = code_challenge 
    536 
    537        if code_challenge_method == "S256": 
    538            h = hashlib.sha256() 
    539            h.update(code_verifier.encode(encoding='ascii')) 
    540            sha256_val = h.digest() 
    541            code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val)) 
    542            # replace '+' with '-', '/' with '_', and remove trailing '=' 
    543            code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "") 
    544            self.code_challenge = code_challenge 
    545 
    546        return code_challenge 
    547 
    548    def _add_mac_token(self, uri, http_method='GET', body=None, 
    549                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs): 
    550        """Add a MAC token to the request authorization header. 
    551 
    552        Warning: MAC token support is experimental as the spec is not yet stable. 
    553        """ 
    554        if token_placement != AUTH_HEADER: 
    555            raise ValueError("Invalid token placement.") 
    556 
    557        headers = tokens.prepare_mac_header(self.access_token, uri, 
    558                                            self.mac_key, http_method, headers=headers, body=body, ext=ext, 
    559                                            hash_algorithm=self.mac_algorithm, **kwargs) 
    560        return uri, headers, body 
    561 
    562    def _populate_attributes(self, response): 
    563        warnings.warn("Please switch to the public method " 
    564                      "populate_token_attributes.", DeprecationWarning) 
    565        return self.populate_token_attributes(response) 
    566 
    567    def populate_code_attributes(self, response): 
    568        """Add attributes from an auth code response to self.""" 
    569 
    570        if 'code' in response: 
    571            self.code = response.get('code') 
    572 
    573    def populate_token_attributes(self, response): 
    574        """Add attributes from a token exchange response to self.""" 
    575 
    576        if 'access_token' in response: 
    577            self.access_token = response.get('access_token') 
    578 
    579        if 'refresh_token' in response: 
    580            self.refresh_token = response.get('refresh_token') 
    581 
    582        if 'token_type' in response: 
    583            self.token_type = response.get('token_type') 
    584 
    585        vin, vat, v_at = parse_expires(response) 
    586        if vin: 
    587            self.expires_in = vin 
    588        if vat: 
    589            self.expires_at = vat 
    590        if v_at: 
    591            self._expires_at = v_at 
    592 
    593        if 'mac_key' in response: 
    594            self.mac_key = response.get('mac_key') 
    595 
    596        if 'mac_algorithm' in response: 
    597            self.mac_algorithm = response.get('mac_algorithm')