1""" 
    2oauthlib.oauth2.rfc6749.tokens 
    3~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    4 
    5This module contains methods for adding two types of access tokens to requests. 
    6 
    7- Bearer https://tools.ietf.org/html/rfc6750 
    8- MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 
    9""" 
    10import hashlib 
    11import hmac 
    12import warnings 
    13from binascii import b2a_base64 
    14from urllib.parse import urlparse 
    15 
    16from oauthlib import common 
    17from oauthlib.common import add_params_to_qs, add_params_to_uri 
    18 
    19from . import utils 
    20 
    21 
    22class OAuth2Token(dict): 
    23 
    24    def __init__(self, params, old_scope=None): 
    25        super().__init__(params) 
    26        self._new_scope = None 
    27        if params.get('scope'): 
    28            self._new_scope = set(utils.scope_to_list(params['scope'])) 
    29        if old_scope is not None: 
    30            self._old_scope = set(utils.scope_to_list(old_scope)) 
    31            if self._new_scope is None: 
    32                # the rfc says that if the scope hasn't changed, it's optional 
    33                # in params so set the new scope to the old scope 
    34                self._new_scope = self._old_scope 
    35        else: 
    36            self._old_scope = self._new_scope 
    37 
    38    @property 
    39    def scope_changed(self): 
    40        return self._new_scope != self._old_scope 
    41 
    42    @property 
    43    def old_scope(self): 
    44        return utils.list_to_scope(self._old_scope) 
    45 
    46    @property 
    47    def old_scopes(self): 
    48        return list(self._old_scope) 
    49 
    50    @property 
    51    def scope(self): 
    52        return utils.list_to_scope(self._new_scope) 
    53 
    54    @property 
    55    def scopes(self): 
    56        return list(self._new_scope) 
    57 
    58    @property 
    59    def missing_scopes(self): 
    60        return list(self._old_scope - self._new_scope) 
    61 
    62    @property 
    63    def additional_scopes(self): 
    64        return list(self._new_scope - self._old_scope) 
    65 
    66 
    67def prepare_mac_header(token, uri, key, http_method, 
    68                       nonce=None, 
    69                       headers=None, 
    70                       body=None, 
    71                       ext='', 
    72                       hash_algorithm='hmac-sha-1', 
    73                       issue_time=None, 
    74                       draft=0): 
    75    """Add an `MAC Access Authentication`_ signature to headers. 
    76 
    77    Unlike OAuth 1, this HMAC signature does not require inclusion of the 
    78    request payload/body, neither does it use a combination of client_secret 
    79    and token_secret but rather a mac_key provided together with the access 
    80    token. 
    81 
    82    Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256", 
    83    `extension algorithms`_ are not supported. 
    84 
    85    Example MAC Authorization header, linebreaks added for clarity 
    86 
    87    Authorization: MAC id="h480djs93hd8", 
    88                       nonce="1336363200:dj83hs9s", 
    89                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM=" 
    90 
    91    .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 
    92    .. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1 
    93 
    94    :param token: 
    95    :param uri: Request URI. 
    96    :param key: MAC given provided by token endpoint. 
    97    :param http_method: HTTP Request method. 
    98    :param nonce: 
    99    :param headers: Request headers as a dictionary. 
    100    :param body: 
    101    :param ext: 
    102    :param hash_algorithm: HMAC algorithm provided by token endpoint. 
    103    :param issue_time: Time when the MAC credentials were issued (datetime). 
    104    :param draft: MAC authentication specification version. 
    105    :return: headers dictionary with the authorization field added. 
    106    """ 
    107    http_method = http_method.upper() 
    108    host, port = utils.host_from_uri(uri) 
    109 
    110    if hash_algorithm.lower() == 'hmac-sha-1': 
    111        h = hashlib.sha1 
    112    elif hash_algorithm.lower() == 'hmac-sha-256': 
    113        h = hashlib.sha256 
    114    else: 
    115        raise ValueError('unknown hash algorithm') 
    116 
    117    if draft == 0: 
    118        nonce = nonce or '{}:{}'.format(utils.generate_age(issue_time), 
    119                                          common.generate_nonce()) 
    120    else: 
    121        ts = common.generate_timestamp() 
    122        nonce = common.generate_nonce() 
    123 
    124    sch, net, path, par, query, fra = urlparse(uri) 
    125 
    126    request_uri = path + '?' + query if query else path 
    127 
    128    # Hash the body/payload 
    129    if body is not None and draft == 0: 
    130        body = body.encode('utf-8') 
    131        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8') 
    132    else: 
    133        bodyhash = '' 
    134 
    135    # Create the normalized base string 
    136    base = [] 
    137    if draft == 0: 
    138        base.append(nonce) 
    139    else: 
    140        base.append(ts) 
    141        base.append(nonce) 
    142    base.append(http_method.upper()) 
    143    base.append(request_uri) 
    144    base.append(host) 
    145    base.append(port) 
    146    if draft == 0: 
    147        base.append(bodyhash) 
    148    base.append(ext or '') 
    149    base_string = '\n'.join(base) + '\n' 
    150 
    151    # hmac struggles with unicode strings - http://bugs.python.org/issue5285 
    152    if isinstance(key, str): 
    153        key = key.encode('utf-8') 
    154    sign = hmac.new(key, base_string.encode('utf-8'), h) 
    155    sign = b2a_base64(sign.digest())[:-1].decode('utf-8') 
    156 
    157    header = [] 
    158    header.append('MAC id="%s"' % token) 
    159    if draft != 0: 
    160        header.append('ts="%s"' % ts) 
    161    header.append('nonce="%s"' % nonce) 
    162    if bodyhash: 
    163        header.append('bodyhash="%s"' % bodyhash) 
    164    if ext: 
    165        header.append('ext="%s"' % ext) 
    166    header.append('mac="%s"' % sign) 
    167 
    168    headers = headers or {} 
    169    headers['Authorization'] = ', '.join(header) 
    170    return headers 
    171 
    172 
    173def prepare_bearer_uri(token, uri): 
    174    """Add a `Bearer Token`_ to the request URI. 
    175    Not recommended, use only if client can't use authorization header or body. 
    176 
    177    http://www.example.com/path?access_token=h480djs93hd8 
    178 
    179    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 
    180 
    181    :param token: 
    182    :param uri: 
    183    """ 
    184    return add_params_to_uri(uri, [(('access_token', token))]) 
    185 
    186 
    187def prepare_bearer_headers(token, headers=None): 
    188    """Add a `Bearer Token`_ to the request URI. 
    189    Recommended method of passing bearer tokens. 
    190 
    191    Authorization: Bearer h480djs93hd8 
    192 
    193    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 
    194 
    195    :param token: 
    196    :param headers: 
    197    """ 
    198    headers = headers or {} 
    199    headers['Authorization'] = 'Bearer %s' % token 
    200    return headers 
    201 
    202 
    203def prepare_bearer_body(token, body=''): 
    204    """Add a `Bearer Token`_ to the request body. 
    205 
    206    access_token=h480djs93hd8 
    207 
    208    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 
    209 
    210    :param token: 
    211    :param body: 
    212    """ 
    213    return add_params_to_qs(body, [(('access_token', token))]) 
    214 
    215 
    216def random_token_generator(request, refresh_token=False): 
    217    """ 
    218    :param request: OAuthlib request. 
    219    :type request: oauthlib.common.Request 
    220    :param refresh_token: 
    221    """ 
    222    return common.generate_token() 
    223 
    224 
    225def signed_token_generator(private_pem, **kwargs): 
    226    """ 
    227    :param private_pem: 
    228    """ 
    229    def signed_token_generator(request): 
    230        request.claims = kwargs 
    231        return common.generate_signed_token(private_pem, request) 
    232 
    233    return signed_token_generator 
    234 
    235 
    236def get_token_from_header(request): 
    237    """ 
    238    Helper function to extract a token from the request header. 
    239 
    240    :param request: OAuthlib request. 
    241    :type request: oauthlib.common.Request 
    242    :return: Return the token or None if the Authorization header is malformed. 
    243    """ 
    244    token = None 
    245 
    246    if 'Authorization' in request.headers: 
    247        split_header = request.headers.get('Authorization').split() 
    248        if len(split_header) == 2 and split_header[0].lower() == 'bearer': 
    249            token = split_header[1] 
    250    else: 
    251        token = request.access_token 
    252 
    253    return token 
    254 
    255 
    256class TokenBase: 
    257    __slots__ = () 
    258 
    259    def __call__(self, request, refresh_token=False): 
    260        raise NotImplementedError('Subclasses must implement this method.') 
    261 
    262    def validate_request(self, request): 
    263        """ 
    264        :param request: OAuthlib request. 
    265        :type request: oauthlib.common.Request 
    266        """ 
    267        raise NotImplementedError('Subclasses must implement this method.') 
    268 
    269    def estimate_type(self, request): 
    270        """ 
    271        :param request: OAuthlib request. 
    272        :type request: oauthlib.common.Request 
    273        """ 
    274        raise NotImplementedError('Subclasses must implement this method.') 
    275 
    276 
    277class BearerToken(TokenBase): 
    278    __slots__ = ( 
    279        'request_validator', 'token_generator', 
    280        'refresh_token_generator', 'expires_in' 
    281    ) 
    282 
    283    def __init__(self, request_validator=None, token_generator=None, 
    284                 expires_in=None, refresh_token_generator=None): 
    285        self.request_validator = request_validator 
    286        self.token_generator = token_generator or random_token_generator 
    287        self.refresh_token_generator = ( 
    288            refresh_token_generator or self.token_generator 
    289        ) 
    290        self.expires_in = expires_in or 3600 
    291 
    292    def create_token(self, request, refresh_token=False, **kwargs): 
    293        """ 
    294        Create a BearerToken, by default without refresh token. 
    295 
    296        :param request: OAuthlib request. 
    297        :type request: oauthlib.common.Request 
    298        :param refresh_token: 
    299        """ 
    300        if "save_token" in kwargs: 
    301            warnings.warn("`save_token` has been deprecated, it was not called internally." 
    302                          "If you do, call `request_validator.save_token()` instead.", 
    303                          DeprecationWarning) 
    304 
    305        expires_in = self.expires_in(request) if callable(self.expires_in) else self.expires_in 
    306 
    307        request.expires_in = expires_in 
    308 
    309        token = { 
    310            'access_token': self.token_generator(request), 
    311            'expires_in': expires_in, 
    312            'token_type': 'Bearer', 
    313        } 
    314 
    315        # If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but 
    316        # there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so 
    317        # all tokens issued are for the entire set of requested scopes. 
    318        if request.scopes is not None: 
    319            token['scope'] = ' '.join(request.scopes) 
    320 
    321        if refresh_token: 
    322            if (request.refresh_token and 
    323                    not self.request_validator.rotate_refresh_token(request)): 
    324                token['refresh_token'] = request.refresh_token 
    325            else: 
    326                token['refresh_token'] = self.refresh_token_generator(request) 
    327 
    328        token.update(request.extra_credentials or {}) 
    329        return OAuth2Token(token) 
    330 
    331    def validate_request(self, request): 
    332        """ 
    333        :param request: OAuthlib request. 
    334        :type request: oauthlib.common.Request 
    335        """ 
    336        token = get_token_from_header(request) 
    337        return self.request_validator.validate_bearer_token( 
    338            token, request.scopes, request) 
    339 
    340    def estimate_type(self, request): 
    341        """ 
    342        :param request: OAuthlib request. 
    343        :type request: oauthlib.common.Request 
    344        """ 
    345        if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer': 
    346            return 9 
    347        elif request.access_token is not None: 
    348            return 5 
    349        else: 
    350            return 0