oauthlib
authorKenneth Reitz <me@kennethreitz.com>
Thu, 28 Jun 2012 23:23:25 +0000 (16:23 -0700)
committerKenneth Reitz <me@kennethreitz.com>
Thu, 28 Jun 2012 23:23:25 +0000 (16:23 -0700)
14 files changed:
Makefile
requests/packages/oauthlib/__init__.py [new file with mode: 0644]
requests/packages/oauthlib/common.py [new file with mode: 0644]
requests/packages/oauthlib/oauth1/__init__.py [new file with mode: 0644]
requests/packages/oauthlib/oauth1/rfc5849/__init__.py [new file with mode: 0644]
requests/packages/oauthlib/oauth1/rfc5849/parameters.py [new file with mode: 0644]
requests/packages/oauthlib/oauth1/rfc5849/signature.py [new file with mode: 0644]
requests/packages/oauthlib/oauth1/rfc5849/utils.py [new file with mode: 0644]
requests/packages/oauthlib/oauth2/__init__.py [new file with mode: 0644]
requests/packages/oauthlib/oauth2/draft25/__init__.py [new file with mode: 0644]
requests/packages/oauthlib/oauth2/draft25/parameters.py [new file with mode: 0644]
requests/packages/oauthlib/oauth2/draft25/tokens.py [new file with mode: 0644]
requests/packages/oauthlib/oauth2/draft25/utils.py [new file with mode: 0644]
setup.py

index f416ed6e877315d256b882828e5d89fd83f70798..1473d1258b24a3bd0d024026af3f81938b6a6a86 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -58,14 +58,23 @@ site:
 clean:
        git clean -Xfd
 
-deps:
+deps: urllib3 certs
+
+urllib3:
        rm -fr requests/packages/urllib3
        git clone https://github.com/shazow/urllib3.git
        cd urllib3 && git checkout master && cd ..
        mv urllib3/urllib3 requests/packages/
        rm -fr urllib3
 
+oauthlib:
+       rm -fr requests/packages/oauthlib
+       git clone https://github.com/idan/oauthlib.git
+       cd oauthlib && git checkout master && cd ..
+       mv oauthlib/oauthlib requests/packages/
+       rm -fr oauthlib
+
 certs:
-       cd requests && curl -O http://curl.haxx.se/ca/cacert.pem
+       cd requests && curl -O https://raw.github.com/kennethreitz/certifi/master/certifi/cacert.pem
 
 docs: site
diff --git a/requests/packages/oauthlib/__init__.py b/requests/packages/oauthlib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/requests/packages/oauthlib/common.py b/requests/packages/oauthlib/common.py
new file mode 100644 (file)
index 0000000..70fb6a0
--- /dev/null
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+"""
+oauthlib.common
+~~~~~~~~~~~~~~
+
+This module provides data structures and utilities common
+to all implementations of OAuth.
+"""
+
+import random
+import re
+import string
+import time
+import urllib
+import urlparse
+
+UNICODE_ASCII_CHARACTER_SET = (string.ascii_letters.decode('ascii') +
+    string.digits.decode('ascii'))
+
+always_safe = (u'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+               u'abcdefghijklmnopqrstuvwxyz'
+               u'0123456789' u'_.-')
+
+
+def quote(s, safe=u'/'):
+    encoded = s.encode("utf-8")
+    quoted = urllib.quote(encoded, safe)
+    return quoted.decode("utf-8")
+
+
+def unquote(s):
+    encoded = s.encode("utf-8")
+    unquoted = urllib.unquote(encoded)
+    return unquoted.decode("utf-8")
+
+
+def urlencode(params):
+    utf8_params = encode_params_utf8(params)
+    urlencoded = urllib.urlencode(utf8_params)
+    return urlencoded.decode("utf-8")
+
+
+def encode_params_utf8(params):
+    """Ensures that all parameters in a list of 2-element tuples are encoded to
+    bytestrings using UTF-8
+    """
+    encoded = []
+    for k, v in params:
+        encoded.append((
+            k.encode('utf-8') if isinstance(k, unicode) else k,
+            v.encode('utf-8') if isinstance(v, unicode) else v))
+    return encoded
+
+
+def decode_params_utf8(params):
+    """Ensures that all parameters in a list of 2-element tuples are decoded to
+    unicode using UTF-8.
+    """
+    decoded = []
+    for k, v in params:
+        decoded.append((
+            k.decode('utf-8') if isinstance(k, str) else k,
+            v.decode('utf-8') if isinstance(v, str) else v))
+    return decoded
+
+
+urlencoded = set(always_safe) | set(u'=&;%+~')
+
+
+def urldecode(query):
+    """Decode a query string in x-www-form-urlencoded format into a sequence
+    of two-element tuples.
+
+    Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce
+    correct formatting of the query string by validation. If validation fails
+    a ValueError will be raised. urllib.parse_qsl will only raise errors if
+    any of name-value pairs omits the equals sign.
+    """
+    # Check if query contains invalid characters
+    if query and not set(query) <= urlencoded:
+        raise ValueError('Invalid characters in query string.')
+
+    # Check for correctly hex encoded values using a regular expression
+    # All encoded values begin with % followed by two hex characters
+    # correct = %00, %A0, %0A, %FF
+    # invalid = %G0, %5H, %PO
+    invalid_hex = u'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]'
+    if len(re.findall(invalid_hex, query)):
+        raise ValueError('Invalid hex encoding in query string.')
+
+    query = query.decode('utf-8') if isinstance(query, str) else query
+    # We want to allow queries such as "c2" whereas urlparse.parse_qsl
+    # with the strict_parsing flag will not.
+    params = urlparse.parse_qsl(query, keep_blank_values=True)
+
+    # unicode all the things
+    return decode_params_utf8(params)
+
+
+def extract_params(raw):
+    """Extract parameters and return them as a list of 2-tuples.
+
+    Will successfully extract parameters from urlencoded query strings,
+    dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an
+    empty list of parameters. Any other input will result in a return
+    value of None.
+    """
+    if isinstance(raw, basestring):
+        try:
+            params = urldecode(raw)
+        except ValueError:
+            params = None
+    elif hasattr(raw, '__iter__'):
+        try:
+            dict(raw)
+        except ValueError:
+            params = None
+        except TypeError:
+            params = None
+        else:
+            params = list(raw.items() if isinstance(raw, dict) else raw)
+            params = decode_params_utf8(params)
+    else:
+        params = None
+
+    return params
+
+
+def generate_nonce():
+    """Generate pseudorandom nonce that is unlikely to repeat.
+
+    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
+    Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+    A random 64-bit number is appended to the epoch timestamp for both
+    randomness and to decrease the likelihood of collisions.
+
+    .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+    .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
+    """
+    return unicode(unicode(random.getrandbits(64)) + generate_timestamp())
+
+
+def generate_timestamp():
+    """Get seconds since epoch (UTC).
+
+    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
+    Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+    .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+    .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
+    """
+    return unicode(int(time.time()))
+
+
+def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
+    """Generates a non-guessable OAuth token
+
+    OAuth (1 and 2) does not specify the format of tokens except that they
+    should be strings of random characters. Tokens should not be guessable
+    and entropy when generating the random characters is important. Which is
+    why SystemRandom is used instead of the default random.choice method.
+    """
+    rand = random.SystemRandom()
+    return u''.join(rand.choice(chars) for x in range(length))
+
+
+def add_params_to_qs(query, params):
+    """Extend a query with a list of two-tuples."""
+    queryparams = urlparse.parse_qsl(query, keep_blank_values=True)
+    queryparams.extend(params)
+    return urlencode(queryparams)
+
+
+def add_params_to_uri(uri, params):
+    """Add a list of two-tuples to the uri query components."""
+    sch, net, path, par, query, fra = urlparse.urlparse(uri)
+    query = add_params_to_qs(query, params)
+    return urlparse.urlunparse((sch, net, path, par, query, fra))
+
+def safe_string_equals(a, b):
+    """ Near-constant time string comparison. 
+
+    Used in order to avoid timing attacks on sensitive information such
+    as secret keys during request verification (`rootLabs`_).
+
+    .. _`rootLabs`: http://rdist.root.org/2010/01/07/timing-independent-array-comparison/
+    
+    """
+    if len(a) != len(b):
+        return False
+
+    result = 0
+    for x, y in zip(a, b):
+        result |= ord(x) ^ ord(y)
+    return result == 0
+
+class Request(object):
+    """A malleable representation of a signable HTTP request.
+
+    Body argument may contain any data, but parameters will only be decoded if
+    they are one of:
+
+    * urlencoded query string
+    * dict
+    * list of 2-tuples
+
+    Anything else will be treated as raw body data to be passed through
+    unmolested.
+    """
+
+    def __init__(self, uri, http_method=u'GET', body=None, headers=None):
+        self.uri = uri
+        self.http_method = http_method
+        self.headers = headers or {}
+        self.body = body
+        self.decoded_body = extract_params(body)
+        self.oauth_params = []
+
+    @property
+    def uri_query(self):
+        return urlparse.urlparse(self.uri).query
+
+    @property
+    def uri_query_params(self):
+        return urlparse.parse_qsl(self.uri_query, keep_blank_values=True,
+                                  strict_parsing=True)
diff --git a/requests/packages/oauthlib/oauth1/__init__.py b/requests/packages/oauthlib/oauth1/__init__.py
new file mode 100644 (file)
index 0000000..ef692b5
--- /dev/null
@@ -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/packages/oauthlib/oauth1/rfc5849/__init__.py b/requests/packages/oauthlib/oauth1/rfc5849/__init__.py
new file mode 100644 (file)
index 0000000..3e518a8
--- /dev/null
@@ -0,0 +1,887 @@
+# -*- 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
+
+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)
+        logging.debug("Collected params: {0}".format(collected_params))
+
+        normalized_params = signature.normalize_parameters(collected_params)
+        normalized_uri = signature.normalize_base_string_uri(request.uri)
+        logging.debug("Normalized params: {0}".format(normalized_params))
+        logging.debug("Normalized URI: {0}".format(normalized_uri))
+
+        base_string = signature.construct_base_string(request.http_method,
+            normalized_uri, normalized_params)
+
+        logging.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)
+
+        logging.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/packages/oauthlib/oauth1/rfc5849/parameters.py b/requests/packages/oauthlib/oauth1/rfc5849/parameters.py
new file mode 100644 (file)
index 0000000..dee23a4
--- /dev/null
@@ -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/packages/oauthlib/oauth1/rfc5849/signature.py b/requests/packages/oauthlib/oauth1/rfc5849/signature.py
new file mode 100644 (file)
index 0000000..dbd43aa
--- /dev/null
@@ -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/packages/oauthlib/oauth1/rfc5849/utils.py b/requests/packages/oauthlib/oauth1/rfc5849/utils.py
new file mode 100644 (file)
index 0000000..8fb0e77
--- /dev/null
@@ -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')
diff --git a/requests/packages/oauthlib/oauth2/__init__.py b/requests/packages/oauthlib/oauth2/__init__.py
new file mode 100644 (file)
index 0000000..0e8933c
--- /dev/null
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+"""
+oauthlib.oauth2
+~~~~~~~~~~~~~~
+
+This module is a wrapper for the most recent implementation of OAuth 2.0 Client
+and Server classes.
+"""
+
+from .draft25 import Client, Server
+
diff --git a/requests/packages/oauthlib/oauth2/draft25/__init__.py b/requests/packages/oauthlib/oauth2/draft25/__init__.py
new file mode 100644 (file)
index 0000000..7c90573
--- /dev/null
@@ -0,0 +1,497 @@
+"""
+oauthlib.oauth2.draft_25
+~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 2.0 draft 25 requests.
+"""
+from tokens import prepare_bearer_uri, prepare_bearer_headers
+from tokens import prepare_bearer_body, prepare_mac_header
+from parameters import prepare_grant_uri, prepare_token_request
+from parameters import parse_authorization_code_response
+from parameters import parse_implicit_response, parse_token_response
+
+
+AUTH_HEADER = u'auth_header'
+URI_QUERY = u'query'
+BODY = u'body'
+
+
+class Client(object):
+
+    def __init__(self, client_id,
+            default_redirect_uri=None,
+            token_type=None,
+            access_token=None,
+            refresh_token=None):
+        """Initialize a client with commonly used attributes."""
+
+        self.client_id = client_id
+        self.default_redirect_uri = default_redirect_uri
+        self.token_type = token_type
+        self.access_token = access_token
+        self.refresh_token = refresh_token
+        self.token_types = {
+            u'bearer': self._add_bearer_token,
+            u'mac': self._add_mac_token
+        }
+
+    def add_token(self, uri, http_method=u'GET', body=None, headers=None,
+            token_placement=AUTH_HEADER):
+        """Add token to the request uri, body or authorization header.
+
+        The access token type provides the client with the information
+        required to successfully utilize the access token to make a protected
+        resource request (along with type-specific attributes).  The client
+        MUST NOT use an access token if it does not understand the token
+        type.
+
+        For example, the "bearer" token type defined in
+        [I-D.ietf-oauth-v2-bearer] is utilized by simply including the access
+        token string in the request:
+
+        GET /resource/1 HTTP/1.1
+        Host: example.com
+        Authorization: Bearer mF_9.B5f-4.1JqM
+
+        while the "mac" token type defined in [I-D.ietf-oauth-v2-http-mac] is
+        utilized by issuing a MAC key together with the access token which is
+        used to sign certain components of the HTTP requests:
+
+        GET /resource/1 HTTP/1.1
+        Host: example.com
+        Authorization: MAC id="h480djs93hd8",
+                            nonce="274312:dj83hs9s",
+                            mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
+
+        .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#ref-I-D.ietf-oauth-v2-bearer
+        .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#ref-I-D.ietf-oauth-v2-http-mac
+        """
+        return self.token_types[self.token_type](uri, http_method, body,
+                    headers, token_placement)
+
+    def prepare_refresh_body(self, body=u'', refresh_token=None, scope=None):
+        """Prepare an access token request, using a refresh token.
+
+        If the authorization server issued a refresh token to the client, the
+        client makes a refresh request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format in the HTTP request entity-body:
+
+        grant_type
+                REQUIRED.  Value MUST be set to "refresh_token".
+        refresh_token
+                REQUIRED.  The refresh token issued to the client.
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                Section 3.3.  The requested scope MUST NOT include any scope
+                not originally granted by the resource owner, and if omitted is
+                treated as equal to the scope originally granted by the
+                resource owner.
+        """
+        refresh_token = refresh_token or self.refresh_token
+        return prepare_token_request(u'refresh_token', body=body, scope=scope,
+                refresh_token=refresh_token)
+
+    def _add_bearer_token(self, uri, http_method=u'GET', body=None,
+            headers=None, token_placement=AUTH_HEADER):
+        """Add a bearer token to the request uri, body or authorization header."""
+        if token_placement == AUTH_HEADER:
+            headers = prepare_bearer_headers(self.token, headers)
+
+        if token_placement == URI_QUERY:
+            uri = prepare_bearer_uri(self.token, uri)
+
+        if token_placement == BODY:
+            body = prepare_bearer_body(self.token, body)
+
+        return uri, headers, body
+
+    def _add_mac_token(self, uri, http_method=u'GET', body=None,
+            headers=None, token_placement=AUTH_HEADER):
+        """Add a MAC token to the request authorization header."""
+        headers = prepare_mac_header(self.token, uri, self.key, http_method,
+                        headers=headers, body=body, ext=self.ext,
+                        hash_algorithm=self.hash_algorithm)
+        return uri, headers, body
+
+    def _populate_attributes(self, response):
+        """Add commonly used values such as access_token to self."""
+
+        if u'access_token' in response:
+            self.access_token = response.get(u'access_token')
+
+        if u'refresh_token' in response:
+            self.refresh_token = response.get(u'refresh_token')
+
+        if u'token_type' in response:
+            self.token_type = response.get(u'token_type')
+
+        if u'expires_in' in response:
+            self.expires_in = response.get(u'expires_in')
+
+        if u'code' in response:
+            self.code = response.get(u'code')
+
+    def prepare_request_uri(self, *args, **kwargs):
+        """Abstract method used to create request URIs."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def prepare_request_body(self, *args, **kwargs):
+        """Abstract method used to create request bodies."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def parse_request_uri_response(self, *args, **kwargs):
+        """Abstract method used to parse redirection responses."""
+
+    def parse_request_body_response(self, *args, **kwargs):
+        """Abstract method used to parse JSON responses."""
+
+
+class WebApplicationClient(Client):
+    """A client utilizing the authorization code grant workflow.
+
+    A web application is a confidential client running on a web
+    server.  Resource owners access the client via an HTML user
+    interface rendered in a user-agent on the device used by the
+    resource owner.  The client credentials as well as any access
+    token issued to the client are stored on the web server and are
+    not exposed to or accessible by the resource owner.
+
+    The authorization code grant type is used to obtain both access
+    tokens and refresh tokens and is optimized for confidential clients.
+    As a redirection-based flow, the client must be capable of
+    interacting with the resource owner's user-agent (typically a web
+    browser) and capable of receiving incoming requests (via redirection)
+    from the authorization server.
+    """
+
+    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
+            state=None, **kwargs):
+        """Prepare the authorization code request URI
+
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format as defined by
+        [`W3C.REC-html401-19991224`_]:
+
+        response_type
+                REQUIRED.  Value MUST be set to "code".
+        client_id
+                REQUIRED.  The client identifier as described in `Section 2.2`_.
+        redirect_uri
+                OPTIONAL.  As described in `Section 3.1.2`_.
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+        state
+                RECOMMENDED.  An opaque value used by the client to maintain
+                state between the request and callback.  The authorization
+                server includes this value when redirecting the user-agent back
+                to the client.  The parameter SHOULD be used for preventing
+                cross-site request forgery as described in `Section 10.12`_.
+
+        .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#ref-W3C.REC-html401-19991224
+        .. _`Section 2.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-2.2
+        .. _`Section 3.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.1.2
+        .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+        .. _`Section 10.12`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-10.12
+        """
+        redirect_uri = redirect_uri or self.default_redirect_uri
+        return prepare_grant_uri(uri, self.client_id, u'code',
+                redirect_uri=redirect_uri, scope=scope, state=state, **kwargs)
+
+    def prepare_request_body(self, code, body=u'', redirect_uri=None, **kwargs):
+        """Prepare the access token request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format in the HTTP request entity-body:
+
+        grant_type
+                REQUIRED.  Value MUST be set to "authorization_code".
+        code
+                REQUIRED.  The authorization code received from the
+                authorization server.
+        redirect_uri
+                REQUIRED, if the "redirect_uri" parameter was included in the
+                authorization request as described in Section 4.1.1, and their
+                values MUST be identical.
+
+        .. _`Section 4.1.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.1.1
+        """
+        redirect_uri = redirect_uri or self.default_redirect_uri
+        code = code or self.code
+        return prepare_token_request(u'authorization_code', code=code, body=body,
+                                         redirect_uri=redirect_uri, **kwargs)
+
+    def parse_request_uri_response(self, uri, state=None):
+        """Parse the URI query for code and state.
+
+        If the resource owner grants the access request, the authorization
+        server issues an authorization code and delivers it to the client by
+        adding the following parameters to the query component of the
+        redirection URI using the "application/x-www-form-urlencoded" format:
+
+        code
+                REQUIRED.  The authorization code generated by the
+                authorization server.  The authorization code MUST expire
+                shortly after it is issued to mitigate the risk of leaks.  A
+                maximum authorization code lifetime of 10 minutes is
+                RECOMMENDED.  The client MUST NOT use the authorization code
+                more than once.  If an authorization code is used more than
+                once, the authorization server MUST deny the request and SHOULD
+                revoke (when possible) all tokens previously issued based on
+                that authorization code.  The authorization code is bound to
+                the client identifier and redirection URI.
+        state
+                REQUIRED if the "state" parameter was present in the client
+                authorization request.  The exact value received from the
+                client.
+        """
+        response = parse_authorization_code_response(uri, state=state)
+        self._populate_attributes(response)
+        return response
+
+    def parse_request_body_response(self, body, scope=None):
+        """Parse the JSON response body.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token and optional refresh
+        token as described in `Section 5.1`_.  If the request client
+        authentication failed or is invalid, the authorization server returns
+        an error response as described in `Section 5.2`_.
+
+        .. `Section 5.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.1
+        .. `Section 5.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.2
+        """
+        response = parse_token_response(body, scope=scope)
+        self._populate_attributes(response)
+        return response
+
+
+class UserAgentClient(Client):
+    """A public client utilizing the implicit code grant workflow.
+
+    A user-agent-based application is a public client in which the
+    client code is downloaded from a web server and executes within a
+    user-agent (e.g. web browser) on the device used by the resource
+    owner.  Protocol data and credentials are easily accessible (and
+    often visible) to the resource owner.  Since such applications
+    reside within the user-agent, they can make seamless use of the
+    user-agent capabilities when requesting authorization.
+
+    The implicit grant type is used to obtain access tokens (it does not
+    support the issuance of refresh tokens) and is optimized for public
+    clients known to operate a particular redirection URI.  These clients
+    are typically implemented in a browser using a scripting language
+    such as JavaScript.
+
+    As a redirection-based flow, the client must be capable of
+    interacting with the resource owner's user-agent (typically a web
+    browser) and capable of receiving incoming requests (via redirection)
+    from the authorization server.
+
+    Unlike the authorization code grant type in which the client makes
+    separate requests for authorization and access token, the client
+    receives the access token as the result of the authorization request.
+
+    The implicit grant type does not include client authentication, and
+    relies on the presence of the resource owner and the registration of
+    the redirection URI.  Because the access token is encoded into the
+    redirection URI, it may be exposed to the resource owner and other
+    applications residing on the same device.
+    """
+
+    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
+            state=None, **kwargs):
+        """Prepare the implicit grant request URI.
+
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format:
+
+        response_type
+                REQUIRED.  Value MUST be set to "token".
+        client_id
+                REQUIRED.  The client identifier as described in Section 2.2.
+        redirect_uri
+                OPTIONAL.  As described in Section 3.1.2.
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                Section 3.3.
+        state
+                RECOMMENDED.  An opaque value used by the client to maintain
+                state between the request and callback.  The authorization
+                server includes this value when redirecting the user-agent back
+                to the client.  The parameter SHOULD be used for preventing
+                cross-site request forgery as described in Section 10.12.
+        """
+        redirect_uri = redirect_uri or self.default_redirect_uri
+        return prepare_grant_uri(uri, self.client_id, u'token',
+                redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
+
+    def parse_request_uri_response(self, uri, state=None, scope=None):
+        """Parse the response URI fragment.
+
+        If the resource owner grants the access request, the authorization
+        server issues an access token and delivers it to the client by adding
+        the following parameters to the fragment component of the redirection
+        URI using the "application/x-www-form-urlencoded" format:
+
+        access_token
+                REQUIRED.  The access token issued by the authorization server.
+        token_type
+                REQUIRED.  The type of the token issued as described in
+                `Section 7.1`_.  Value is case insensitive.
+        expires_in
+                RECOMMENDED.  The lifetime in seconds of the access token.  For
+                example, the value "3600" denotes that the access token will
+                expire in one hour from the time the response was generated.
+                If omitted, the authorization server SHOULD provide the
+                expiration time via other means or document the default value.
+        scope
+                OPTIONAL, if identical to the scope requested by the client,
+                otherwise REQUIRED.  The scope of the access token as described
+                by `Section 3.3`_.
+        state
+                REQUIRED if the "state" parameter was present in the client
+                authorization request.  The exact value received from the
+                client.
+
+        .. _`Section 7.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-7.1
+        .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+        """
+        response = parse_implicit_response(uri, state=state, scope=scope)
+        self._populate_attributes(response)
+        return response
+
+
+class NativeApplicationClient(Client):
+    """A public client utilizing the client credentials grant workflow.
+
+    A native application is a public client installed and executed on
+    the device used by the resource owner.  Protocol data and
+    credentials are accessible to the resource owner.  It is assumed
+    that any client authentication credentials included in the
+    application can be extracted.  On the other hand, dynamically
+    issued credentials such as access tokens or refresh tokens can
+    receive an acceptable level of protection.  At a minimum, these
+    credentials are protected from hostile servers with which the
+    application may interact with.  On some platforms these
+    credentials might be protected from other applications residing on
+    the same device.
+
+    The client can request an access token using only its client
+    credentials (or other supported means of authentication) when the
+    client is requesting access to the protected resources under its
+    control, or those of another resource owner which has been previously
+    arranged with the authorization server (the method of which is beyond
+    the scope of this specification).
+
+    The client credentials grant type MUST only be used by confidential
+    clients.
+
+    Since the client authentication is used as the authorization grant,
+    no additional authorization request is needed.
+    """
+
+    def prepare_request_body(self, body=u'', scope=None, **kwargs):
+        """Add the client credentials to the request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format in the HTTP request entity-body:
+
+        grant_type
+                REQUIRED.  Value MUST be set to "client_credentials".
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+
+        .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+        """
+        return prepare_token_request(u'client_credentials', body=body,
+                                     scope=scope, **kwargs)
+
+    def parse_request_body_response(self, body, scope=None):
+        """Parse the JSON response body.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token as described in
+        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
+        failed client authentication or is invalid, the authorization server
+        returns an error response as described in `Section 5.2`_.
+
+        .. `Section 5.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.1
+        .. `Section 5.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.2
+        """
+        response = parse_token_response(body, scope=scope)
+        self._populate_attributes(response)
+        return response
+
+
+class PasswordCredentialsClient(Client):
+    """A public client using the resource owner password and username directly.
+
+    The resource owner password credentials grant type is suitable in
+    cases where the resource owner has a trust relationship with the
+    client, such as the device operating system or a highly privileged
+    application.  The authorization server should take special care when
+    enabling this grant type, and only allow it when other flows are not
+    viable.
+
+    The grant type is suitable for clients capable of obtaining the
+    resource owner's credentials (username and password, typically using
+    an interactive form).  It is also used to migrate existing clients
+    using direct authentication schemes such as HTTP Basic or Digest
+    authentication to OAuth by converting the stored credentials to an
+    access token.
+
+    The method through which the client obtains the resource owner
+    credentials is beyond the scope of this specification.  The client
+    MUST discard the credentials once an access token has been obtained.
+    """
+
+    def prepare_request_body(self, username, password, body=u'', scope=None,
+            **kwargs):
+        """Add the resource owner password and username to the request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format in the HTTP request entity-body:
+
+        grant_type
+                REQUIRED.  Value MUST be set to "password".
+        username
+                REQUIRED.  The resource owner username.
+        password
+                REQUIRED.  The resource owner password.
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+
+        .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+        """
+        return prepare_token_request(u'password', body=body, username=username,
+                password=password, scope=scope, **kwargs)
+
+    def parse_request_body_response(self, body, scope=None):
+        """Parse the JSON response body.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token and optional refresh
+        token as described in `Section 5.1`_.  If the request failed client
+        authentication or is invalid, the authorization server returns an
+        error response as described in `Section 5.2`_.
+
+        .. `Section 5.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.1
+        .. `Section 5.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-5.2
+        """
+        response = parse_token_response(body, scope=scope)
+        self._populate_attributes(response)
+        return response
+
+
+class Server(object):
+    pass
diff --git a/requests/packages/oauthlib/oauth2/draft25/parameters.py b/requests/packages/oauthlib/oauth2/draft25/parameters.py
new file mode 100644 (file)
index 0000000..ecc9f63
--- /dev/null
@@ -0,0 +1,256 @@
+"""
+oauthlib.oauth2_draft28.parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module contains methods related to `Section 4`_ of the OAuth 2 draft.
+
+.. _`Section 4`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4
+"""
+
+import json
+import urlparse
+from oauthlib.common import add_params_to_uri, add_params_to_qs
+
+
+def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
+            scope=None, state=None, **kwargs):
+    """Prepare the authorization grant request URI.
+
+    The client constructs the request URI by adding the following
+    parameters to the query component of the authorization endpoint URI
+    using the "application/x-www-form-urlencoded" format as defined by
+    [W3C.REC-html401-19991224]:
+
+    response_type
+            REQUIRED.  Value MUST be set to "code".
+    client_id
+            REQUIRED.  The client identifier as described in `Section 2.2`_.
+    redirect_uri
+            OPTIONAL.  As described in `Section 3.1.2`_.
+    scope
+            OPTIONAL.  The scope of the access request as described by
+            `Section 3.3`_.
+    state
+            RECOMMENDED.  An opaque value used by the client to maintain
+            state between the request and callback.  The authorization
+            server includes this value when redirecting the user-agent back
+            to the client.  The parameter SHOULD be used for preventing
+            cross-site request forgery as described in `Section 10.12`_.
+
+    GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
+        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
+    Host: server.example.com
+
+    .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#ref-W3C.REC-html401-19991224
+    .. _`Section 2.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-2.2
+    .. _`Section 3.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.1.2
+    .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+    .. _`section 10.12`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-10.12
+    """
+    params = [((u'response_type', response_type)),
+              ((u'client_id', client_id))]
+
+    if redirect_uri:
+        params.append((u'redirect_uri', redirect_uri))
+    if scope:
+        params.append((u'scope', scope))
+    if state:
+        params.append((u'state', state))
+
+    for k in kwargs:
+        params.append((unicode(k), kwargs[k]))
+
+    return add_params_to_uri(uri, params)
+
+
+def prepare_token_request(grant_type, body=u'', **kwargs):
+    """Prepare the access token request.
+
+    The client makes a request to the token endpoint by adding the
+    following parameters using the "application/x-www-form-urlencoded"
+    format in the HTTP request entity-body:
+
+    grant_type
+            REQUIRED.  Value MUST be set to "authorization_code".
+    code
+            REQUIRED.  The authorization code received from the
+            authorization server.
+    redirect_uri
+            REQUIRED, if the "redirect_uri" parameter was included in the
+            authorization request as described in `Section 4.1.1`_, and their
+            values MUST be identical.
+
+    grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
+    &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
+
+    .. _`Section 4.1.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-4.1.1
+    """
+    params = [(u'grant_type', grant_type)]
+    for k in kwargs:
+        params.append((unicode(k), kwargs[k]))
+
+    return add_params_to_qs(body, params)
+
+
+def parse_authorization_code_response(uri, state=None):
+    """Parse authorization grant response URI into a dict.
+
+    If the resource owner grants the access request, the authorization
+    server issues an authorization code and delivers it to the client by
+    adding the following parameters to the query component of the
+    redirection URI using the "application/x-www-form-urlencoded" format:
+
+    code
+            REQUIRED.  The authorization code generated by the
+            authorization server.  The authorization code MUST expire
+            shortly after it is issued to mitigate the risk of leaks.  A
+            maximum authorization code lifetime of 10 minutes is
+            RECOMMENDED.  The client MUST NOT use the authorization code
+            more than once.  If an authorization code is used more than
+            once, the authorization server MUST deny the request and SHOULD
+            revoke (when possible) all tokens previously issued based on
+            that authorization code.  The authorization code is bound to
+            the client identifier and redirection URI.
+    state
+            REQUIRED if the "state" parameter was present in the client
+            authorization request.  The exact value received from the
+            client.
+
+    For example, the authorization server redirects the user-agent by
+    sending the following HTTP response:
+
+    HTTP/1.1 302 Found
+    Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
+            &state=xyz
+
+    """
+    query = urlparse.urlparse(uri).query
+    params = dict(urlparse.parse_qsl(query))
+
+    if not u'code' in params:
+        raise KeyError("Missing code parameter in response.")
+
+    if state and params.get(u'state', None) != state:
+        raise ValueError("Mismatching or missing state in response.")
+
+    return params
+
+
+def parse_implicit_response(uri, state=None, scope=None):
+    """Parse the implicit token response URI into a dict.
+
+    If the resource owner grants the access request, the authorization
+    server issues an access token and delivers it to the client by adding
+    the following parameters to the fragment component of the redirection
+    URI using the "application/x-www-form-urlencoded" format:
+
+    access_token
+            REQUIRED.  The access token issued by the authorization server.
+    token_type
+            REQUIRED.  The type of the token issued as described in
+            Section 7.1.  Value is case insensitive.
+    expires_in
+            RECOMMENDED.  The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+    scope
+            OPTIONAL, if identical to the scope requested by the client,
+            otherwise REQUIRED.  The scope of the access token as described
+            by Section 3.3.
+    state
+            REQUIRED if the "state" parameter was present in the client
+            authorization request.  The exact value received from the
+            client.
+
+    HTTP/1.1 302 Found
+    Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
+            &state=xyz&token_type=example&expires_in=3600
+    """
+    fragment = urlparse.urlparse(uri).fragment
+    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
+    validate_token_parameters(params, scope)
+
+    if state and params.get(u'state', None) != state:
+        raise ValueError("Mismatching or missing state in params.")
+
+    return params
+
+
+def parse_token_response(body, scope=None):
+    """Parse the JSON token response body into a dict.
+
+    The authorization server issues an access token and optional refresh
+    token, and constructs the response by adding the following parameters
+    to the entity body of the HTTP response with a 200 (OK) status code:
+
+    access_token
+            REQUIRED.  The access token issued by the authorization server.
+    token_type
+            REQUIRED.  The type of the token issued as described in
+            `Section 7.1`_.  Value is case insensitive.
+    expires_in
+            RECOMMENDED.  The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+    refresh_token
+            OPTIONAL.  The refresh token which can be used to obtain new
+            access tokens using the same authorization grant as described
+            in `Section 6`_.
+    scope
+            OPTIONAL, if identical to the scope requested by the client,
+            otherwise REQUIRED.  The scope of the access token as described
+            by `Section 3.3`_.
+
+    The parameters are included in the entity body of the HTTP response
+    using the "application/json" media type as defined by [`RFC4627`_].  The
+    parameters are serialized into a JSON structure by adding each
+    parameter at the highest structure level.  Parameter names and string
+    values are included as JSON strings.  Numerical values are included
+    as JSON numbers.  The order of parameters does not matter and can
+    vary.
+
+    For example:
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json;charset=UTF-8
+        Cache-Control: no-store
+        Pragma: no-cache
+
+        {
+        "access_token":"2YotnFZFEjr1zCsicMWpAA",
+        "token_type":"example",
+        "expires_in":3600,
+        "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
+        "example_parameter":"example_value"
+        }
+
+    .. _`Section 7.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-7.1
+    .. _`Section 6`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-6
+    .. _`Section 3.3`: http://tools.ietf.org/html/draft-ietf-oauth-v2-28#section-3.3
+    .. _`RFC4627`: http://tools.ietf.org/html/rfc4627
+    """
+    params = json.loads(body)
+    validate_token_parameters(params, scope)
+    return params
+
+
+def validate_token_parameters(params, scope=None):
+    """Ensures token precence, token type, expiration and scope in params."""
+
+    if not u'access_token' in params:
+        raise KeyError("Missing access token parameter.")
+
+    if not u'token_type' in params:
+        raise KeyError("Missing token type parameter.")
+
+    # If the issued access token scope is different from the one requested by
+    # the client, the authorization server MUST include the "scope" response
+    # parameter to inform the client of the actual scope granted.
+    # http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.3
+    new_scope = params.get(u'scope', None)
+    if scope and new_scope and scope != new_scope:
+        raise Warning("Scope has changed to %s." % new_scope)
diff --git a/requests/packages/oauthlib/oauth2/draft25/tokens.py b/requests/packages/oauthlib/oauth2/draft25/tokens.py
new file mode 100644 (file)
index 0000000..74491fb
--- /dev/null
@@ -0,0 +1,132 @@
+from __future__ import absolute_import
+"""
+oauthlib.oauth2.draft25.tokens
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module contains methods for adding two types of access tokens to requests.
+
+- Bearer http://tools.ietf.org/html/draft-ietf-oauth-saml2-bearer-08
+- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-00
+
+"""
+from binascii import b2a_base64
+import hashlib
+import hmac
+from urlparse import urlparse
+
+from oauthlib.common import add_params_to_uri, add_params_to_qs
+from . import utils
+
+
+def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None,
+        body=None, ext=u'', hash_algorithm=u'hmac-sha-1'):
+    """Add an `MAC Access Authentication`_ signature to headers.
+
+    Unlike OAuth 1, this HMAC signature does not require inclusion of the request
+    payload/body, neither does it use a combination of client_secret and
+    token_secret but rather a mac_key provided together with the access token.
+
+    Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
+    `extension algorithms`_ are not supported.
+
+    Example MAC Authorization header, linebreaks added for clarity
+
+    Authorization: MAC id="h480djs93hd8",
+                       nonce="1336363200:dj83hs9s",
+                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
+
+    .. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
+    .. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
+
+    :param uri: Request URI.
+    :param headers: Request headers as a dictionary.
+    :param http_method: HTTP Request method.
+    :param key: MAC given provided by token endpoint.
+    :param algorithm: HMAC algorithm provided by token endpoint.
+    :return: headers dictionary with the authorization field added.
+    """
+    http_method = http_method.upper()
+    host, port = utils.host_from_uri(uri)
+
+    if hash_algorithm.lower() == u'hmac-sha-1':
+        h = hashlib.sha1
+    else:
+        h = hashlib.sha256
+
+    nonce = nonce or u'{0}:{1}'.format(utils.generate_nonce(), utils.generate_timestamp())
+    sch, net, path, par, query, fra = urlparse(uri)
+
+    if query:
+        request_uri = path + u'?' + query
+    else:
+        request_uri = path
+
+    # Hash the body/payload
+    if body is not None:
+        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
+    else:
+        bodyhash = u''
+
+    # Create the normalized base string
+    base = []
+    base.append(nonce)
+    base.append(http_method.upper())
+    base.append(request_uri)
+    base.append(host)
+    base.append(port)
+    base.append(bodyhash)
+    base.append(ext)
+    base_string = '\n'.join(base) + u'\n'
+
+    # hmac struggles with unicode strings - http://bugs.python.org/issue5285
+    if isinstance(key, unicode):
+        key = key.encode('utf-8')
+    sign = hmac.new(key, base_string, h)
+    sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
+
+    header = []
+    header.append(u'MAC id="%s"' % token)
+    header.append(u'nonce="%s"' % nonce)
+    if bodyhash:
+        header.append(u'bodyhash="%s"' % bodyhash)
+    if ext:
+        header.append(u'ext="%s"' % ext)
+    header.append(u'mac="%s"' % sign)
+
+    headers = headers or {}
+    headers[u'Authorization'] = u', '.join(header)
+    return headers
+
+
+def prepare_bearer_uri(token, uri):
+    """Add a `Bearer Token`_ to the request URI.
+    Not recommended, use only if client can't use authorization header or body.
+
+    http://www.example.com/path?access_token=h480djs93hd8
+
+    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
+    """
+    return add_params_to_uri(uri, [((u'access_token', token))])
+
+
+def prepare_bearer_headers(token, headers=None):
+    """Add a `Bearer Token`_ to the request URI.
+    Recommended method of passing bearer tokens.
+
+    Authorization: Bearer h480djs93hd8
+
+    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
+    """
+    headers = headers or {}
+    headers[u'Authorization'] = u'Bearer %s' % token
+    return headers
+
+
+def prepare_bearer_body(token, body=u''):
+    """Add a `Bearer Token`_ to the request body.
+
+    access_token=h480djs93hd8
+
+    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
+    """
+    return add_params_to_qs(body, [((u'access_token', token))])
diff --git a/requests/packages/oauthlib/oauth2/draft25/utils.py b/requests/packages/oauthlib/oauth2/draft25/utils.py
new file mode 100644 (file)
index 0000000..75d5fcc
--- /dev/null
@@ -0,0 +1,39 @@
+"""
+oauthlib.utils
+~~~~~~~~~~~~~~
+
+This module contains utility methods used by various parts of the OAuth 2 spec.
+"""
+
+import urllib
+import urlparse
+
+
+def host_from_uri(uri):
+    """Extract hostname and port from URI.
+
+    Will use default port for HTTP and HTTPS if none is present in the URI.
+    """
+    default_ports = {
+        u'HTTP': u'80',
+        u'HTTPS': u'443',
+    }
+
+    sch, netloc, path, par, query, fra = urlparse.urlparse(uri)
+    if u':' in netloc:
+        netloc, port = netloc.split(u':', 1)
+    else:
+        port = default_ports.get(sch.upper())
+
+    return netloc, port
+
+
+def escape(u):
+    """Escape a string in an OAuth-compatible fashion.
+
+    TODO: verify whether this can in fact be used for OAuth 2
+
+    """
+    if not isinstance(u, unicode):
+        raise ValueError('Only unicode objects are escapable.')
+    return urllib.quote(u.encode('utf-8'), safe='~')
index d8bcceb450a84787263c5cfea62d2d29e84f6e17..1d0e62a31a6d5fc82d943c559a9f0263119f9ba0 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,6 @@ import os
 import sys
 
 import requests
-from requests.compat import is_py2
 
 try:
     from setuptools import setup
@@ -28,14 +27,11 @@ packages = [
     'requests.packages.chardet2',
     'requests.packages.urllib3',
     'requests.packages.urllib3.packages',
-    'requests.packages.urllib3.packages.ssl_match_hostname',
+    'requests.packages.urllib3.packages.ssl_match_hostname'
 ]
 
 requires = []
 
-if is_py2:
-    requires.append('oauthlib>=0.1.0,<0.2.0')
-
 setup(
     name='requests',
     version=requests.__version__,