15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The MIT License
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Permission is hereby granted, free of charge, to any person obtaining a copy
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)of this software and associated documentation files (the "Software"), to deal
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)in the Software without restriction, including without limitation the rights
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)copies of the Software, and to permit persons to whom the Software is
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)furnished to do so, subject to the following conditions:
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The above copyright notice and this permission notice shall be included in
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)all copies or substantial portions of the Software.
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)THE SOFTWARE.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import random
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import hmac
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import binascii
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import httplib2
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    from urlparse import parse_qs, parse_qsl
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    from cgi import parse_qs, parse_qsl
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)VERSION = '1.0'  # Hi Blaine!
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HTTP_METHOD = 'GET'
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SIGNATURE_METHOD = 'PLAINTEXT'
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Error(RuntimeError):
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Generic exception class."""
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, message='OAuth error occurred.'):
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._message = message
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @property
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def message(self):
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """A hack to get around the deprecation errors in 2.6."""
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self._message
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __str__(self):
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self._message
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MissingSignature(Error):
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def build_authenticate_header(realm=''):
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Optional WWW-Authenticate header (401 error)"""
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def build_xoauth_string(url, consumer, token=None):
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Build an XOAUTH string for use in SMTP/IMPA authentication."""
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    request = Request.from_consumer_and_token(consumer, token,
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "GET", url)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signing_method = SignatureMethod_HMAC_SHA1()
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    request.sign_request(signing_method, consumer, token)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    params = []
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in sorted(request.iteritems()):
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if v is not None:
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            params.append('%s="%s"' % (k, escape(v)))
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return "%s %s %s" % ("GET", url, ','.join(params))
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def escape(s):
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Escape a URL including any /."""
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return urllib.quote(s, safe='~')
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_timestamp():
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Get seconds since epoch (UTC)."""
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return int(time.time())
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_nonce(length=8):
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Generate pseudorandom number."""
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ''.join([str(random.randint(0, 9)) for i in range(length)])
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_verifier(length=8):
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Generate pseudorandom number."""
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ''.join([str(random.randint(0, 9)) for i in range(length)])
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Consumer(object):
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A consumer of OAuth-protected services.
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The OAuth consumer is a "third-party" service that wants to access
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    protected resources from an OAuth service provider on behalf of an end
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    user. It's kind of the OAuth client.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Usually a consumer must be registered with the service provider by the
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    developer of the consumer software. As part of that process, the service
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    provider gives the consumer a *key* and a *secret* with which the consumer
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    software can identify itself to the service. The consumer will include its
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key in each request to identify itself, but will use its secret only when
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signing requests, to prove that the request is from that particular
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    registered consumer.
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Once registered, the consumer can then use its consumer credentials to ask
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the service provider for a request token, kicking off the OAuth
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    authorization process.
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key = None
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    secret = None
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, key, secret):
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.key = key
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.secret = secret
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.key is None or self.secret is None:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Key and secret must be set.")
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __str__(self):
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        data = {'oauth_consumer_key': self.key,
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_consumer_secret': self.secret}
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return urllib.urlencode(data)
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Token(object):
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """An OAuth credential used to request authorization or a protected
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    resource.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Tokens in OAuth comprise a *key* and a *secret*. The key is included in
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    requests to identify the token being used, but the secret is used only in
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    the signature, to prove that the requester is who the server gave the
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    token to.
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    When first negotiating the authorization, the consumer asks for a *request
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    token* that the live user authorizes with the service provider. The
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    consumer then exchanges the request token for an *access token* that can
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    be used to access protected resources.
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key = None
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    secret = None
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    callback = None
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    callback_confirmed = None
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    verifier = None
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, key, secret):
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.key = key
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.secret = secret
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.key is None or self.secret is None:
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Key and secret must be set.")
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set_callback(self, callback):
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.callback = callback
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.callback_confirmed = 'true'
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set_verifier(self, verifier=None):
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if verifier is not None:
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.verifier = verifier
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.verifier = generate_verifier()
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_callback_url(self):
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.callback and self.verifier:
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Append the oauth_verifier.
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parts = urlparse.urlparse(self.callback)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            scheme, netloc, path, params, query, fragment = parts[:6]
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if query:
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                query = '%s&oauth_verifier=%s' % (query, self.verifier)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            else:
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                query = 'oauth_verifier=%s' % self.verifier
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return urlparse.urlunparse((scheme, netloc, path, params,
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                query, fragment))
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self.callback
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def to_string(self):
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Returns this token as a plain string, suitable for storage.
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        The resulting string includes the token's secret, so you should never
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        send or store this string where a third party can read it.
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        data = {
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_token': self.key,
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_token_secret': self.secret,
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.callback_confirmed is not None:
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            data['oauth_callback_confirmed'] = self.callback_confirmed
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return urllib.urlencode(data)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @staticmethod
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def from_string(s):
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Deserializes a token from a string like one returned by
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        `to_string()`."""
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not len(s):
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Invalid parameter string.")
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        params = parse_qs(s, keep_blank_values=False)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not len(params):
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Invalid parameter string.")
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            key = params['oauth_token'][0]
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except Exception:
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("'oauth_token' not found in OAuth request.")
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            secret = params['oauth_token_secret'][0]
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except Exception:
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("'oauth_token_secret' not found in "
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                "OAuth request.")
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        token = Token(key, secret)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            token.callback_confirmed = params['oauth_callback_confirmed'][0]
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except KeyError:
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            pass  # 1.0, no callback confirmed.
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return token
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __str__(self):
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self.to_string()
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def setter(attr):
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    name = attr.__name__
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def getter(self):
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return self.__dict__[name]
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except KeyError:
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise AttributeError(name)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def deleter(self):
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        del self.__dict__[name]
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return property(getter, attr, deleter)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Request(dict):
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """The parameters and information for an HTTP request, suitable for
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    authorizing with OAuth credentials.
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    When a consumer wants to access a service's protected resources, it does
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    so using a signed HTTP request identifying itself (the consumer) with its
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    key, and providing an access token authorized by the end user to access
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    those resources.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    version = VERSION
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, method=HTTP_METHOD, url=None, parameters=None):
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.method = method
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.url = url
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if parameters is not None:
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.update(parameters)
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @setter
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def url(self, value):
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.__dict__['url'] = value
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if value is not None:
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            scheme, netloc, path, params, query, fragment = urlparse.urlparse(value)
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Exclude default port numbers.
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if scheme == 'http' and netloc[-3:] == ':80':
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                netloc = netloc[:-3]
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            elif scheme == 'https' and netloc[-4:] == ':443':
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                netloc = netloc[:-4]
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if scheme not in ('http', 'https'):
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                raise ValueError("Unsupported URL %s (%s)." % (value, scheme))
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Normalized URL excludes params, query, and fragment.
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None))
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.normalized_url = None
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.__dict__['url'] = None
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @setter
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def method(self, value):
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.__dict__['method'] = value.upper()
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _get_timestamp_nonce(self):
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self['oauth_timestamp'], self['oauth_nonce']
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_nonoauth_parameters(self):
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Get any non-OAuth parameters."""
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return dict([(k, v) for k, v in self.iteritems()
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    if not k.startswith('oauth_')])
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def to_header(self, realm=''):
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Serialize as a header for an HTTPAuth request."""
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        oauth_params = ((k, v) for k, v in self.items()
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            if k.startswith('oauth_'))
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        stringy_params = ((k, escape(str(v))) for k, v in oauth_params)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        header_params = ('%s="%s"' % (k, v) for k, v in stringy_params)
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        params_header = ', '.join(header_params)
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        auth_header = 'OAuth realm="%s"' % realm
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if params_header:
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            auth_header = "%s, %s" % (auth_header, params_header)
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return {'Authorization': auth_header}
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def to_postdata(self):
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Serialize as post data for a POST request."""
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # tell urlencode to deal with sequence values and map them correctly
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # to resulting querystring. for example self["k"] = ["v1", "v2"] will
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return urllib.urlencode(self, True).replace('+', '%20')
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def to_url(self):
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Serialize as a URL for a GET request."""
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        base_url = urlparse.urlparse(self.url)
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            query = base_url.query
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except AttributeError:
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # must be python <2.5
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            query = base_url[4]
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        query = parse_qs(query)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for k, v in self.items():
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            query.setdefault(k, []).append(v)
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            scheme = base_url.scheme
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            netloc = base_url.netloc
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            path = base_url.path
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            params = base_url.params
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            fragment = base_url.fragment
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except AttributeError:
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # must be python <2.5
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            scheme = base_url[0]
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            netloc = base_url[1]
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            path = base_url[2]
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            params = base_url[3]
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            fragment = base_url[5]
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url = (scheme, netloc, path, params,
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               urllib.urlencode(query, True), fragment)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return urlparse.urlunparse(url)
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_parameter(self, parameter):
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ret = self.get(parameter)
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if ret is None:
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise Error('Parameter not found: %s' % parameter)
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return ret
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def get_normalized_parameters(self):
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Return a string that contains the parameters that must be signed."""
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        items = []
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for key, value in self.iteritems():
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if key == 'oauth_signature':
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                continue
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # so we unpack sequence values into multiple items for sorting.
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if hasattr(value, '__iter__'):
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                items.extend((key, item) for item in value)
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            else:
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                items.append((key, value))
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Include any query string parameters from the provided URL
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        query = urlparse.urlparse(self.url)[4]
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url_items = self._split_url_string(query).items()
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        non_oauth_url_items = list([(k, v) for k, v in url_items  if not k.startswith('oauth_')])
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        items.extend(non_oauth_url_items)
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        encoded_str = urllib.urlencode(sorted(items))
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Encode signature parameters per Oauth Core 1.0 protocol
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # spec draft 7, section 3.6
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Spaces must be encoded with "%20" instead of "+"
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return encoded_str.replace('+', '%20').replace('%7E', '~')
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def sign_request(self, signature_method, consumer, token):
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Set the signature parameter to the result of sign."""
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if 'oauth_consumer_key' not in self:
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self['oauth_consumer_key'] = consumer.key
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if token and 'oauth_token' not in self:
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self['oauth_token'] = token.key
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self['oauth_signature_method'] = signature_method.name
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self['oauth_signature'] = signature_method.sign(self, consumer, token)
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @classmethod
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def make_timestamp(cls):
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Get seconds since epoch (UTC)."""
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return str(int(time.time()))
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @classmethod
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def make_nonce(cls):
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Generate pseudorandom number."""
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return str(random.randint(0, 100000000))
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @classmethod
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def from_request(cls, http_method, http_url, headers=None, parameters=None,
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            query_string=None):
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Combines multiple parameter sources."""
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if parameters is None:
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters = {}
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Headers
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if headers and 'Authorization' in headers:
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            auth_header = headers['Authorization']
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Check that the authorization header is OAuth.
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if auth_header[:6] == 'OAuth ':
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                auth_header = auth_header[6:]
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                try:
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    # Get the parameters from the header.
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    header_params = cls._split_header(auth_header)
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    parameters.update(header_params)
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                except:
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    raise Error('Unable to parse OAuth parameters from '
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        'Authorization header.')
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # GET or POST query string.
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if query_string:
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            query_params = cls._split_url_string(query_string)
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters.update(query_params)
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # URL parameters.
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        param_str = urlparse.urlparse(http_url)[4] # query
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        url_params = cls._split_url_string(param_str)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parameters.update(url_params)
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if parameters:
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return cls(http_method, http_url, parameters)
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return None
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @classmethod
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def from_consumer_and_token(cls, consumer, token=None,
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            http_method=HTTP_METHOD, http_url=None, parameters=None):
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not parameters:
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters = {}
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        defaults = {
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_consumer_key': consumer.key,
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_timestamp': cls.make_timestamp(),
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_nonce': cls.make_nonce(),
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'oauth_version': cls.version,
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        defaults.update(parameters)
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parameters = defaults
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if token:
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters['oauth_token'] = token.key
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if token.verifier:
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                parameters['oauth_verifier'] = token.verifier
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return Request(http_method, http_url, parameters)
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @classmethod
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def from_token_and_callback(cls, token, callback=None,
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        http_method=HTTP_METHOD, http_url=None, parameters=None):
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not parameters:
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters = {}
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parameters['oauth_token'] = token.key
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if callback:
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters['oauth_callback'] = callback
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return cls(http_method, http_url, parameters)
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @staticmethod
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _split_header(header):
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Turn Authorization: header into parameters."""
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        params = {}
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parts = header.split(',')
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for param in parts:
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Ignore realm parameter.
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if param.find('realm') > -1:
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                continue
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Remove whitespace.
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            param = param.strip()
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Split key-value.
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            param_parts = param.split('=', 1)
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Remove quotes and unescape the value.
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return params
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    @staticmethod
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _split_url_string(param_str):
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Turn URL string into parameters."""
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parameters = parse_qs(param_str, keep_blank_values=False)
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for k, v in parameters.iteritems():
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters[k] = urllib.unquote(v[0])
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return parameters
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Client(httplib2.Http):
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """OAuthClient is a worker to attempt to execute a request."""
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, consumer, token=None, cache=None, timeout=None,
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        proxy_info=None):
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if consumer is not None and not isinstance(consumer, Consumer):
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Invalid consumer.")
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if token is not None and not isinstance(token, Token):
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Invalid token.")
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.consumer = consumer
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.token = token
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.method = SignatureMethod_HMAC_SHA1()
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        httplib2.Http.__init__(self, cache=cache, timeout=timeout,
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            proxy_info=proxy_info)
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set_signature_method(self, method):
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not isinstance(method, SignatureMethod):
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Invalid signature method.")
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.method = method
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def request(self, uri, method="GET", body=None, headers=None,
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not isinstance(headers, dict):
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            headers = {}
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        is_multipart = method == 'POST' and headers.get('Content-Type',
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if body and method == "POST" and not is_multipart:
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters = dict(parse_qsl(body))
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters = None
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        req = Request.from_consumer_and_token(self.consumer,
5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            token=self.token, http_method=method, http_url=uri,
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parameters=parameters)
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        req.sign_request(self.method, self.consumer, self.token)
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if method == "POST":
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            headers['Content-Type'] = headers.get('Content-Type',
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                DEFAULT_CONTENT_TYPE)
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if is_multipart:
5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                headers.update(req.to_header())
5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            else:
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                body = req.to_postdata()
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif method == "GET":
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            uri = req.to_url()
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            headers.update(req.to_header())
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return httplib2.Http.request(self, uri, method=method, body=body,
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            headers=headers, redirections=redirections,
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            connection_type=connection_type)
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Server(object):
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A skeletal implementation of a service provider, providing protected
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    resources to requests from authorized consumers.
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This class implements the logic to check requests for authorization. You
5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    can use it with your web server or web framework to protect certain
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    resources with OAuth.
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    timestamp_threshold = 300 # In seconds, five minutes.
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    version = VERSION
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signature_methods = None
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, signature_methods=None):
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.signature_methods = signature_methods or {}
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def add_signature_method(self, signature_method):
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.signature_methods[signature_method.name] = signature_method
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self.signature_methods
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def verify_request(self, request, consumer, token):
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Verifies an api call and checks all the parameters."""
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        version = self._get_version(request)
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._check_signature(request, consumer, token)
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parameters = request.get_nonoauth_parameters()
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return parameters
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def build_authenticate_header(self, realm=''):
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Optional support for the authenticate header."""
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _get_version(self, request):
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Verify the correct version request for this server."""
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            version = request.get_parameter('oauth_version')
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except:
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            version = VERSION
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if version and version != self.version:
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise Error('OAuth version %s not supported.' % str(version))
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return version
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _get_signature_method(self, request):
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Figure out the signature with some defaults."""
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            signature_method = request.get_parameter('oauth_signature_method')
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except:
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            signature_method = SIGNATURE_METHOD
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # Get the signature method object.
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            signature_method = self.signature_methods[signature_method]
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except:
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            signature_method_names = ', '.join(self.signature_methods.keys())
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return signature_method
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _get_verifier(self, request):
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return request.get_parameter('oauth_verifier')
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _check_signature(self, request, consumer, token):
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        timestamp, nonce = request._get_timestamp_nonce()
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._check_timestamp(timestamp)
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        signature_method = self._get_signature_method(request)
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            signature = request.get_parameter('oauth_signature')
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except:
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise MissingSignature('Missing oauth_signature.')
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Validate the signature.
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        valid = signature_method.check(request, consumer, token, signature)
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not valid:
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            key, base = signature_method.signing_base(request, consumer, token)
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise Error('Invalid signature. Expected signature base '
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                'string: %s' % base)
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        built = signature_method.sign(request, consumer, token)
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def _check_timestamp(self, timestamp):
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Verify that timestamp is recentish."""
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        timestamp = int(timestamp)
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        now = int(time.time())
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lapsed = now - timestamp
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if lapsed > self.timestamp_threshold:
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise Error('Expired timestamp: given %d and now %s has a '
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                'greater difference than threshold %d' % (timestamp, now,
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    self.timestamp_threshold))
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod(object):
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """A way of signing requests.
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The OAuth protocol lets consumers and service providers pick a way to sign
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    requests. This interface shows the methods expected by the other `oauth`
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    modules for signing requests. Subclass it and implement its methods to
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    provide a new way to sign requests.
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def signing_base(self, request, consumer, token):
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Calculates the string that needs to be signed.
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        This method returns a 2-tuple containing the starting key for the
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        signing and the message to be signed. The latter may be used in error
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        messages to help clients debug their software.
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise NotImplementedError
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def sign(self, request, consumer, token):
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Returns the signature for the given request, based on the consumer
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        and token also provided.
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        You should use your implementation of `signing_base()` to build the
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        message to sign. Otherwise it may be less useful for debugging.
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise NotImplementedError
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def check(self, request, consumer, token, signature):
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Returns whether the given signature is the correct signature for
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        the given consumer and token signing the given request."""
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        built = self.sign(request, consumer, token)
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return built == signature
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod_HMAC_SHA1(SignatureMethod):
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    name = 'HMAC-SHA1'
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def signing_base(self, request, consumer, token):
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if request.normalized_url is None:
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ValueError("Base URL for request is not set.")
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        sig = (
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            escape(request.method),
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            escape(request.normalized_url),
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            escape(request.get_normalized_parameters()),
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        )
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        key = '%s&' % escape(consumer.secret)
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if token:
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            key += escape(token.secret)
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raw = '&'.join(sig)
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return key, raw
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def sign(self, request, consumer, token):
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Builds the base signature string."""
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        key, raw = self.signing_base(request, consumer, token)
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # HMAC object.
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            from hashlib import sha1 as sha
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except ImportError:
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            import sha # Deprecated
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        hashed = hmac.new(key, raw, sha)
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Calculate the digest base 64.
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return binascii.b2a_base64(hashed.digest())[:-1]
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod_PLAINTEXT(SignatureMethod):
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    name = 'PLAINTEXT'
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def signing_base(self, request, consumer, token):
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """Concatenates the consumer key and secret with the token's
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        secret."""
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        sig = '%s&' % escape(consumer.secret)
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if token:
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            sig = sig + escape(token.secret)
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return sig, sig
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def sign(self, request, consumer, token):
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        key, raw = self.signing_base(request, consumer, token)
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return raw
759