1# -*- coding: utf-8 -*- 
    2""" 
    3oauthlib.oauth1.rfc5849.endpoints.access_token 
    4~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    5 
    6This module is an implementation of the access token provider logic of 
    7OAuth 1.0 RFC 5849. It validates the correctness of access token requests, 
    8creates and persists tokens as well as create the proper response to be 
    9returned to the client. 
    10""" 
    11import logging 
    12 
    13from oauthlib.common import urlencode 
    14 
    15from .. import errors 
    16from .base import BaseEndpoint 
    17 
    18log = logging.getLogger(__name__) 
    19 
    20 
    21class AccessTokenEndpoint(BaseEndpoint): 
    22 
    23    """An endpoint responsible for providing OAuth 1 access tokens. 
    24 
    25    Typical use is to instantiate with a request validator and invoke the 
    26    ``create_access_token_response`` from a view function. The tuple returned 
    27    has all information necessary (body, status, headers) to quickly form 
    28    and return a proper response. See :doc:`/oauth1/validator` for details on which 
    29    validator methods to implement for this endpoint. 
    30    """ 
    31 
    32    def create_access_token(self, request, credentials): 
    33        """Create and save a new access token. 
    34 
    35        Similar to OAuth 2, indication of granted scopes will be included as a 
    36        space separated list in ``oauth_authorized_realms``. 
    37 
    38        :param request: OAuthlib request. 
    39        :type request: oauthlib.common.Request 
    40        :returns: The token as an urlencoded string. 
    41        """ 
    42        request.realms = self.request_validator.get_realms( 
    43            request.resource_owner_key, request) 
    44        token = { 
    45            'oauth_token': self.token_generator(), 
    46            'oauth_token_secret': self.token_generator(), 
    47            # Backport the authorized scopes indication used in OAuth2 
    48            'oauth_authorized_realms': ' '.join(request.realms) 
    49        } 
    50        token.update(credentials) 
    51        self.request_validator.save_access_token(token, request) 
    52        return urlencode(token.items()) 
    53 
    54    def create_access_token_response(self, uri, http_method='GET', body=None, 
    55                                     headers=None, credentials=None): 
    56        """Create an access token response, with a new request token if valid. 
    57 
    58        :param uri: The full URI of the token request. 
    59        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. 
    60        :param body: The request body as a string. 
    61        :param headers: The request headers as a dict. 
    62        :param credentials: A list of extra credentials to include in the token. 
    63        :returns: A tuple of 3 elements. 
    64                  1. A dict of headers to set on the response. 
    65                  2. The response body as a string. 
    66                  3. The response status code as an integer. 
    67 
    68        An example of a valid request:: 
    69 
    70            >>> from your_validator import your_validator 
    71            >>> from oauthlib.oauth1 import AccessTokenEndpoint 
    72            >>> endpoint = AccessTokenEndpoint(your_validator) 
    73            >>> h, b, s = endpoint.create_access_token_response( 
    74            ...     'https://your.provider/access_token?foo=bar', 
    75            ...     headers={ 
    76            ...         'Authorization': 'OAuth oauth_token=234lsdkf....' 
    77            ...     }, 
    78            ...     credentials={ 
    79            ...         'my_specific': 'argument', 
    80            ...     }) 
    81            >>> h 
    82            {'Content-Type': 'application/x-www-form-urlencoded'} 
    83            >>> b 
    84            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument' 
    85            >>> s 
    86            200 
    87 
    88        An response to invalid request would have a different body and status:: 
    89 
    90            >>> b 
    91            'error=invalid_request&description=missing+resource+owner+key' 
    92            >>> s 
    93            400 
    94 
    95        The same goes for an an unauthorized request: 
    96 
    97            >>> b 
    98            '' 
    99            >>> s 
    100            401 
    101        """ 
    102        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'} 
    103        try: 
    104            request = self._create_request(uri, http_method, body, headers) 
    105            valid, processed_request = self.validate_access_token_request( 
    106                request) 
    107            if valid: 
    108                token = self.create_access_token(request, credentials or {}) 
    109                self.request_validator.invalidate_request_token( 
    110                    request.client_key, 
    111                    request.resource_owner_key, 
    112                    request) 
    113                return resp_headers, token, 200 
    114            else: 
    115                return {}, None, 401 
    116        except errors.OAuth1Error as e: 
    117            return resp_headers, e.urlencoded, e.status_code 
    118 
    119    def validate_access_token_request(self, request): 
    120        """Validate an access token request. 
    121 
    122        :param request: OAuthlib request. 
    123        :type request: oauthlib.common.Request 
    124        :raises: OAuth1Error if the request is invalid. 
    125        :returns: A tuple of 2 elements. 
    126                  1. The validation result (True or False). 
    127                  2. The request object. 
    128        """ 
    129        self._check_transport_security(request) 
    130        self._check_mandatory_parameters(request) 
    131 
    132        if not request.resource_owner_key: 
    133            raise errors.InvalidRequestError( 
    134                description='Missing resource owner.') 
    135 
    136        if not self.request_validator.check_request_token( 
    137                request.resource_owner_key): 
    138            raise errors.InvalidRequestError( 
    139                description='Invalid resource owner key format.') 
    140 
    141        if not request.verifier: 
    142            raise errors.InvalidRequestError( 
    143                description='Missing verifier.') 
    144 
    145        if not self.request_validator.check_verifier(request.verifier): 
    146            raise errors.InvalidRequestError( 
    147                description='Invalid verifier format.') 
    148 
    149        if not self.request_validator.validate_timestamp_and_nonce( 
    150                request.client_key, request.timestamp, request.nonce, request, 
    151                request_token=request.resource_owner_key): 
    152            return False, request 
    153 
    154        # The server SHOULD return a 401 (Unauthorized) status code when 
    155        # receiving a request with invalid client credentials. 
    156        # Note: This is postponed in order to avoid timing attacks, instead 
    157        # a dummy client is assigned and used to maintain near constant 
    158        # time request verification. 
    159        # 
    160        # Note that early exit would enable client enumeration 
    161        valid_client = self.request_validator.validate_client_key( 
    162            request.client_key, request) 
    163        if not valid_client: 
    164            request.client_key = self.request_validator.dummy_client 
    165 
    166        # The server SHOULD return a 401 (Unauthorized) status code when 
    167        # receiving a request with invalid or expired token. 
    168        # Note: This is postponed in order to avoid timing attacks, instead 
    169        # a dummy token is assigned and used to maintain near constant 
    170        # time request verification. 
    171        # 
    172        # Note that early exit would enable resource owner enumeration 
    173        valid_resource_owner = self.request_validator.validate_request_token( 
    174            request.client_key, request.resource_owner_key, request) 
    175        if not valid_resource_owner: 
    176            request.resource_owner_key = self.request_validator.dummy_request_token 
    177 
    178        # The server MUST verify (Section 3.2) the validity of the request, 
    179        # ensure that the resource owner has authorized the provisioning of 
    180        # token credentials to the client, and ensure that the temporary 
    181        # credentials have not expired or been used before.  The server MUST 
    182        # also verify the verification code received from the client. 
    183        # .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2 
    184        # 
    185        # Note that early exit would enable resource owner authorization 
    186        # verifier enumertion. 
    187        valid_verifier = self.request_validator.validate_verifier( 
    188            request.client_key, 
    189            request.resource_owner_key, 
    190            request.verifier, 
    191            request) 
    192 
    193        valid_signature = self._check_signature(request, is_token_request=True) 
    194 
    195        # log the results to the validator_log 
    196        # this lets us handle internal reporting and analysis 
    197        request.validator_log['client'] = valid_client 
    198        request.validator_log['resource_owner'] = valid_resource_owner 
    199        request.validator_log['verifier'] = valid_verifier 
    200        request.validator_log['signature'] = valid_signature 
    201 
    202        # We delay checking validity until the very end, using dummy values for 
    203        # calculations and fetching secrets/keys to ensure the flow of every 
    204        # request remains almost identical regardless of whether valid values 
    205        # have been supplied. This ensures near constant time execution and 
    206        # prevents malicious users from guessing sensitive information 
    207        v = all((valid_client, valid_resource_owner, valid_verifier, 
    208                 valid_signature)) 
    209        if not v: 
    210            log.info("[Failure] request verification failed.") 
    211            log.info("Valid client:, %s", valid_client) 
    212            log.info("Valid token:, %s", valid_resource_owner) 
    213            log.info("Valid verifier:, %s", valid_verifier) 
    214            log.info("Valid signature:, %s", valid_signature) 
    215        return v, request