18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen"""
28ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenThe MIT License
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
48ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenCopyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
68ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenPermission is hereby granted, free of charge, to any person obtaining a copy
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenof this software and associated documentation files (the "Software"), to deal
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenin the Software without restriction, including without limitation the rights
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsento use, copy, modify, merge, publish, distribute, sublicense, and/or sell
108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsencopies of the Software, and to permit persons to whom the Software is
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfurnished to do so, subject to the following conditions:
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
138ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenThe above copyright notice and this permission notice shall be included in
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenall copies or substantial portions of the Software.
158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
168ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
178ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
188ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
198ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
208ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
218ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
228ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenTHE SOFTWARE.
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen"""
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport urllib
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport time
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport random
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport urlparse
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport hmac
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport binascii
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport httplib2
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsentry:
348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    from urlparse import parse_qs, parse_qsl
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenexcept ImportError:
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    from cgi import parse_qs, parse_qsl
378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
398ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenVERSION = '1.0'  # Hi Blaine!
408ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenHTTP_METHOD = 'GET'
418ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenSIGNATURE_METHOD = 'PLAINTEXT'
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Error(RuntimeError):
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Generic exception class."""
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, message='OAuth error occurred.'):
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self._message = message
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @property
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def message(self):
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """A hack to get around the deprecation errors in 2.6."""
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self._message
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __str__(self):
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self._message
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass MissingSignature(Error):
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    pass
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef build_authenticate_header(realm=''):
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Optional WWW-Authenticate header (401 error)"""
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef build_xoauth_string(url, consumer, token=None):
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Build an XOAUTH string for use in SMTP/IMPA authentication."""
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    request = Request.from_consumer_and_token(consumer, token,
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        "GET", url)
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signing_method = SignatureMethod_HMAC_SHA1()
748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    request.sign_request(signing_method, consumer, token)
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    params = []
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    for k, v in sorted(request.iteritems()):
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if v is not None:
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            params.append('%s="%s"' % (k, escape(v)))
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return "%s %s %s" % ("GET", url, ','.join(params))
828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef escape(s):
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Escape a URL including any /."""
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return urllib.quote(s, safe='~')
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_timestamp():
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Get seconds since epoch (UTC)."""
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return int(time.time())
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_nonce(length=8):
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Generate pseudorandom number."""
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return ''.join([str(random.randint(0, 9)) for i in range(length)])
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_verifier(length=8):
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """Generate pseudorandom number."""
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return ''.join([str(random.randint(0, 9)) for i in range(length)])
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Consumer(object):
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """A consumer of OAuth-protected services.
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    The OAuth consumer is a "third-party" service that wants to access
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    protected resources from an OAuth service provider on behalf of an end
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    user. It's kind of the OAuth client.
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    Usually a consumer must be registered with the service provider by the
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    developer of the consumer software. As part of that process, the service
1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    provider gives the consumer a *key* and a *secret* with which the consumer
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    software can identify itself to the service. The consumer will include its
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    key in each request to identify itself, but will use its secret only when
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signing requests, to prove that the request is from that particular
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    registered consumer.
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    Once registered, the consumer can then use its consumer credentials to ask
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    the service provider for a request token, kicking off the OAuth
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    authorization process.
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """
1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    key = None
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    secret = None
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, key, secret):
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.key = key
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.secret = secret
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if self.key is None or self.secret is None:
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Key and secret must be set.")
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __str__(self):
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        data = {'oauth_consumer_key': self.key,
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_consumer_secret': self.secret}
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return urllib.urlencode(data)
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Token(object):
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """An OAuth credential used to request authorization or a protected
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    resource.
1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    Tokens in OAuth comprise a *key* and a *secret*. The key is included in
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    requests to identify the token being used, but the secret is used only in
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    the signature, to prove that the requester is who the server gave the
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    token to.
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    When first negotiating the authorization, the consumer asks for a *request
1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    token* that the live user authorizes with the service provider. The
1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    consumer then exchanges the request token for an *access token* that can
1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    be used to access protected resources.
1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """
1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    key = None
1578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    secret = None
1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    callback = None
1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    callback_confirmed = None
1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    verifier = None
1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, key, secret):
1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.key = key
1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.secret = secret
1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if self.key is None or self.secret is None:
1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Key and secret must be set.")
1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def set_callback(self, callback):
1708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.callback = callback
1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.callback_confirmed = 'true'
1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def set_verifier(self, verifier=None):
1748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if verifier is not None:
1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.verifier = verifier
1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        else:
1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.verifier = generate_verifier()
1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def get_callback_url(self):
1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if self.callback and self.verifier:
1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Append the oauth_verifier.
1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parts = urlparse.urlparse(self.callback)
1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            scheme, netloc, path, params, query, fragment = parts[:6]
1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if query:
1858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                query = '%s&oauth_verifier=%s' % (query, self.verifier)
1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            else:
1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                query = 'oauth_verifier=%s' % self.verifier
1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            return urlparse.urlunparse((scheme, netloc, path, params,
1898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                query, fragment))
1908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self.callback
1918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def to_string(self):
1938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Returns this token as a plain string, suitable for storage.
1948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        The resulting string includes the token's secret, so you should never
1968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        send or store this string where a third party can read it.
1978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """
1988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        data = {
2008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_token': self.key,
2018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_token_secret': self.secret,
2028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        }
2038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if self.callback_confirmed is not None:
2058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            data['oauth_callback_confirmed'] = self.callback_confirmed
2068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return urllib.urlencode(data)
2078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @staticmethod
2098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def from_string(s):
2108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Deserializes a token from a string like one returned by
2118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        `to_string()`."""
2128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not len(s):
2148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Invalid parameter string.")
2158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        params = parse_qs(s, keep_blank_values=False)
2178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not len(params):
2188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Invalid parameter string.")
2198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
2218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            key = params['oauth_token'][0]
2228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except Exception:
2238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("'oauth_token' not found in OAuth request.")
2248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
2268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            secret = params['oauth_token_secret'][0]
2278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except Exception:
2288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("'oauth_token_secret' not found in "
2298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                "OAuth request.")
2308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        token = Token(key, secret)
2328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
2338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            token.callback_confirmed = params['oauth_callback_confirmed'][0]
2348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except KeyError:
2358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            pass  # 1.0, no callback confirmed.
2368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return token
2378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __str__(self):
2398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self.to_string()
2408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef setter(attr):
2438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    name = attr.__name__
2448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def getter(self):
2468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
2478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            return self.__dict__[name]
2488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except KeyError:
2498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise AttributeError(name)
2508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def deleter(self):
2528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        del self.__dict__[name]
2538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return property(getter, attr, deleter)
2558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Request(dict):
2588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """The parameters and information for an HTTP request, suitable for
2608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    authorizing with OAuth credentials.
2618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    When a consumer wants to access a service's protected resources, it does
2638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    so using a signed HTTP request identifying itself (the consumer) with its
2648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    key, and providing an access token authorized by the end user to access
2658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    those resources.
2668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """
2688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    version = VERSION
2708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, method=HTTP_METHOD, url=None, parameters=None):
2728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.method = method
2738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.url = url
2748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if parameters is not None:
2758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.update(parameters)
2768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @setter
2788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def url(self, value):
2798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.__dict__['url'] = value
2808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if value is not None:
2818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            scheme, netloc, path, params, query, fragment = urlparse.urlparse(value)
2828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Exclude default port numbers.
2848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if scheme == 'http' and netloc[-3:] == ':80':
2858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                netloc = netloc[:-3]
2868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            elif scheme == 'https' and netloc[-4:] == ':443':
2878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                netloc = netloc[:-4]
2888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if scheme not in ('http', 'https'):
2898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                raise ValueError("Unsupported URL %s (%s)." % (value, scheme))
2908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Normalized URL excludes params, query, and fragment.
2928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None))
2938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        else:
2948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.normalized_url = None
2958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self.__dict__['url'] = None
2968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @setter
2988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def method(self, value):
2998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.__dict__['method'] = value.upper()
3008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _get_timestamp_nonce(self):
3028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self['oauth_timestamp'], self['oauth_nonce']
3038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def get_nonoauth_parameters(self):
3058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Get any non-OAuth parameters."""
3068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return dict([(k, v) for k, v in self.iteritems()
3078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    if not k.startswith('oauth_')])
3088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def to_header(self, realm=''):
3108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Serialize as a header for an HTTPAuth request."""
3118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        oauth_params = ((k, v) for k, v in self.items()
3128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                            if k.startswith('oauth_'))
3138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        stringy_params = ((k, escape(str(v))) for k, v in oauth_params)
3148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        header_params = ('%s="%s"' % (k, v) for k, v in stringy_params)
3158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        params_header = ', '.join(header_params)
3168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        auth_header = 'OAuth realm="%s"' % realm
3188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if params_header:
3198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            auth_header = "%s, %s" % (auth_header, params_header)
3208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return {'Authorization': auth_header}
3228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def to_postdata(self):
3248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Serialize as post data for a POST request."""
3258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # tell urlencode to deal with sequence values and map them correctly
3268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # to resulting querystring. for example self["k"] = ["v1", "v2"] will
3278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
3288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return urllib.urlencode(self, True).replace('+', '%20')
3298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def to_url(self):
3318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Serialize as a URL for a GET request."""
3328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        base_url = urlparse.urlparse(self.url)
3338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
3348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            query = base_url.query
3358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except AttributeError:
3368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # must be python <2.5
3378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            query = base_url[4]
3388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        query = parse_qs(query)
3398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        for k, v in self.items():
3408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            query.setdefault(k, []).append(v)
3418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
3438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            scheme = base_url.scheme
3448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            netloc = base_url.netloc
3458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            path = base_url.path
3468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            params = base_url.params
3478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            fragment = base_url.fragment
3488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except AttributeError:
3498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # must be python <2.5
3508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            scheme = base_url[0]
3518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            netloc = base_url[1]
3528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            path = base_url[2]
3538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            params = base_url[3]
3548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            fragment = base_url[5]
3558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        url = (scheme, netloc, path, params,
3578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen               urllib.urlencode(query, True), fragment)
3588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return urlparse.urlunparse(url)
3598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def get_parameter(self, parameter):
3618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        ret = self.get(parameter)
3628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if ret is None:
3638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise Error('Parameter not found: %s' % parameter)
3648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return ret
3668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def get_normalized_parameters(self):
3688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Return a string that contains the parameters that must be signed."""
3698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        items = []
3708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        for key, value in self.iteritems():
3718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if key == 'oauth_signature':
3728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                continue
3738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
3748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # so we unpack sequence values into multiple items for sorting.
3758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if hasattr(value, '__iter__'):
3768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                items.extend((key, item) for item in value)
3778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            else:
3788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                items.append((key, value))
3798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Include any query string parameters from the provided URL
3818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        query = urlparse.urlparse(self.url)[4]
3828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        url_items = self._split_url_string(query).items()
3848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        non_oauth_url_items = list([(k, v) for k, v in url_items  if not k.startswith('oauth_')])
3858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        items.extend(non_oauth_url_items)
3868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        encoded_str = urllib.urlencode(sorted(items))
3888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Encode signature parameters per Oauth Core 1.0 protocol
3898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # spec draft 7, section 3.6
3908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
3918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Spaces must be encoded with "%20" instead of "+"
3928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return encoded_str.replace('+', '%20').replace('%7E', '~')
3938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def sign_request(self, signature_method, consumer, token):
3958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Set the signature parameter to the result of sign."""
3968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if 'oauth_consumer_key' not in self:
3988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self['oauth_consumer_key'] = consumer.key
3998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if token and 'oauth_token' not in self:
4018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            self['oauth_token'] = token.key
4028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self['oauth_signature_method'] = signature_method.name
4048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self['oauth_signature'] = signature_method.sign(self, consumer, token)
4058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @classmethod
4078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def make_timestamp(cls):
4088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Get seconds since epoch (UTC)."""
4098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return str(int(time.time()))
4108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @classmethod
4128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def make_nonce(cls):
4138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Generate pseudorandom number."""
4148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return str(random.randint(0, 100000000))
4158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @classmethod
4178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def from_request(cls, http_method, http_url, headers=None, parameters=None,
4188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            query_string=None):
4198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Combines multiple parameter sources."""
4208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if parameters is None:
4218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters = {}
4228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Headers
4248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if headers and 'Authorization' in headers:
4258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            auth_header = headers['Authorization']
4268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Check that the authorization header is OAuth.
4278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if auth_header[:6] == 'OAuth ':
4288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                auth_header = auth_header[6:]
4298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                try:
4308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    # Get the parameters from the header.
4318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    header_params = cls._split_header(auth_header)
4328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    parameters.update(header_params)
4338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                except:
4348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    raise Error('Unable to parse OAuth parameters from '
4358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                        'Authorization header.')
4368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # GET or POST query string.
4388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if query_string:
4398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            query_params = cls._split_url_string(query_string)
4408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters.update(query_params)
4418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # URL parameters.
4438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        param_str = urlparse.urlparse(http_url)[4] # query
4448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        url_params = cls._split_url_string(param_str)
4458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parameters.update(url_params)
4468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if parameters:
4488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            return cls(http_method, http_url, parameters)
4498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return None
4518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @classmethod
4538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def from_consumer_and_token(cls, consumer, token=None,
4548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            http_method=HTTP_METHOD, http_url=None, parameters=None):
4558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not parameters:
4568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters = {}
4578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        defaults = {
4598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_consumer_key': consumer.key,
4608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_timestamp': cls.make_timestamp(),
4618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_nonce': cls.make_nonce(),
4628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            'oauth_version': cls.version,
4638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        }
4648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        defaults.update(parameters)
4668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parameters = defaults
4678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if token:
4698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters['oauth_token'] = token.key
4708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if token.verifier:
4718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                parameters['oauth_verifier'] = token.verifier
4728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return Request(http_method, http_url, parameters)
4748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @classmethod
4768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def from_token_and_callback(cls, token, callback=None,
4778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        http_method=HTTP_METHOD, http_url=None, parameters=None):
4788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not parameters:
4808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters = {}
4818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parameters['oauth_token'] = token.key
4838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if callback:
4858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters['oauth_callback'] = callback
4868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return cls(http_method, http_url, parameters)
4888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @staticmethod
4908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _split_header(header):
4918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Turn Authorization: header into parameters."""
4928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        params = {}
4938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parts = header.split(',')
4948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        for param in parts:
4958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Ignore realm parameter.
4968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if param.find('realm') > -1:
4978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                continue
4988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Remove whitespace.
4998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            param = param.strip()
5008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Split key-value.
5018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            param_parts = param.split('=', 1)
5028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Remove quotes and unescape the value.
5038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
5048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return params
5058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    @staticmethod
5078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _split_url_string(param_str):
5088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Turn URL string into parameters."""
5098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parameters = parse_qs(param_str, keep_blank_values=False)
5108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        for k, v in parameters.iteritems():
5118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters[k] = urllib.unquote(v[0])
5128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return parameters
5138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Client(httplib2.Http):
5168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """OAuthClient is a worker to attempt to execute a request."""
5178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, consumer, token=None, cache=None, timeout=None,
5198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        proxy_info=None):
5208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if consumer is not None and not isinstance(consumer, Consumer):
5228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Invalid consumer.")
5238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if token is not None and not isinstance(token, Token):
5258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Invalid token.")
5268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.consumer = consumer
5288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.token = token
5298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.method = SignatureMethod_HMAC_SHA1()
5308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        httplib2.Http.__init__(self, cache=cache, timeout=timeout,
5328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            proxy_info=proxy_info)
5338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def set_signature_method(self, method):
5358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not isinstance(method, SignatureMethod):
5368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Invalid signature method.")
5378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.method = method
5398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def request(self, uri, method="GET", body=None, headers=None,
5418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
5428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
5438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not isinstance(headers, dict):
5458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            headers = {}
5468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        is_multipart = method == 'POST' and headers.get('Content-Type',
5488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE
5498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if body and method == "POST" and not is_multipart:
5518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters = dict(parse_qsl(body))
5528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        else:
5538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters = None
5548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        req = Request.from_consumer_and_token(self.consumer,
5568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            token=self.token, http_method=method, http_url=uri,
5578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            parameters=parameters)
5588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        req.sign_request(self.method, self.consumer, self.token)
5608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if method == "POST":
5628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            headers['Content-Type'] = headers.get('Content-Type',
5638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                DEFAULT_CONTENT_TYPE)
5648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            if is_multipart:
5658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                headers.update(req.to_header())
5668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            else:
5678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                body = req.to_postdata()
5688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        elif method == "GET":
5698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            uri = req.to_url()
5708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        else:
5718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            headers.update(req.to_header())
5728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return httplib2.Http.request(self, uri, method=method, body=body,
5748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            headers=headers, redirections=redirections,
5758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            connection_type=connection_type)
5768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Server(object):
5798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """A skeletal implementation of a service provider, providing protected
5808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    resources to requests from authorized consumers.
5818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    This class implements the logic to check requests for authorization. You
5838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    can use it with your web server or web framework to protect certain
5848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    resources with OAuth.
5858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """
5868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    timestamp_threshold = 300 # In seconds, five minutes.
5888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    version = VERSION
5898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signature_methods = None
5908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def __init__(self, signature_methods=None):
5928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.signature_methods = signature_methods or {}
5938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def add_signature_method(self, signature_method):
5958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self.signature_methods[signature_method.name] = signature_method
5968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return self.signature_methods
5978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def verify_request(self, request, consumer, token):
5998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Verifies an api call and checks all the parameters."""
6008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        version = self._get_version(request)
6028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self._check_signature(request, consumer, token)
6038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        parameters = request.get_nonoauth_parameters()
6048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return parameters
6058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def build_authenticate_header(self, realm=''):
6078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Optional support for the authenticate header."""
6088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
6098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _get_version(self, request):
6118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Verify the correct version request for this server."""
6128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
6138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            version = request.get_parameter('oauth_version')
6148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except:
6158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            version = VERSION
6168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if version and version != self.version:
6188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise Error('OAuth version %s not supported.' % str(version))
6198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return version
6218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _get_signature_method(self, request):
6238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Figure out the signature with some defaults."""
6248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
6258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            signature_method = request.get_parameter('oauth_signature_method')
6268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except:
6278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            signature_method = SIGNATURE_METHOD
6288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
6308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            # Get the signature method object.
6318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            signature_method = self.signature_methods[signature_method]
6328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except:
6338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            signature_method_names = ', '.join(self.signature_methods.keys())
6348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
6358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return signature_method
6378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _get_verifier(self, request):
6398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return request.get_parameter('oauth_verifier')
6408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _check_signature(self, request, consumer, token):
6428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        timestamp, nonce = request._get_timestamp_nonce()
6438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        self._check_timestamp(timestamp)
6448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        signature_method = self._get_signature_method(request)
6458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
6478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            signature = request.get_parameter('oauth_signature')
6488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except:
6498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise MissingSignature('Missing oauth_signature.')
6508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Validate the signature.
6528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        valid = signature_method.check(request, consumer, token, signature)
6538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if not valid:
6558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            key, base = signature_method.signing_base(request, consumer, token)
6568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise Error('Invalid signature. Expected signature base '
6588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                'string: %s' % base)
6598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        built = signature_method.sign(request, consumer, token)
6618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def _check_timestamp(self, timestamp):
6638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Verify that timestamp is recentish."""
6648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        timestamp = int(timestamp)
6658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        now = int(time.time())
6668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        lapsed = now - timestamp
6678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if lapsed > self.timestamp_threshold:
6688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise Error('Expired timestamp: given %d and now %s has a '
6698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                'greater difference than threshold %d' % (timestamp, now,
6708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                    self.timestamp_threshold))
6718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod(object):
6748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """A way of signing requests.
6758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    The OAuth protocol lets consumers and service providers pick a way to sign
6778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    requests. This interface shows the methods expected by the other `oauth`
6788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    modules for signing requests. Subclass it and implement its methods to
6798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    provide a new way to sign requests.
6808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    """
6818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def signing_base(self, request, consumer, token):
6838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Calculates the string that needs to be signed.
6848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        This method returns a 2-tuple containing the starting key for the
6868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        signing and the message to be signed. The latter may be used in error
6878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        messages to help clients debug their software.
6888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """
6908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        raise NotImplementedError
6918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def sign(self, request, consumer, token):
6938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Returns the signature for the given request, based on the consumer
6948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        and token also provided.
6958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        You should use your implementation of `signing_base()` to build the
6978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        message to sign. Otherwise it may be less useful for debugging.
6988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
6998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """
7008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        raise NotImplementedError
7018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def check(self, request, consumer, token, signature):
7038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Returns whether the given signature is the correct signature for
7048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        the given consumer and token signing the given request."""
7058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        built = self.sign(request, consumer, token)
7068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return built == signature
7078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod_HMAC_SHA1(SignatureMethod):
7108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    name = 'HMAC-SHA1'
7118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def signing_base(self, request, consumer, token):
7138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if request.normalized_url is None:
7148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            raise ValueError("Base URL for request is not set.")
7158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        sig = (
7178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            escape(request.method),
7188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            escape(request.normalized_url),
7198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            escape(request.get_normalized_parameters()),
7208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        )
7218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        key = '%s&' % escape(consumer.secret)
7238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if token:
7248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            key += escape(token.secret)
7258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        raw = '&'.join(sig)
7268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return key, raw
7278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def sign(self, request, consumer, token):
7298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Builds the base signature string."""
7308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        key, raw = self.signing_base(request, consumer, token)
7318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # HMAC object.
7338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        try:
7348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            from hashlib import sha1 as sha
7358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        except ImportError:
7368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            import sha # Deprecated
7378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        hashed = hmac.new(key, raw, sha)
7398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        # Calculate the digest base 64.
7418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return binascii.b2a_base64(hashed.digest())[:-1]
7428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod_PLAINTEXT(SignatureMethod):
7458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    name = 'PLAINTEXT'
7478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def signing_base(self, request, consumer, token):
7498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        """Concatenates the consumer key and secret with the token's
7508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        secret."""
7518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        sig = '%s&' % escape(consumer.secret)
7528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if token:
7538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            sig = sig + escape(token.secret)
7548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return sig, sig
7558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
7568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    def sign(self, request, consumer, token):
7578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        key, raw = self.signing_base(request, consumer, token)
7588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return raw
759