From 568720334aa630ea504b2ce3b8c324f0a557d6e6 Mon Sep 17 00:00:00 2001 From: k clair Date: Tue, 9 Oct 2012 13:14:36 -0700 Subject: add source files from upstream --- .../requests/packages/oauthlib/oauth1/__init__.py | 13 + .../packages/oauthlib/oauth1/rfc5849/__init__.py | 889 +++++++++++++++++++++ .../packages/oauthlib/oauth1/rfc5849/parameters.py | 134 ++++ .../packages/oauthlib/oauth1/rfc5849/signature.py | 551 +++++++++++++ .../packages/oauthlib/oauth1/rfc5849/utils.py | 99 +++ 5 files changed, 1686 insertions(+) create mode 100644 requests-0.14.0/requests/packages/oauthlib/oauth1/__init__.py create mode 100644 requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/__init__.py create mode 100644 requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/parameters.py create mode 100644 requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/signature.py create mode 100644 requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/utils.py (limited to 'requests-0.14.0/requests/packages/oauthlib/oauth1') diff --git a/requests-0.14.0/requests/packages/oauthlib/oauth1/__init__.py b/requests-0.14.0/requests/packages/oauthlib/oauth1/__init__.py new file mode 100644 index 0000000..ef692b5 --- /dev/null +++ b/requests-0.14.0/requests/packages/oauthlib/oauth1/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +""" +oauthlib.oauth1 +~~~~~~~~~~~~~~ + +This module is a wrapper for the most recent implementation of OAuth 1.0 Client +and Server classes. +""" + +from .rfc5849 import Client, Server + diff --git a/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/__init__.py b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/__init__.py new file mode 100644 index 0000000..da3988d --- /dev/null +++ b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/__init__.py @@ -0,0 +1,889 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +""" +oauthlib.oauth1.rfc5849 +~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for signing and checking OAuth 1.0 RFC 5849 requests. +""" + +import logging +import time +import urlparse + +from oauthlib.common import Request, urlencode, generate_nonce +from oauthlib.common import generate_timestamp +from . import parameters, signature, utils + +logger = logging.getLogger(__name__) + +SIGNATURE_HMAC = u"HMAC-SHA1" +SIGNATURE_RSA = u"RSA-SHA1" +SIGNATURE_PLAINTEXT = u"PLAINTEXT" +SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT) + +SIGNATURE_TYPE_AUTH_HEADER = u'AUTH_HEADER' +SIGNATURE_TYPE_QUERY = u'QUERY' +SIGNATURE_TYPE_BODY = u'BODY' + +CONTENT_TYPE_FORM_URLENCODED = u'application/x-www-form-urlencoded' + + +class Client(object): + """A client used to sign OAuth 1.0 RFC 5849 requests""" + def __init__(self, client_key, + client_secret=None, + resource_owner_key=None, + resource_owner_secret=None, + callback_uri=None, + signature_method=SIGNATURE_HMAC, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + rsa_key=None, verifier=None): + self.client_key = client_key + self.client_secret = client_secret + self.resource_owner_key = resource_owner_key + self.resource_owner_secret = resource_owner_secret + self.signature_method = signature_method + self.signature_type = signature_type + self.callback_uri = callback_uri + self.rsa_key = rsa_key + self.verifier = verifier + + if self.signature_method == SIGNATURE_RSA and self.rsa_key is None: + raise ValueError('rsa_key is required when using RSA signature method.') + + def get_oauth_signature(self, request): + """Get an OAuth signature to be used in signing a request + """ + if self.signature_method == SIGNATURE_PLAINTEXT: + # fast-path + return signature.sign_plaintext(self.client_secret, + self.resource_owner_secret) + + uri, headers, body = self._render(request) + + collected_params = signature.collect_parameters( + uri_query=urlparse.urlparse(uri).query, + body=body, + headers=headers) + logger.debug("Collected params: {0}".format(collected_params)) + + normalized_params = signature.normalize_parameters(collected_params) + normalized_uri = signature.normalize_base_string_uri(request.uri) + logger.debug("Normalized params: {0}".format(normalized_params)) + logger.debug("Normalized URI: {0}".format(normalized_uri)) + + base_string = signature.construct_base_string(request.http_method, + normalized_uri, normalized_params) + + logger.debug("Base signing string: {0}".format(base_string)) + + if self.signature_method == SIGNATURE_HMAC: + sig = signature.sign_hmac_sha1(base_string, self.client_secret, + self.resource_owner_secret) + elif self.signature_method == SIGNATURE_RSA: + sig = signature.sign_rsa_sha1(base_string, self.rsa_key) + else: + sig = signature.sign_plaintext(self.client_secret, + self.resource_owner_secret) + + logger.debug("Signature: {0}".format(sig)) + return sig + + def get_oauth_params(self): + """Get the basic OAuth parameters to be used in generating a signature. + """ + params = [ + (u'oauth_nonce', generate_nonce()), + (u'oauth_timestamp', generate_timestamp()), + (u'oauth_version', u'1.0'), + (u'oauth_signature_method', self.signature_method), + (u'oauth_consumer_key', self.client_key), + ] + if self.resource_owner_key: + params.append((u'oauth_token', self.resource_owner_key)) + if self.callback_uri: + params.append((u'oauth_callback', self.callback_uri)) + if self.verifier: + params.append((u'oauth_verifier', self.verifier)) + + return params + + def _render(self, request, formencode=False): + """Render a signed request according to signature type + + Returns a 3-tuple containing the request URI, headers, and body. + + If the formencode argument is True and the body contains parameters, it + is escaped and returned as a valid formencoded string. + """ + # TODO what if there are body params on a header-type auth? + # TODO what if there are query params on a body-type auth? + + uri, headers, body = request.uri, request.headers, request.body + + # TODO: right now these prepare_* methods are very narrow in scope--they + # only affect their little thing. In some cases (for example, with + # header auth) it might be advantageous to allow these methods to touch + # other parts of the request, like the headers—so the prepare_headers + # method could also set the Content-Type header to x-www-form-urlencoded + # like the spec requires. This would be a fundamental change though, and + # I'm not sure how I feel about it. + if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER: + headers = parameters.prepare_headers(request.oauth_params, request.headers) + elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None: + body = parameters.prepare_form_encoded_body(request.oauth_params, request.decoded_body) + if formencode: + body = urlencode(body) + headers['Content-Type'] = u'application/x-www-form-urlencoded' + elif self.signature_type == SIGNATURE_TYPE_QUERY: + uri = parameters.prepare_request_uri_query(request.oauth_params, request.uri) + else: + raise ValueError('Unknown signature type specified.') + + return uri, headers, body + + def sign(self, uri, http_method=u'GET', body=None, headers=None): + """Sign a request + + Signs an HTTP request with the specified parts. + + Returns a 3-tuple of the signed request's URI, headers, and body. + Note that http_method is not returned as it is unaffected by the OAuth + signing process. + + The body argument may be a dict, a list of 2-tuples, or a formencoded + string. The Content-Type header must be 'application/x-www-form-urlencoded' + if it is present. + + If the body argument is not one of the above, it will be returned + verbatim as it is unaffected by the OAuth signing process. Attempting to + sign a request with non-formencoded data using the OAuth body signature + type is invalid and will raise an exception. + + If the body does contain parameters, it will be returned as a properly- + formatted formencoded string. + + All string data MUST be unicode. This includes strings inside body + dicts, for example. + """ + # normalize request data + request = Request(uri, http_method, body, headers) + + # sanity check + content_type = request.headers.get('Content-Type', None) + multipart = content_type and content_type.startswith('multipart/') + should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED + has_params = request.decoded_body is not None + # 3.4.1.3.1. Parameter Sources + # [Parameters are collected from the HTTP request entity-body, but only + # if [...]: + # * The entity-body is single-part. + if multipart and has_params: + raise ValueError("Headers indicate a multipart body but body contains parameters.") + # * The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # [W3C.REC-html40-19980424]. + elif should_have_params and not has_params: + raise ValueError("Headers indicate a formencoded body but body was not decodable.") + # * The HTTP request entity-header includes the "Content-Type" + # header field set to "application/x-www-form-urlencoded". + elif not should_have_params and has_params: + raise ValueError("Body contains parameters but Content-Type header was not set.") + + # 3.5.2. Form-Encoded Body + # Protocol parameters can be transmitted in the HTTP request entity- + # body, but only if the following REQUIRED conditions are met: + # o The entity-body is single-part. + # o The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # [W3C.REC-html40-19980424]. + # o The HTTP request entity-header includes the "Content-Type" header + # field set to "application/x-www-form-urlencoded". + elif self.signature_type == SIGNATURE_TYPE_BODY and not ( + should_have_params and has_params and not multipart): + raise ValueError('Body signatures may only be used with form-urlencoded content') + + # generate the basic OAuth parameters + request.oauth_params = self.get_oauth_params() + + # generate the signature + request.oauth_params.append((u'oauth_signature', self.get_oauth_signature(request))) + + # render the signed request and return it + return self._render(request, formencode=True) + + +class Server(object): + """A server base class used to verify OAuth 1.0 RFC 5849 requests + + OAuth providers should inherit from Server and implement the methods + and properties outlined below. Further details are provided in the + documentation for each method and property. + + Methods used to check the format of input parameters. Common tests include + length, character set, membership, range or pattern. These tests are + referred to as `whitelisting or blacklisting`_. Whitelisting is better + but blacklisting can be usefull to spot malicious activity. + The following have methods a default implementation: + + - check_client_key + - check_request_token + - check_access_token + - check_nonce + - check_verifier + - check_realm + + The methods above default to whitelist input parameters, checking that they + are alphanumerical and between a minimum and maximum length. Rather than + overloading the methods a few properties can be used to configure these + methods. + + @ safe_characters -> (character set) + @ client_key_length -> (min, max) + @ request_token_length -> (min, max) + @ access_token_length -> (min, max) + @ nonce_length -> (min, max) + @ verifier_length -> (min, max) + @ realms -> [list, of, realms] + + Methods used to validate input parameters. These checks usually hit either + persistent or temporary storage such as databases or the filesystem. See + each methods documentation for detailed usage. + The following methods must be implemented: + + - validate_client + - validate_request_token + - validate_access_token + - validate_nonce_and_timestamp + - validate_redirect_uri + - validate_requested_realm + - validate_realm + - validate_verifier + + Method used to retrieve sensitive information from storage. + The following methods must be implemented: + + - get_client_secret + - get_request_token_secret + - get_access_token_secret + - get_rsa_key + + To prevent timing attacks it is necessary to not exit early even if the + client key or resource owner key is invalid. Instead dummy values should + be used during the remaining verification process. It is very important + that the dummy client and token are valid input parameters to the methods + get_client_secret, get_rsa_key and get_(access/request)_token_secret and + that the running time of those methods when given a dummy value remain + equivalent to the running time when given a valid client/resource owner. + The following properties must be implemented: + + @ dummy_client + @ dummy_request_token + @ dummy_access_token + + .. _`whitelisting or blacklisting`: http://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html + """ + + def __init__(self): + pass + + @property + def allowed_signature_methods(self): + return SIGNATURE_METHODS + + @property + def safe_characters(self): + return set(utils.UNICODE_ASCII_CHARACTER_SET) + + @property + def client_key_length(self): + return 20, 30 + + @property + def request_token_length(self): + return 20, 30 + + @property + def access_token_length(self): + return 20, 30 + + @property + def timestamp_lifetime(self): + return 600 + + @property + def nonce_length(self): + return 20, 30 + + @property + def verifier_length(self): + return 20, 30 + + @property + def realms(self): + return [] + + @property + def enforce_ssl(self): + return True + + def check_client_key(self, client_key): + """Check that the client key only contains safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.client_key_length + return (set(client_key) <= self.safe_characters and + lower <= len(client_key) <= upper) + + def check_request_token(self, request_token): + """Checks that the request token contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.request_token_length + return (set(request_token) <= self.safe_characters and + lower <= len(request_token) <= upper) + + def check_access_token(self, request_token): + """Checks that the token contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.access_token_length + return (set(request_token) <= self.safe_characters and + lower <= len(request_token) <= upper) + + def check_nonce(self, nonce): + """Checks that the nonce only contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.nonce_length + return (set(nonce) <= self.safe_characters and + lower <= len(nonce) <= upper) + + def check_verifier(self, verifier): + """Checks that the verifier contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.verifier_length + return (set(verifier) <= self.safe_characters and + lower <= len(verifier) <= upper) + + def check_realm(self, realm): + """Check that the realm is one of a set allowed realms. + """ + return realm in self.realms + + def get_client_secret(self, client_key): + """Retrieves the client secret associated with the client key. + + This method must allow the use of a dummy client_key value. + Fetching the secret using the dummy key must take the same amount of + time as fetching a secret for a valid client. + + Note that the returned key must be in plaintext. + """ + raise NotImplementedError("Subclasses must implement this function.") + + @property + def dummy_client(self): + """Dummy client used when an invalid client key is supplied. + + The dummy client should be associated with either a client secret, + a rsa key or both depending on which signature methods are supported. + Providers should make sure that + + get_client_secret(dummy_client) + get_rsa_key(dummy_client) + + return a valid secret or key for the dummy client. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def get_request_token_secret(self, client_key, request_token): + """Retrieves the shared secret associated with the request token. + + This method must allow the use of a dummy values and the running time + must be roughly equivalent to that of the running time of valid values. + + Note that the returned key must be in plaintext. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def get_access_token_secret(self, client_key, access_token): + """Retrieves the shared secret associated with the access token. + + This method must allow the use of a dummy values and the running time + must be roughly equivalent to that of the running time of valid values. + + Note that the returned key must be in plaintext. + """ + raise NotImplementedError("Subclasses must implement this function.") + + @property + def dummy_request_token(self): + """Dummy request token used when an invalid token was supplied. + + The dummy request token should be associated with a request token + secret such that get_request_token_secret(.., dummy_request_token) + returns a valid secret. + """ + raise NotImplementedError("Subclasses must implement this function.") + + @property + def dummy_access_token(self): + """Dummy access token used when an invalid token was supplied. + + The dummy access token should be associated with an access token + secret such that get_access_token_secret(.., dummy_access_token) + returns a valid secret. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def get_rsa_key(self, client_key): + """Retrieves a previously stored client provided RSA key. + + This method must allow the use of a dummy client_key value. Fetching + the rsa key using the dummy key must take the same aount of time + as fetching a key for a valid client. + + Note that the key must be returned in plaintext. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def get_signature_type_and_params(self, request): + """Extracts parameters from query, headers and body. Signature type + is set to the source in which parameters were found. + """ + header_params = signature.collect_parameters(headers=request.headers, + exclude_oauth_signature=False) + body_params = signature.collect_parameters(body=request.body, + exclude_oauth_signature=False) + query_params = signature.collect_parameters(uri_query=request.uri_query, + exclude_oauth_signature=False) + + params = [] + params.extend(header_params) + params.extend(body_params) + params.extend(query_params) + signature_types_with_oauth_params = filter(lambda s: s[2], ( + (SIGNATURE_TYPE_AUTH_HEADER, params, + utils.filter_oauth_params(header_params)), + (SIGNATURE_TYPE_BODY, params, + utils.filter_oauth_params(body_params)), + (SIGNATURE_TYPE_QUERY, params, + utils.filter_oauth_params(query_params)) + )) + + if len(signature_types_with_oauth_params) > 1: + raise ValueError('oauth_ params must come from only 1 signature type but were found in %s' % ', '.join( + [s[0] for s in signature_types_with_oauth_params])) + try: + signature_type, params, oauth_params = signature_types_with_oauth_params[0] + except IndexError: + raise ValueError('oauth_ params are missing. Could not determine signature type.') + + return signature_type, params, oauth_params + + def validate_client_key(self, client_key): + """Validates that supplied client key is a registered and valid client. + + Note that if the dummy client is supplied it should validate in same + or nearly the same amount of time as a valid one. + + Bad: + + if client_key == self.dummy_client: + return False + else: + return storage.has_client(client_key) + + Good: + + return storage.has_client(client_key) and client_key != self.dummy_client + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_request_token(self, client_key, request_token): + """Validates that supplied request token is registered and valid. + + Note that if the dummy request_token is supplied it should validate in + the same nearly the same amount of time as a valid one. + + Bad: + + if request_token == self.dummy_request_token: + return False + else: + return storage.has_request_token(request_token) + + Good: + + return (storage.has_request_token(request_token) and + request_token != self.dummy_request_token) + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_access_token(self, client_key, access_token): + """Validates that supplied access token is registered and valid. + + Note that if the dummy access token is supplied it should validate in + the same or nearly the same amount of time as a valid one. + + Bad: + + if access_token == self.dummy_access_token: + return False + else: + return storage.has_access_token(access_token) + + Good: + + return (storage.has_access_token(access_token) and + access_token != self.dummy_access_token) + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, + request_token=None, access_token=None): + """Validates that the nonce has not been used before. + + Per `Section 3.3`_ of the spec. + + "A nonce is a random string, uniquely generated by the client to allow + the server to verify that a request has never been made before and + helps prevent replay attacks when requests are made over a non-secure + channel. The nonce value MUST be unique across all requests with the + same timestamp, client credentials, and token combinations." + + .. _`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_redirect_uri(self, client_key, redirect_uri): + """Validates the client supplied redirection URI. + + It is highly recommended that OAuth providers require their clients + to register all redirection URIs prior to using them in requests and + register them as absolute URIs. See `CWE-601`_ for more information + about open redirection attacks. + + By requiring registration of all redirection URIs it should be + straightforward for the provider to verify whether the supplied + redirect_uri is valid or not. + + .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601 + """ + raise NotImplementedError("Subclasses must implement this function.") + + + def validate_requested_realm(self, client_key, realm): + """Validates that the client may request access to the realm. + + This method is invoked when obtaining a request token and should + tie a realm to the request token and after user authorization + this realm restriction should transfer to the access token. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_realm(self, client_key, access_token, uri=None, + required_realm=None): + """Validates access to the request realm. + + How providers choose to use the realm parameter is outside the OAuth + specification but it is commonly used to restrict access to a subset + of protected resources such as "photos". + + required_realm is a convenience parameter which can be used to provide + a per view method pre-defined list of allowed realms. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def validate_verifier(self, client_key, request_token, verifier): + """Validates a verification code. + + OAuth providers issue a verification code to clients after the + resource owner authorizes access. This code is used by the client to + obtain token credentials and the provider must verify that the + verifier is valid and associated with the client as well as the + resource owner. + """ + raise NotImplementedError("Subclasses must implement this function.") + + def verify_request(self, uri, http_method=u'GET', body=None, + headers=None, require_resource_owner=True, require_verifier=False, + require_realm=False, required_realm=None): + """Verifies a request ensuring that the following is true: + + Per `section 3.2`_ of the spec. + + - all mandated OAuth parameters are supplied + - parameters are only supplied in one source which may be the URI + query, the Authorization header or the body + - all parameters are checked and validated, see comments and the + methods and properties of this class for further details. + - the supplied signature is verified against a recalculated one + + A ValueError will be raised if any parameter is missing, + supplied twice or invalid. A HTTP 400 Response should be returned + upon catching an exception. + + A HTTP 401 Response should be returned if verify_request returns False. + + `Timing attacks`_ are prevented through the use of dummy credentials to + create near constant time verification even if an invalid credential + is used. Early exit on invalid credentials would enable attackers + to perform `enumeration attacks`_. Near constant time string comparison + is used to prevent secret key guessing. Note that timing attacks can + only be prevented through near constant time execution, not by adding + a random delay which would only require more samples to be gathered. + + .. _`section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2 + .. _`Timing attacks`: http://rdist.root.org/2010/07/19/exploiting-remote-timing-attacks/ + .. _`enumeration attacks`: http://www.sans.edu/research/security-laboratory/article/attacks-browsing + """ + # Only include body data from x-www-form-urlencoded requests + headers = headers or {} + if (u"Content-Type" in headers and + headers[u"Content-Type"] == CONTENT_TYPE_FORM_URLENCODED): + request = Request(uri, http_method, body, headers) + else: + request = Request(uri, http_method, u'', headers) + + if self.enforce_ssl and not request.uri.lower().startswith("https://"): + raise ValueError("Insecure transport, only HTTPS is allowed.") + + signature_type, params, oauth_params = self.get_signature_type_and_params(request) + + # The server SHOULD return a 400 (Bad Request) status code when + # receiving a request with duplicated protocol parameters. + if len(dict(oauth_params)) != len(oauth_params): + raise ValueError("Duplicate OAuth entries.") + + oauth_params = dict(oauth_params) + request_signature = oauth_params.get(u'oauth_signature') + client_key = oauth_params.get(u'oauth_consumer_key') + resource_owner_key = oauth_params.get(u'oauth_token') + nonce = oauth_params.get(u'oauth_nonce') + timestamp = oauth_params.get(u'oauth_timestamp') + callback_uri = oauth_params.get(u'oauth_callback') + verifier = oauth_params.get(u'oauth_verifier') + signature_method = oauth_params.get(u'oauth_signature_method') + realm = dict(params).get(u'realm') + + # The server SHOULD return a 400 (Bad Request) status code when + # receiving a request with missing parameters. + if not all((request_signature, client_key, nonce, + timestamp, signature_method)): + raise ValueError("Missing OAuth parameters.") + + # OAuth does not mandate a particular signature method, as each + # implementation can have its own unique requirements. Servers are + # free to implement and document their own custom methods. + # Recommending any particular method is beyond the scope of this + # specification. Implementers should review the Security + # Considerations section (`Section 4`_) before deciding on which + # method to support. + # .. _`Section 4`: http://tools.ietf.org/html/rfc5849#section-4 + if not signature_method in self.allowed_signature_methods: + raise ValueError("Invalid signature method.") + + # Servers receiving an authenticated request MUST validate it by: + # If the "oauth_version" parameter is present, ensuring its value is + # "1.0". + if u'oauth_version' in oauth_params and oauth_params[u'oauth_version'] != u'1.0': + raise ValueError("Invalid OAuth version.") + + # The timestamp value MUST be a positive integer. Unless otherwise + # specified by the server's documentation, the timestamp is expressed + # in the number of seconds since January 1, 1970 00:00:00 GMT. + if len(timestamp) != 10: + raise ValueError("Invalid timestamp size") + try: + ts = int(timestamp) + + except ValueError: + raise ValueError("Timestamp must be an integer") + + else: + # To avoid the need to retain an infinite number of nonce values for + # future checks, servers MAY choose to restrict the time period after + # which a request with an old timestamp is rejected. + if time.time() - ts > self.timestamp_lifetime: + raise ValueError("Request too old, over 10 minutes.") + + # Provider specific validation of parameters, used to enforce + # restrictions such as character set and length. + if not self.check_client_key(client_key): + raise ValueError("Invalid client key.") + + if not resource_owner_key and require_resource_owner: + raise ValueError("Missing resource owner.") + + if (require_resource_owner and not require_verifier and + not self.check_access_token(resource_owner_key)): + raise ValueError("Invalid resource owner key.") + + if (require_resource_owner and require_verifier and + not self.check_request_token(resource_owner_key)): + raise ValueError("Invalid resource owner key.") + + if not self.check_nonce(nonce): + raise ValueError("Invalid nonce.") + + if realm and not self.check_realm(realm): + raise ValueError("Invalid realm. Allowed are %s" % self.realms) + + if not verifier and require_verifier: + raise ValueError("Missing verifier.") + + if require_verifier and not self.check_verifier(verifier): + raise ValueError("Invalid verifier.") + + # Servers receiving an authenticated request MUST validate it by: + # If using the "HMAC-SHA1" or "RSA-SHA1" signature methods, ensuring + # that the combination of nonce/timestamp/token (if present) + # received from the client has not been used before in a previous + # request (the server MAY reject requests with stale timestamps as + # described in `Section 3.3`_). + # .._`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + # + # We check this before validating client and resource owner for + # increased security and performance, both gained by doing less work. + if require_verifier: + token = {"request_token": resource_owner_key} + else: + token = {"access_token": resource_owner_key} + if not self.validate_timestamp_and_nonce(client_key, timestamp, + nonce, **token): + return False + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid client credentials. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy client is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable client enumeration + valid_client = self.validate_client_key(client_key) + if not valid_client: + client_key = self.dummy_client + + # Ensure a valid redirection uri is used + valid_redirect = self.validate_redirect_uri(client_key, callback_uri) + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid or expired token. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy token is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable resource owner enumeration + if resource_owner_key: + if require_verifier: + valid_resource_owner = self.validate_request_token( + client_key, resource_owner_key) + else: + valid_resource_owner = self.validate_access_token( + client_key, resource_owner_key) + if not valid_resource_owner: + resource_owner_key = self.dummy_resource_owner + else: + valid_resource_owner = True + + # Note that `realm`_ is only used in authorization headers and how + # it should be interepreted is not included in the OAuth spec. + # However they could be seen as a scope or realm to which the + # client has access and as such every client should be checked + # to ensure it is authorized access to that scope or realm. + # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2 + # + # Note that early exit would enable client realm access enumeration. + # + # The require_realm indicates this is the first step in the OAuth + # workflow where a client requests access to a specific realm. + # + # Clients obtaining an access token will not supply a realm and it will + # not be checked. Instead the previously requested realm should be + # transferred from the request token to the access token. + # + # Access to protected resources will always validate the realm but note + # that the realm is now tied to the access token and not provided by + # the client. + if require_realm and not resource_owner_key: + valid_realm = self.validate_requested_realm(client_key, realm) + elif require_verifier: + valid_realm = True + else: + valid_realm = self.validate_realm(client_key, resource_owner_key, + uri=request.uri, required_realm=required_realm) + + # The server MUST verify (Section 3.2) the validity of the request, + # ensure that the resource owner has authorized the provisioning of + # token credentials to the client, and ensure that the temporary + # credentials have not expired or been used before. The server MUST + # also verify the verification code received from the client. + # .. _`Section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2 + # + # Note that early exit would enable resource owner authorization + # verifier enumertion. + if verifier: + valid_verifier = self.validate_verifier(client_key, + resource_owner_key, verifier) + else: + valid_verifier = True + + # Parameters to Client depend on signature method which may vary + # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters + + request.params = filter(lambda x: x[0] != "oauth_signature", params) + request.signature = request_signature + + # ---- RSA Signature verification ---- + if signature_method == SIGNATURE_RSA: + # The server verifies the signature per `[RFC3447] section 8.2.2`_ + # .. _`[RFC3447] section 8.2.2`: http://tools.ietf.org/html/rfc3447#section-8.2.1 + rsa_key = self.get_rsa_key(client_key) + valid_signature = signature.verify_rsa_sha1(request, rsa_key) + + # ---- HMAC or Plaintext Signature verification ---- + else: + # Servers receiving an authenticated request MUST validate it by: + # Recalculating the request signature independently as described in + # `Section 3.4`_ and comparing it to the value received from the + # client via the "oauth_signature" parameter. + # .. _`Section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + client_secret = self.get_client_secret(client_key) + if require_verifier: + resource_owner_secret = self.get_request_token_secret( + client_key, resource_owner_key) + else: + resource_owner_secret = self.get_access_token_secret( + client_key, resource_owner_key) + + if signature_method == SIGNATURE_HMAC: + valid_signature = signature.verify_hmac_sha1(request, + client_secret, resource_owner_secret) + else: + valid_signature = signature.verify_plaintext(request, + client_secret, resource_owner_secret) + + # We delay checking validity until the very end, using dummy values for + # calculations and fetching secrets/keys to ensure the flow of every + # request remains almost identical regardless of whether valid values + # have been supplied. This ensures near constant time execution and + # prevents malicious users from guessing sensitive information + v = all((valid_client, valid_resource_owner, valid_realm, + valid_redirect, valid_verifier, valid_signature)) + logger = logging.getLogger("oauthlib") + if not v: + logger.info("[Failure] OAuthLib request verification failed.") + logger.info("Valid client:\t%s" % valid_client) + logger.info("Valid token:\t%s\t(Required: %s" % (valid_resource_owner, require_resource_owner)) + logger.info("Valid realm:\t%s\t(Required: %s)" % (valid_realm, require_realm)) + logger.info("Valid callback:\t%s" % valid_redirect) + logger.info("Valid verifier:\t%s\t(Required: %s)" % (valid_verifier, require_verifier)) + logger.info("Valid signature:\t%s" % valid_signature) + return v diff --git a/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/parameters.py b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/parameters.py new file mode 100644 index 0000000..dee23a4 --- /dev/null +++ b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/parameters.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +""" +oauthlib.parameters +~~~~~~~~~~~~~~~~~~~ + +This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec. + +.. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5 +""" + +from urlparse import urlparse, urlunparse +from . import utils +from oauthlib.common import extract_params, urlencode + + +# TODO: do we need filter_params now that oauth_params are handled by Request? +# We can easily pass in just oauth protocol params. +@utils.filter_params +def prepare_headers(oauth_params, headers=None, realm=None): + """**Prepare the Authorization header.** + Per `section 3.5.1`_ of the spec. + + Protocol parameters can be transmitted using the HTTP "Authorization" + header field as defined by `RFC2617`_ with the auth-scheme name set to + "OAuth" (case insensitive). + + For example:: + + Authorization: OAuth realm="Example", + oauth_consumer_key="0685bd9184jfhq22", + oauth_token="ad180jjd733klru7", + oauth_signature_method="HMAC-SHA1", + oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + oauth_timestamp="137131200", + oauth_nonce="4572616e48616d6d65724c61686176", + oauth_version="1.0" + + + .. _`section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 + .. _`RFC2617`: http://tools.ietf.org/html/rfc2617 + """ + headers = headers or {} + + # Protocol parameters SHALL be included in the "Authorization" header + # field as follows: + authorization_header_parameters_parts = [] + for oauth_parameter_name, value in oauth_params: + # 1. Parameter names and values are encoded per Parameter Encoding + # (`Section 3.6`_) + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + escaped_name = utils.escape(oauth_parameter_name) + escaped_value = utils.escape(value) + + # 2. Each parameter's name is immediately followed by an "=" character + # (ASCII code 61), a """ character (ASCII code 34), the parameter + # value (MAY be empty), and another """ character (ASCII code 34). + part = u'{0}="{1}"'.format(escaped_name, escaped_value) + + authorization_header_parameters_parts.append(part) + + # 3. Parameters are separated by a "," character (ASCII code 44) and + # OPTIONAL linear whitespace per `RFC2617`_. + # + # .. _`RFC2617`: http://tools.ietf.org/html/rfc2617 + authorization_header_parameters = ', '.join( + authorization_header_parameters_parts) + + # 4. The OPTIONAL "realm" parameter MAY be added and interpreted per + # `RFC2617 section 1.2`_. + # + # .. _`RFC2617 section 1.2`: http://tools.ietf.org/html/rfc2617#section-1.2 + if realm: + # NOTE: realm should *not* be escaped + authorization_header_parameters = (u'realm="%s", ' % realm + + authorization_header_parameters) + + # the auth-scheme name set to "OAuth" (case insensitive). + authorization_header = u'OAuth %s' % authorization_header_parameters + + # contribute the Authorization header to the given headers + full_headers = {} + full_headers.update(headers) + full_headers[u'Authorization'] = authorization_header + return full_headers + + +def _append_params(oauth_params, params): + """Append OAuth params to an existing set of parameters. + + Both params and oauth_params is must be lists of 2-tuples. + + Per `section 3.5.2`_ and `3.5.3`_ of the spec. + + .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2 + .. _`3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 + + """ + merged = list(params) + merged.extend(oauth_params) + # The request URI / entity-body MAY include other request-specific + # parameters, in which case, the protocol parameters SHOULD be appended + # following the request-specific parameters, properly separated by an "&" + # character (ASCII code 38) + merged.sort(key=lambda i: i[0].startswith('oauth_')) + return merged + + +def prepare_form_encoded_body(oauth_params, body): + """Prepare the Form-Encoded Body. + + Per `section 3.5.2`_ of the spec. + + .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2 + + """ + # append OAuth params to the existing body + return _append_params(oauth_params, body) + + +def prepare_request_uri_query(oauth_params, uri): + """Prepare the Request URI Query. + + Per `section 3.5.3`_ of the spec. + + .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 + + """ + # append OAuth params to the existing set of query components + sch, net, path, par, query, fra = urlparse(uri) + query = urlencode(_append_params(oauth_params, extract_params(query) or [])) + return urlunparse((sch, net, path, par, query, fra)) diff --git a/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/signature.py b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/signature.py new file mode 100644 index 0000000..dbd43aa --- /dev/null +++ b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/signature.py @@ -0,0 +1,551 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +""" +oauthlib.oauth1.rfc5849.signature +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module represents a direct implementation of `section 3.4`_ of the spec. + +Terminology: + * Client: software interfacing with an OAuth API + * Server: the API provider + * Resource Owner: the user who is granting authorization to the client + +Steps for signing a request: + +1. Collect parameters from the uri query, auth header, & body +2. Normalize those parameters +3. Normalize the uri +4. Pass the normalized uri, normalized parameters, and http method to + construct the base string +5. Pass the base string and any keys needed to a signing function + +.. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 +""" +import binascii +import hashlib +import hmac +import urlparse +from . import utils +from oauthlib.common import extract_params, safe_string_equals + + +def construct_base_string(http_method, base_string_uri, + normalized_encoded_request_parameters): + """**String Construction** + Per `section 3.4.1.1`_ of the spec. + + For example, the HTTP request:: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" + + c2&a3=2+q + + is represented by the following signature base string (line breaks + are for display purposes only):: + + POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q + %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_ + key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m + ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk + 9d7dh3k39sjv7 + + .. _`section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 + """ + + # The signature base string is constructed by concatenating together, + # in order, the following HTTP request elements: + + # 1. The HTTP request method in uppercase. For example: "HEAD", + # "GET", "POST", etc. If the request uses a custom HTTP method, it + # MUST be encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + base_string = utils.escape(http_method.upper()) + + # 2. An "&" character (ASCII code 38). + base_string += u'&' + + # 3. The base string URI from `Section 3.4.1.2`_, after being encoded + # (`Section 3.6`_). + # + # .. _`Section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 + # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6 + base_string += utils.escape(base_string_uri) + + # 4. An "&" character (ASCII code 38). + base_string += u'&' + + # 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after + # being encoded (`Section 3.6`). + # + # .. _`Section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 + # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6 + base_string += utils.escape(normalized_encoded_request_parameters) + + return base_string + + +def normalize_base_string_uri(uri): + """**Base String URI** + Per `section 3.4.1.2`_ of the spec. + + For example, the HTTP request:: + + GET /r%20v/X?id=123 HTTP/1.1 + Host: EXAMPLE.COM:80 + + is represented by the base string URI: "http://example.com/r%20v/X". + + In another example, the HTTPS request:: + + GET /?q=1 HTTP/1.1 + Host: www.example.net:8080 + + is represented by the base string URI: "https://www.example.net:8080/". + + .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 + """ + if not isinstance(uri, unicode): + raise ValueError('uri must be a unicode object.') + + # FIXME: urlparse does not support unicode + scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) + + # The scheme, authority, and path of the request resource URI `RFC3986` + # are included by constructing an "http" or "https" URI representing + # the request resource (without the query or fragment) as follows: + # + # .. _`RFC2616`: http://tools.ietf.org/html/rfc3986 + + # 1. The scheme and host MUST be in lowercase. + scheme = scheme.lower() + netloc = netloc.lower() + + # 2. The host and port values MUST match the content of the HTTP + # request "Host" header field. + # TODO: enforce this constraint + + # 3. The port MUST be included if it is not the default port for the + # scheme, and MUST be excluded if it is the default. Specifically, + # the port MUST be excluded when making an HTTP request `RFC2616`_ + # to port 80 or when making an HTTPS request `RFC2818`_ to port 443. + # All other non-default port numbers MUST be included. + # + # .. _`RFC2616`: http://tools.ietf.org/html/rfc2616 + # .. _`RFC2818`: http://tools.ietf.org/html/rfc2818 + default_ports = ( + (u'http', u'80'), + (u'https', u'443'), + ) + if u':' in netloc: + host, port = netloc.split(u':', 1) + if (scheme, port) in default_ports: + netloc = host + + return urlparse.urlunparse((scheme, netloc, path, u'', u'', u'')) + + +# ** Request Parameters ** +# +# Per `section 3.4.1.3`_ of the spec. +# +# In order to guarantee a consistent and reproducible representation of +# the request parameters, the parameters are collected and decoded to +# their original decoded form. They are then sorted and encoded in a +# particular manner that is often different from their original +# encoding scheme, and concatenated into a single string. +# +# .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 + +def collect_parameters(uri_query='', body=[], headers=None, + exclude_oauth_signature=True): + """**Parameter Sources** + + Parameters starting with `oauth_` will be unescaped. + + Body parameters must be supplied as a dict, a list of 2-tuples, or a + formencoded query string. + + Headers must be supplied as a dict. + + Per `section 3.4.1.3.1`_ of the spec. + + For example, the HTTP request:: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" + + c2&a3=2+q + + contains the following (fully decoded) parameters used in the + signature base sting:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | =%3D | + | a3 | a | + | c@ | | + | a2 | r b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2 q | + +------------------------+------------------+ + + Note that the value of "b5" is "=%3D" and not "==". Both "c@" and + "c2" have empty values. While the encoding rules specified in this + specification for the purpose of constructing the signature base + string exclude the use of a "+" character (ASCII code 43) to + represent an encoded space character (ASCII code 32), this practice + is widely used in "application/x-www-form-urlencoded" encoded values, + and MUST be properly decoded, as demonstrated by one of the "a3" + parameter instances (the "a3" parameter is used twice in this + request). + + .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 + """ + headers = headers or {} + params = [] + + # The parameters from the following sources are collected into a single + # list of name/value pairs: + + # * The query component of the HTTP request URI as defined by + # `RFC3986, Section 3.4`_. The query component is parsed into a list + # of name/value pairs by treating it as an + # "application/x-www-form-urlencoded" string, separating the names + # and values and decoding them as defined by + # `W3C.REC-html40-19980424`_, Section 17.13.4. + # + # .. _`RFC3986, Section 3.4`: http://tools.ietf.org/html/rfc3986#section-3.4 + # .. _`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + if uri_query: + params.extend(urlparse.parse_qsl(uri_query, keep_blank_values=True)) + + # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if + # present. The header's content is parsed into a list of name/value + # pairs excluding the "realm" parameter if present. The parameter + # values are decoded as defined by `Section 3.5.1`_. + # + # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 + if headers: + headers_lower = dict((k.lower(), v) for k, v in headers.items()) + authorization_header = headers_lower.get(u'authorization') + if authorization_header is not None: + params.extend([i for i in utils.parse_authorization_header( + authorization_header) if i[0] != u'realm']) + + # * The HTTP request entity-body, but only if all of the following + # conditions are met: + # * The entity-body is single-part. + # + # * The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # `W3C.REC-html40-19980424`_. + + # * The HTTP request entity-header includes the "Content-Type" + # header field set to "application/x-www-form-urlencoded". + # + # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + + # TODO: enforce header param inclusion conditions + bodyparams = extract_params(body) or [] + params.extend(bodyparams) + + # ensure all oauth params are unescaped + unescaped_params = [] + for k, v in params: + if k.startswith(u'oauth_'): + v = utils.unescape(v) + unescaped_params.append((k, v)) + + # The "oauth_signature" parameter MUST be excluded from the signature + # base string if present. + if exclude_oauth_signature: + unescaped_params = filter(lambda i: i[0] != u'oauth_signature', + unescaped_params) + + return unescaped_params + + +def normalize_parameters(params): + """**Parameters Normalization** + Per `section 3.4.1.3.2`_ of the spec. + + For example, the list of parameters from the previous section would + be normalized as follows: + + Encoded:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | %3D%253D | + | a3 | a | + | c%40 | | + | a2 | r%20b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2%20q | + +------------------------+------------------+ + + Sorted:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | a2 | r%20b | + | a3 | 2%20q | + | a3 | a | + | b5 | %3D%253D | + | c%40 | | + | c2 | | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_nonce | 7d8f3e4a | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_token | kkk9d7dh3k39sjv7 | + +------------------------+------------------+ + + Concatenated Pairs:: + + +-------------------------------------+ + | Name=Value | + +-------------------------------------+ + | a2=r%20b | + | a3=2%20q | + | a3=a | + | b5=%3D%253D | + | c%40= | + | c2= | + | oauth_consumer_key=9djdj82h48djs9d2 | + | oauth_nonce=7d8f3e4a | + | oauth_signature_method=HMAC-SHA1 | + | oauth_timestamp=137131201 | + | oauth_token=kkk9d7dh3k39sjv7 | + +-------------------------------------+ + + and concatenated together into a single string (line breaks are for + display purposes only):: + + a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj + dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1 + &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7 + + .. _`section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 + """ + + # The parameters collected in `Section 3.4.1.3`_ are normalized into a + # single string as follows: + # + # .. _`Section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 + + # 1. First, the name and value of each parameter are encoded + # (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key_values = [(utils.escape(k), utils.escape(v)) for k, v in params] + + # 2. The parameters are sorted by name, using ascending byte value + # ordering. If two or more parameters share the same name, they + # are sorted by their value. + key_values.sort() + + # 3. The name of each parameter is concatenated to its corresponding + # value using an "=" character (ASCII code 61) as a separator, even + # if the value is empty. + parameter_parts = [u'{0}={1}'.format(k, v) for k, v in key_values] + + # 4. The sorted name/value pairs are concatenated together into a + # single string by using an "&" character (ASCII code 38) as + # separator. + return u'&'.join(parameter_parts) + + +def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): + """**HMAC-SHA1** + + The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature + algorithm as defined in `RFC2104`_:: + + digest = HMAC-SHA1 (key, text) + + Per `section 3.4.2`_ of the spec. + + .. _`RFC2104`: http://tools.ietf.org/html/rfc2104 + .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2 + """ + + # The HMAC-SHA1 function variables are used in following way: + + # text is set to the value of the signature base string from + # `Section 3.4.1.1`_. + # + # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 + text = base_string + + # key is set to the concatenated values of: + # 1. The client shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key = utils.escape(client_secret or u'') + + # 2. An "&" character (ASCII code 38), which MUST be included + # even when either secret is empty. + key += u'&' + + # 3. The token shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key += utils.escape(resource_owner_secret or u'') + + # FIXME: HMAC does not support unicode! + key_utf8 = key.encode('utf-8') + text_utf8 = text.encode('utf-8') + signature = hmac.new(key_utf8, text_utf8, hashlib.sha1) + + # digest is used to set the value of the "oauth_signature" protocol + # parameter, after the result octet string is base64-encoded + # per `RFC2045, Section 6.8`. + # + # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8 + return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') + + +def sign_rsa_sha1(base_string, rsa_private_key): + """**RSA-SHA1** + + Per `section 3.4.3`_ of the spec. + + The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature + algorithm as defined in `RFC3447, Section 8.2`_ (also known as + PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. To + use this method, the client MUST have established client credentials + with the server that included its RSA public key (in a manner that is + beyond the scope of this specification). + + NOTE: this method requires the python-rsa library. + + .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3 + .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2 + + """ + # TODO: finish RSA documentation + from Crypto.PublicKey import RSA + from Crypto.Signature import PKCS1_v1_5 + from Crypto.Hash import SHA + key = RSA.importKey(rsa_private_key) + h = SHA.new(base_string) + p = PKCS1_v1_5.new(key) + return binascii.b2a_base64(p.sign(h))[:-1].decode('utf-8') + + +def sign_plaintext(client_secret, resource_owner_secret): + """Sign a request using plaintext. + + Per `section 3.4.4`_ of the spec. + + The "PLAINTEXT" method does not employ a signature algorithm. It + MUST be used with a transport-layer mechanism such as TLS or SSL (or + sent over a secure channel with equivalent protections). It does not + utilize the signature base string or the "oauth_timestamp" and + "oauth_nonce" parameters. + + .. _`section 3.4.4`: http://tools.ietf.org/html/rfc5849#section-3.4.4 + + """ + + # The "oauth_signature" protocol parameter is set to the concatenated + # value of: + + # 1. The client shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + signature = utils.escape(client_secret or u'') + + # 2. An "&" character (ASCII code 38), which MUST be included even + # when either secret is empty. + signature += u'&' + + # 3. The token shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + signature += utils.escape(resource_owner_secret or u'') + + return signature + + +def verify_hmac_sha1(request, client_secret=None, + resource_owner_secret=None): + """Verify a HMAC-SHA1 signature. + + Per `section 3.4`_ of the spec. + + .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + """ + norm_params = normalize_parameters(request.params) + uri = normalize_base_string_uri(request.uri) + base_string = construct_base_string(request.http_method, uri, norm_params) + signature = sign_hmac_sha1(base_string, client_secret, + resource_owner_secret) + return safe_string_equals(signature, request.signature) + + +def verify_rsa_sha1(request, rsa_public_key): + """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature. + + Per `section 3.4.3`_ of the spec. + + Note this method requires the PyCrypto library. + + .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3 + + """ + from Crypto.PublicKey import RSA + from Crypto.Signature import PKCS1_v1_5 + from Crypto.Hash import SHA + key = RSA.importKey(rsa_public_key) + norm_params = normalize_parameters(request.params) + uri = normalize_base_string_uri(request.uri) + message = construct_base_string(request.http_method, uri, norm_params) + h = SHA.new(message) + p = PKCS1_v1_5.new(key) + sig = binascii.a2b_base64(request.signature) + return p.verify(h, sig) + + +def verify_plaintext(request, client_secret=None, resource_owner_secret=None): + """Verify a PLAINTEXT signature. + + Per `section 3.4`_ of the spec. + + .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + """ + signature = sign_plaintext(client_secret, resource_owner_secret) + return safe_string_equals(signature, request.signature) diff --git a/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/utils.py b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/utils.py new file mode 100644 index 0000000..8fb0e77 --- /dev/null +++ b/requests-0.14.0/requests/packages/oauthlib/oauth1/rfc5849/utils.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +""" +oauthlib.utils +~~~~~~~~~~~~~~ + +This module contains utility methods used by various parts of the OAuth +spec. +""" + +import string +import urllib2 + +from oauthlib.common import quote, unquote + +UNICODE_ASCII_CHARACTER_SET = (string.ascii_letters.decode('ascii') + + string.digits.decode('ascii')) + + +def filter_params(target): + """Decorator which filters params to remove non-oauth_* parameters + + Assumes the decorated method takes a params dict or list of tuples as its + first argument. + """ + def wrapper(params, *args, **kwargs): + params = filter_oauth_params(params) + return target(params, *args, **kwargs) + + wrapper.__doc__ = target.__doc__ + return wrapper + + +def filter_oauth_params(params): + """Removes all non oauth parameters from a dict or a list of params.""" + is_oauth = lambda kv: kv[0].startswith(u"oauth_") + if isinstance(params, dict): + return filter(is_oauth, params.items()) + else: + return filter(is_oauth, params) + + +def escape(u): + """Escape a unicode string in an OAuth-compatible fashion. + + Per `section 3.6`_ of the spec. + + .. _`section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + + """ + if not isinstance(u, unicode): + raise ValueError('Only unicode objects are escapable.') + # Letters, digits, and the characters '_.-' are already treated as safe + # by urllib.quote(). We need to add '~' to fully support rfc5849. + return quote(u, safe='~') + + +def unescape(u): + if not isinstance(u, unicode): + raise ValueError('Only unicode objects are unescapable.') + return unquote(u) + + +def urlencode(query): + """Encode a sequence of two-element tuples or dictionary into a URL query string. + + Operates using an OAuth-safe escape() method, in contrast to urllib.urlencode. + """ + # Convert dictionaries to list of tuples + if isinstance(query, dict): + query = query.items() + return u"&".join([u'='.join([escape(k), escape(v)]) for k, v in query]) + + +def parse_keqv_list(l): + """A unicode-safe version of urllib2.parse_keqv_list""" + encoded_list = [u.encode('utf-8') for u in l] + encoded_parsed = urllib2.parse_keqv_list(encoded_list) + return dict((k.decode('utf-8'), + v.decode('utf-8')) for k, v in encoded_parsed.items()) + + +def parse_http_list(u): + """A unicode-safe version of urllib2.parse_http_list""" + encoded_str = u.encode('utf-8') + encoded_list = urllib2.parse_http_list(encoded_str) + return [s.decode('utf-8') for s in encoded_list] + + +def parse_authorization_header(authorization_header): + """Parse an OAuth authorization header into a list of 2-tuples""" + auth_scheme = u'OAuth ' + if authorization_header.startswith(auth_scheme): + authorization_header = authorization_header.replace(auth_scheme, u'', 1) + items = parse_http_list(authorization_header) + try: + return parse_keqv_list(items).items() + except ValueError: + raise ValueError('Malformed authorization header') -- cgit v1.2.3