1""" 
    2oauthlib.oauth1.rfc5849 
    3~~~~~~~~~~~~~~ 
    4 
    5This module is an implementation of various logic needed 
    6for signing and checking OAuth 1.0 RFC 5849 requests. 
    7 
    8It supports all three standard signature methods defined in RFC 5849: 
    9 
    10- HMAC-SHA1 
    11- RSA-SHA1 
    12- PLAINTEXT 
    13 
    14It also supports signature methods that are not defined in RFC 5849. These are 
    15based on the standard ones but replace SHA-1 with the more secure SHA-256: 
    16 
    17- HMAC-SHA256 
    18- RSA-SHA256 
    19 
    20""" 
    21import base64 
    22import hashlib 
    23import logging 
    24import urllib.parse as urlparse 
    25 
    26from oauthlib.common import ( 
    27    Request, generate_nonce, generate_timestamp, to_unicode, urlencode, 
    28) 
    29 
    30from . import parameters, signature 
    31 
    32log = logging.getLogger(__name__) 
    33 
    34# Available signature methods 
    35# 
    36# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility 
    37# with previous versions of this library, when it the only HMAC-based and 
    38# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it 
    39# supports other hashing algorithms besides SHA1, explicitly identifying which 
    40# hashing algorithm is being used is recommended. 
    41# 
    42# Note: if additional values are defined here, don't forget to update the 
    43# imports in "../__init__.py" so they are available outside this module. 
    44 
    45SIGNATURE_HMAC_SHA1 = "HMAC-SHA1" 
    46SIGNATURE_HMAC_SHA256 = "HMAC-SHA256" 
    47SIGNATURE_HMAC_SHA512 = "HMAC-SHA512" 
    48SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1  # deprecated variable for HMAC-SHA1 
    49 
    50SIGNATURE_RSA_SHA1 = "RSA-SHA1" 
    51SIGNATURE_RSA_SHA256 = "RSA-SHA256" 
    52SIGNATURE_RSA_SHA512 = "RSA-SHA512" 
    53SIGNATURE_RSA = SIGNATURE_RSA_SHA1  # deprecated variable for RSA-SHA1 
    54 
    55SIGNATURE_PLAINTEXT = "PLAINTEXT" 
    56 
    57SIGNATURE_METHODS = ( 
    58    SIGNATURE_HMAC_SHA1, 
    59    SIGNATURE_HMAC_SHA256, 
    60    SIGNATURE_HMAC_SHA512, 
    61    SIGNATURE_RSA_SHA1, 
    62    SIGNATURE_RSA_SHA256, 
    63    SIGNATURE_RSA_SHA512, 
    64    SIGNATURE_PLAINTEXT 
    65) 
    66 
    67SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER' 
    68SIGNATURE_TYPE_QUERY = 'QUERY' 
    69SIGNATURE_TYPE_BODY = 'BODY' 
    70 
    71CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' 
    72 
    73 
    74class Client: 
    75 
    76    """A client used to sign OAuth 1.0 RFC 5849 requests.""" 
    77    SIGNATURE_METHODS = { 
    78        SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client, 
    79        SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client, 
    80        SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client, 
    81        SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client, 
    82        SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client, 
    83        SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client, 
    84        SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client 
    85    } 
    86 
    87    @classmethod 
    88    def register_signature_method(cls, method_name, method_callback): 
    89        cls.SIGNATURE_METHODS[method_name] = method_callback 
    90 
    91    def __init__(self, client_key, 
    92                 client_secret=None, 
    93                 resource_owner_key=None, 
    94                 resource_owner_secret=None, 
    95                 callback_uri=None, 
    96                 signature_method=SIGNATURE_HMAC_SHA1, 
    97                 signature_type=SIGNATURE_TYPE_AUTH_HEADER, 
    98                 rsa_key=None, verifier=None, realm=None, 
    99                 encoding='utf-8', decoding=None, 
    100                 nonce=None, timestamp=None): 
    101        """Create an OAuth 1 client. 
    102 
    103        :param client_key: Client key (consumer key), mandatory. 
    104        :param resource_owner_key: Resource owner key (oauth token). 
    105        :param resource_owner_secret: Resource owner secret (oauth token secret). 
    106        :param callback_uri: Callback used when obtaining request token. 
    107        :param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT. 
    108        :param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default), 
    109                               SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY 
    110                               depending on where you want to embed the oauth 
    111                               credentials. 
    112        :param rsa_key: RSA key used with SIGNATURE_RSA. 
    113        :param verifier: Verifier used when obtaining an access token. 
    114        :param realm: Realm (scope) to which access is being requested. 
    115        :param encoding: If you provide non-unicode input you may use this 
    116                         to have oauthlib automatically convert. 
    117        :param decoding: If you wish that the returned uri, headers and body 
    118                         from sign be encoded back from unicode, then set 
    119                         decoding to your preferred encoding, i.e. utf-8. 
    120        :param nonce: Use this nonce instead of generating one. (Mainly for testing) 
    121        :param timestamp: Use this timestamp instead of using current. (Mainly for testing) 
    122        """ 
    123        # Convert to unicode using encoding if given, else assume unicode 
    124        def encode(x): 
    125            return to_unicode(x, encoding) if encoding else x 
    126 
    127        self.client_key = encode(client_key) 
    128        self.client_secret = encode(client_secret) 
    129        self.resource_owner_key = encode(resource_owner_key) 
    130        self.resource_owner_secret = encode(resource_owner_secret) 
    131        self.signature_method = encode(signature_method) 
    132        self.signature_type = encode(signature_type) 
    133        self.callback_uri = encode(callback_uri) 
    134        self.rsa_key = encode(rsa_key) 
    135        self.verifier = encode(verifier) 
    136        self.realm = encode(realm) 
    137        self.encoding = encode(encoding) 
    138        self.decoding = encode(decoding) 
    139        self.nonce = encode(nonce) 
    140        self.timestamp = encode(timestamp) 
    141 
    142    def __repr__(self): 
    143        attrs = vars(self).copy() 
    144        attrs['client_secret'] = '****' if attrs['client_secret'] else None 
    145        attrs['rsa_key'] = '****' if attrs['rsa_key'] else None 
    146        attrs[ 
    147            'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None 
    148        attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items()) 
    149        return '<{} {}>'.format(self.__class__.__name__, attribute_str) 
    150 
    151    def get_oauth_signature(self, request): 
    152        """Get an OAuth signature to be used in signing a request 
    153 
    154        To satisfy `section 3.4.1.2`_ item 2, if the request argument's 
    155        headers dict attribute contains a Host item, its value will 
    156        replace any netloc part of the request argument's uri attribute 
    157        value. 
    158 
    159        .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2 
    160        """ 
    161        if self.signature_method == SIGNATURE_PLAINTEXT: 
    162            # fast-path 
    163            return signature.sign_plaintext(self.client_secret, 
    164                                            self.resource_owner_secret) 
    165 
    166        uri, headers, body = self._render(request) 
    167 
    168        collected_params = signature.collect_parameters( 
    169            uri_query=urlparse.urlparse(uri).query, 
    170            body=body, 
    171            headers=headers) 
    172        log.debug("Collected params: {}".format(collected_params)) 
    173 
    174        normalized_params = signature.normalize_parameters(collected_params) 
    175        normalized_uri = signature.base_string_uri(uri, headers.get('Host', None)) 
    176        log.debug("Normalized params: {}".format(normalized_params)) 
    177        log.debug("Normalized URI: {}".format(normalized_uri)) 
    178 
    179        base_string = signature.signature_base_string(request.http_method, 
    180                                                      normalized_uri, normalized_params) 
    181 
    182        log.debug("Signing: signature base string: {}".format(base_string)) 
    183 
    184        if self.signature_method not in self.SIGNATURE_METHODS: 
    185            raise ValueError('Invalid signature method.') 
    186 
    187        sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self) 
    188 
    189        log.debug("Signature: {}".format(sig)) 
    190        return sig 
    191 
    192    def get_oauth_params(self, request): 
    193        """Get the basic OAuth parameters to be used in generating a signature. 
    194        """ 
    195        nonce = (generate_nonce() 
    196                 if self.nonce is None else self.nonce) 
    197        timestamp = (generate_timestamp() 
    198                     if self.timestamp is None else self.timestamp) 
    199        params = [ 
    200            ('oauth_nonce', nonce), 
    201            ('oauth_timestamp', timestamp), 
    202            ('oauth_version', '1.0'), 
    203            ('oauth_signature_method', self.signature_method), 
    204            ('oauth_consumer_key', self.client_key), 
    205        ] 
    206        if self.resource_owner_key: 
    207            params.append(('oauth_token', self.resource_owner_key)) 
    208        if self.callback_uri: 
    209            params.append(('oauth_callback', self.callback_uri)) 
    210        if self.verifier: 
    211            params.append(('oauth_verifier', self.verifier)) 
    212 
    213        # providing body hash for requests other than x-www-form-urlencoded 
    214        # as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1 
    215        # 4.1.1. When to include the body hash 
    216        #    *  [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies 
    217        #    *  [...] SHOULD include the oauth_body_hash parameter on all other requests. 
    218        # Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2 
    219        # At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension. 
    220        content_type = request.headers.get('Content-Type', None) 
    221        content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0 
    222        if request.body is not None and content_type_eligible: 
    223            params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))  # noqa: S324 
    224 
    225        return params 
    226 
    227    def _render(self, request, formencode=False, realm=None): 
    228        """Render a signed request according to signature type 
    229 
    230        Returns a 3-tuple containing the request URI, headers, and body. 
    231 
    232        If the formencode argument is True and the body contains parameters, it 
    233        is escaped and returned as a valid formencoded string. 
    234        """ 
    235        # TODO what if there are body params on a header-type auth? 
    236        # TODO what if there are query params on a body-type auth? 
    237 
    238        uri, headers, body = request.uri, request.headers, request.body 
    239 
    240        # TODO: right now these prepare_* methods are very narrow in scope--they 
    241        # only affect their little thing. In some cases (for example, with 
    242        # header auth) it might be advantageous to allow these methods to touch 
    243        # other parts of the request, like the headers—so the prepare_headers 
    244        # method could also set the Content-Type header to x-www-form-urlencoded 
    245        # like the spec requires. This would be a fundamental change though, and 
    246        # I'm not sure how I feel about it. 
    247        if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER: 
    248            headers = parameters.prepare_headers( 
    249                request.oauth_params, request.headers, realm=realm) 
    250        elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None: 
    251            body = parameters.prepare_form_encoded_body( 
    252                request.oauth_params, request.decoded_body) 
    253            if formencode: 
    254                body = urlencode(body) 
    255            headers['Content-Type'] = 'application/x-www-form-urlencoded' 
    256        elif self.signature_type == SIGNATURE_TYPE_QUERY: 
    257            uri = parameters.prepare_request_uri_query( 
    258                request.oauth_params, request.uri) 
    259        else: 
    260            raise ValueError('Unknown signature type specified.') 
    261 
    262        return uri, headers, body 
    263 
    264    def sign(self, uri, http_method='GET', body=None, headers=None, realm=None): 
    265        """Sign a request 
    266 
    267        Signs an HTTP request with the specified parts. 
    268 
    269        Returns a 3-tuple of the signed request's URI, headers, and body. 
    270        Note that http_method is not returned as it is unaffected by the OAuth 
    271        signing process. Also worth noting is that duplicate parameters 
    272        will be included in the signature, regardless of where they are 
    273        specified (query, body). 
    274 
    275        The body argument may be a dict, a list of 2-tuples, or a formencoded 
    276        string. The Content-Type header must be 'application/x-www-form-urlencoded' 
    277        if it is present. 
    278 
    279        If the body argument is not one of the above, it will be returned 
    280        verbatim as it is unaffected by the OAuth signing process. Attempting to 
    281        sign a request with non-formencoded data using the OAuth body signature 
    282        type is invalid and will raise an exception. 
    283 
    284        If the body does contain parameters, it will be returned as a properly- 
    285        formatted formencoded string. 
    286 
    287        Body may not be included if the http_method is either GET or HEAD as 
    288        this changes the semantic meaning of the request. 
    289 
    290        All string data MUST be unicode or be encoded with the same encoding 
    291        scheme supplied to the Client constructor, default utf-8. This includes 
    292        strings inside body dicts, for example. 
    293        """ 
    294        # normalize request data 
    295        request = Request(uri, http_method, body, headers, 
    296                          encoding=self.encoding) 
    297 
    298        # sanity check 
    299        content_type = request.headers.get('Content-Type', None) 
    300        multipart = content_type and content_type.startswith('multipart/') 
    301        should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED 
    302        has_params = request.decoded_body is not None 
    303        # 3.4.1.3.1.  Parameter Sources 
    304        # [Parameters are collected from the HTTP request entity-body, but only 
    305        # if [...]: 
    306        #    *  The entity-body is single-part. 
    307        if multipart and has_params: 
    308            raise ValueError( 
    309                "Headers indicate a multipart body but body contains parameters.") 
    310        #    *  The entity-body follows the encoding requirements of the 
    311        #       "application/x-www-form-urlencoded" content-type as defined by 
    312        #       [W3C.REC-html40-19980424]. 
    313        elif should_have_params and not has_params: 
    314            raise ValueError( 
    315                "Headers indicate a formencoded body but body was not decodable.") 
    316        #    *  The HTTP request entity-header includes the "Content-Type" 
    317        #       header field set to "application/x-www-form-urlencoded". 
    318        elif not should_have_params and has_params: 
    319            raise ValueError( 
    320                "Body contains parameters but Content-Type header was {} " 
    321                "instead of {}".format(content_type or "not set", 
    322                                        CONTENT_TYPE_FORM_URLENCODED)) 
    323 
    324        # 3.5.2.  Form-Encoded Body 
    325        # Protocol parameters can be transmitted in the HTTP request entity- 
    326        # body, but only if the following REQUIRED conditions are met: 
    327        # o  The entity-body is single-part. 
    328        # o  The entity-body follows the encoding requirements of the 
    329        #    "application/x-www-form-urlencoded" content-type as defined by 
    330        #    [W3C.REC-html40-19980424]. 
    331        # o  The HTTP request entity-header includes the "Content-Type" header 
    332        #    field set to "application/x-www-form-urlencoded". 
    333        elif self.signature_type == SIGNATURE_TYPE_BODY and not ( 
    334                should_have_params and has_params and not multipart): 
    335            raise ValueError( 
    336                'Body signatures may only be used with form-urlencoded content') 
    337 
    338        # We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 
    339        # with the clause that parameters from body should only be included 
    340        # in non GET or HEAD requests. Extracting the request body parameters 
    341        # and including them in the signature base string would give semantic 
    342        # meaning to the body, which it should not have according to the 
    343        # HTTP 1.1 spec. 
    344        elif http_method.upper() in ('GET', 'HEAD') and has_params: 
    345            raise ValueError('GET/HEAD requests should not include body.') 
    346 
    347        # generate the basic OAuth parameters 
    348        request.oauth_params = self.get_oauth_params(request) 
    349 
    350        # generate the signature 
    351        request.oauth_params.append( 
    352            ('oauth_signature', self.get_oauth_signature(request))) 
    353 
    354        # render the signed request and return it 
    355        uri, headers, body = self._render(request, formencode=True, 
    356                                          realm=(realm or self.realm)) 
    357 
    358        if self.decoding: 
    359            log.debug('Encoding URI, headers and body to %s.', self.decoding) 
    360            uri = uri.encode(self.decoding) 
    361            body = body.encode(self.decoding) if body else body 
    362            new_headers = {} 
    363            for k, v in headers.items(): 
    364                new_headers[k.encode(self.decoding)] = v.encode(self.decoding) 
    365            headers = new_headers 
    366        return uri, headers, body