Commits

Jesper Nøhr committed a6affa7 Merge

merging with the oauth2 fork

Comments (0)

Files changed (24)

piston/authentication.py

-import binascii
-
-import oauth
-from django.http import HttpResponse, HttpResponseRedirect
-from django.contrib.auth.models import User, AnonymousUser
-from django.contrib.auth.decorators import login_required
-from django.template import loader
-from django.contrib.auth import authenticate
-from django.conf import settings
-from django.core.urlresolvers import get_callable
-from django.core.exceptions import ImproperlyConfigured
-from django.shortcuts import render_to_response
-from django.template import RequestContext
-
-from piston import forms
-
-class NoAuthentication(object):
-    """
-    Authentication handler that always returns
-    True, so no authentication is needed, nor
-    initiated (`challenge` is missing.)
-    """
-    def is_authenticated(self, request):
-        return True
-
-class HttpBasicAuthentication(object):
-    """
-    Basic HTTP authenticater. Synopsis:
-    
-    Authentication handlers must implement two methods:
-     - `is_authenticated`: Will be called when checking for
-        authentication. Receives a `request` object, please
-        set your `User` object on `request.user`, otherwise
-        return False (or something that evaluates to False.)
-     - `challenge`: In cases where `is_authenticated` returns
-        False, the result of this method will be returned.
-        This will usually be a `HttpResponse` object with
-        some kind of challenge headers and 401 code on it.
-    """
-    def __init__(self, auth_func=authenticate, realm='API'):
-        self.auth_func = auth_func
-        self.realm = realm
-
-    def is_authenticated(self, request):
-        auth_string = request.META.get('HTTP_AUTHORIZATION', None)
-
-        if not auth_string:
-            return False
-            
-        try:
-            (authmeth, auth) = auth_string.split(" ", 1)
-
-            if not authmeth.lower() == 'basic':
-                return False
-
-            auth = auth.strip().decode('base64')
-            (username, password) = auth.split(':', 1)
-        except (ValueError, binascii.Error):
-            return False
-        
-        request.user = self.auth_func(username=username, password=password) \
-            or AnonymousUser()
-                
-        return not request.user in (False, None, AnonymousUser())
-        
-    def challenge(self):
-        resp = HttpResponse("Authorization Required")
-        resp['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
-        resp.status_code = 401
-        return resp
-
-    def __repr__(self):
-        return u'<HTTPBasic: realm=%s>' % self.realm
-
-class HttpBasicSimple(HttpBasicAuthentication):
-    def __init__(self, realm, username, password):
-        self.user = User.objects.get(username=username)
-        self.password = password
-
-        super(HttpBasicSimple, self).__init__(auth_func=self.hash, realm=realm)
-    
-    def hash(self, username, password):
-        if username == self.user.username and password == self.password:
-            return self.user
-
-def load_data_store():
-    '''Load data store for OAuth Consumers, Tokens, Nonces and Resources
-    '''
-    path = getattr(settings, 'OAUTH_DATA_STORE', 'piston.store.DataStore')
-
-    # stolen from django.contrib.auth.load_backend
-    i = path.rfind('.')
-    module, attr = path[:i], path[i+1:]
-
-    try:
-        mod = __import__(module, {}, {}, attr)
-    except ImportError, e:
-        raise ImproperlyConfigured, 'Error importing OAuth data store %s: "%s"' % (module, e)
-
-    try:
-        cls = getattr(mod, attr)
-    except AttributeError:
-        raise ImproperlyConfigured, 'Module %s does not define a "%s" OAuth data store' % (module, attr)
-
-    return cls
-
-# Set the datastore here.
-oauth_datastore = load_data_store()
-
-def initialize_server_request(request):
-    """
-    Shortcut for initialization.
-    """
-    if request.method == "POST": #and \
-#       request.META['CONTENT_TYPE'] == "application/x-www-form-urlencoded":
-        params = dict(request.REQUEST.items())
-    else:
-        params = { }
-
-    # Seems that we want to put HTTP_AUTHORIZATION into 'Authorization'
-    # for oauth.py to understand. Lovely.
-    request.META['Authorization'] = request.META.get('HTTP_AUTHORIZATION', '')
-
-    oauth_request = oauth.OAuthRequest.from_request(
-        request.method, request.build_absolute_uri(), 
-        headers=request.META, parameters=params,
-        query_string=request.environ.get('QUERY_STRING', ''))
-        
-    if oauth_request:
-        oauth_server = oauth.OAuthServer(oauth_datastore(oauth_request))
-        oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
-        oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
-    else:
-        oauth_server = None
-        
-    return oauth_server, oauth_request
-
-def send_oauth_error(err=None):
-    """
-    Shortcut for sending an error.
-    """
-    response = HttpResponse(err.message.encode('utf-8'))
-    response.status_code = 401
-
-    realm = 'OAuth'
-    header = oauth.build_authenticate_header(realm=realm)
-
-    for k, v in header.iteritems():
-        response[k] = v
-
-    return response
-
-def oauth_request_token(request):
-    oauth_server, oauth_request = initialize_server_request(request)
-    
-    if oauth_server is None:
-        return INVALID_PARAMS_RESPONSE
-    try:
-        token = oauth_server.fetch_request_token(oauth_request)
-
-        response = HttpResponse(token.to_string())
-    except oauth.OAuthError, err:
-        response = send_oauth_error(err)
-
-    return response
-
-def oauth_auth_view(request, token, callback, params):
-    form = forms.OAuthAuthenticationForm(initial={
-        'oauth_token': token.key,
-        'oauth_callback': token.get_callback_url() or callback,
-      })
-
-    return render_to_response('piston/authorize_token.html',
-            { 'form': form }, RequestContext(request))
-
-@login_required
-def oauth_user_auth(request):
-    oauth_server, oauth_request = initialize_server_request(request)
-    
-    if oauth_request is None:
-        return INVALID_PARAMS_RESPONSE
-        
-    try:
-        token = oauth_server.fetch_request_token(oauth_request)
-    except oauth.OAuthError, err:
-        return send_oauth_error(err)
-        
-    try:
-        callback = oauth_server.get_callback(oauth_request)
-    except:
-        callback = None
-    
-    if request.method == "GET":
-        params = oauth_request.get_normalized_parameters()
-
-        oauth_view = getattr(settings, 'OAUTH_AUTH_VIEW', None)
-        if oauth_view is None:
-            return oauth_auth_view(request, token, callback, params)
-        else:
-            return get_callable(oauth_view)(request, token, callback, params)
-    elif request.method == "POST":
-        try:
-            form = forms.OAuthAuthenticationForm(request.POST)
-            if form.is_valid():
-                token = oauth_server.authorize_token(token, request.user)
-                args = '?'+token.to_string(only_key=True)
-            else:
-                args = '?error=%s' % 'Access not granted by user.'
-                print "FORM ERROR", form.errors
-            
-            if not callback:
-                callback = getattr(settings, 'OAUTH_CALLBACK_VIEW')
-                return get_callable(callback)(request, token)
-                
-            response = HttpResponseRedirect(callback+args)
-                
-        except oauth.OAuthError, err:
-            response = send_oauth_error(err)
-    else:
-        response = HttpResponse('Action not allowed.')
-            
-    return response
-
-def oauth_access_token(request):
-    oauth_server, oauth_request = initialize_server_request(request)
-    
-    if oauth_request is None:
-        return INVALID_PARAMS_RESPONSE
-        
-    try:
-        token = oauth_server.fetch_access_token(oauth_request)
-        return HttpResponse(token.to_string())
-    except oauth.OAuthError, err:
-        return send_oauth_error(err)
-
-INVALID_PARAMS_RESPONSE = send_oauth_error(oauth.OAuthError('Invalid request parameters.'))
-                
-class OAuthAuthentication(object):
-    """
-    OAuth authentication. Based on work by Leah Culver.
-    """
-    def __init__(self, realm='API'):
-        self.realm = realm
-        self.builder = oauth.build_authenticate_header
-    
-    def is_authenticated(self, request):
-        """
-        Checks whether a means of specifying authentication
-        is provided, and if so, if it is a valid token.
-        
-        Read the documentation on `HttpBasicAuthentication`
-        for more information about what goes on here.
-        """
-        if self.is_valid_request(request):
-            try:
-                consumer, token, parameters = self.validate_token(request)
-            except oauth.OAuthError, err:
-                print send_oauth_error(err)
-                return False
-
-            if consumer and token:
-                request.user = token.user
-                request.consumer = consumer
-                request.throttle_extra = token.consumer.id
-                return True
-            
-        return False
-        
-    def challenge(self):
-        """
-        Returns a 401 response with a small bit on
-        what OAuth is, and where to learn more about it.
-        
-        When this was written, browsers did not understand
-        OAuth authentication on the browser side, and hence
-        the helpful template we render. Maybe some day in the
-        future, browsers will take care of this stuff for us
-        and understand the 401 with the realm we give it.
-        """
-        response = HttpResponse()
-        response.status_code = 401
-        realm = 'API'
-
-        for k, v in self.builder(realm=realm).iteritems():
-            response[k] = v
-
-        tmpl = loader.render_to_string('oauth/challenge.html',
-            { 'MEDIA_URL': settings.MEDIA_URL })
-
-        response.content = tmpl
-
-        return response
-        
-    @staticmethod
-    def is_valid_request(request):
-        """
-        Checks whether the required parameters are either in
-        the http-authorization header sent by some clients,
-        which is by the way the preferred method according to
-        OAuth spec, but otherwise fall back to `GET` and `POST`.
-        """
-        must_have = [ 'oauth_'+s for s in [
-            'consumer_key', 'token', 'signature',
-            'signature_method', 'timestamp', 'nonce' ] ]
-        
-        is_in = lambda l: all([ (p in l) for p in must_have ])
-
-        auth_params = request.META.get("HTTP_AUTHORIZATION", "")
-        req_params = request.REQUEST
-             
-        return is_in(auth_params) or is_in(req_params)
-        
-    @staticmethod
-    def validate_token(request, check_timestamp=True, check_nonce=True):
-        oauth_server, oauth_request = initialize_server_request(request)
-        return oauth_server.verify_request(oauth_request)
-

piston/authentication/__init__.py

+from piston.authentication.basic import HttpBasicAuthentication, HttpBasicSimple
+
+class NoAuthentication(object):
+    """
+    Authentication handler that always returns
+    True, so no authentication is needed, nor
+    initiated (`challenge` is missing.)
+    """
+    def is_authenticated(self, request):
+        return True

piston/authentication/basic.py

+import binascii
+
+from django.contrib.auth import authenticate
+from django.contrib.auth.models import User, AnonymousUser
+from django.http import HttpResponse
+
+
+class HttpBasicAuthentication(object):
+    """
+    Basic HTTP authenticater. Synopsis:
+
+    Authentication handlers must implement two methods:
+     - `is_authenticated`: Will be called when checking for
+        authentication. Receives a `request` object, please
+        set your `User` object on `request.user`, otherwise
+        return False (or something that evaluates to False.)
+     - `challenge`: In cases where `is_authenticated` returns
+        False, the result of this method will be returned.
+        This will usually be a `HttpResponse` object with
+        some kind of challenge headers and 401 code on it.
+    """
+    def __init__(self, auth_func=authenticate, realm='API'):
+        self.auth_func = auth_func
+        self.realm = realm
+
+    def is_authenticated(self, request):
+        auth_string = request.META.get('HTTP_AUTHORIZATION', None)
+
+        if not auth_string:
+            return False
+
+        try:
+            (authmeth, auth) = auth_string.split(" ", 1)
+
+            if not authmeth.lower() == 'basic':
+                return False
+
+            auth = auth.strip().decode('base64')
+            (username, password) = auth.split(':', 1)
+        except (ValueError, binascii.Error):
+            return False
+
+        request.user = self.auth_func(username=username, password=password) \
+            or AnonymousUser()
+
+        return not request.user in (False, None, AnonymousUser())
+
+    def challenge(self):
+        resp = HttpResponse("Authorization Required")
+        resp['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
+        resp.status_code = 401
+        return resp
+
+    def __repr__(self):
+        return u'<HTTPBasic: realm=%s>' % self.realm
+
+
+class HttpBasicSimple(HttpBasicAuthentication):
+    def __init__(self, realm, username, password):
+        self.user = User.objects.get(username=username)
+        self.password = password
+
+        super(HttpBasicSimple, self).__init__(auth_func=self.hash, realm=realm)
+
+    def hash(self, username, password):
+        if username == self.user.username and password == self.password:
+            return self.user

piston/authentication/oauth/__init__.py

+import oauth2 as oauth
+from django.conf import settings
+from django.http import HttpResponse
+from django.template import loader
+
+from piston.authentication.oauth.store import store, InvalidConsumerError, InvalidTokenError
+from piston.authentication.oauth.utils import get_oauth_request, verify_oauth_request, require_params
+
+
+class OAuthAuthentication(object):
+    def __init__(self, realm='API', two_legged=False):
+        self.realm = realm
+        self.two_legged = two_legged
+
+    def is_authenticated(self, request):
+        oauth_request = get_oauth_request(request)
+
+        if self.two_legged:
+            return self._authenticate_two_legged(request, oauth_request)
+        else:
+            return self._authenticate_three_legged(request, oauth_request)
+
+    def _authenticate_two_legged(self, request, oauth_request):
+        missing_params = require_params(oauth_request)
+        if missing_params is not None:
+            return False
+
+        try:
+            consumer = store.get_consumer(request, oauth_request, oauth_request['oauth_consumer_key'])
+        except InvalidConsumerError:
+            return False
+
+        if not verify_oauth_request(request, oauth_request, consumer):
+            return False
+
+        request.user = store.get_user_for_consumer(request, oauth_request, consumer)
+        request.consumer = consumer
+        request.throttle_extra = consumer.key
+
+        return True
+        
+    def _authenticate_three_legged(self, request, oauth_request):
+        missing_params = require_params(oauth_request, ('oauth_token',))
+        if missing_params is not None:
+            return False
+
+        try:
+            consumer = store.get_consumer(request, oauth_request, oauth_request['oauth_consumer_key'])
+            access_token = store.get_access_token(request, oauth_request, consumer, oauth_request['oauth_token'])
+        except (InvalidConsumerError, InvalidTokenError):
+            return False
+
+        if not verify_oauth_request(request, oauth_request, consumer, access_token):
+            return False
+
+        request.user = store.get_user_for_access_token(request, oauth_request, access_token)
+        request.consumer = store.get_consumer_for_access_token(request, oauth_request, access_token)
+        request.throttle_extra = request.consumer.key
+
+        return True
+
+    def challenge(self):
+        """
+        Returns a 401 response with a small bit on
+        what OAuth is, and where to learn more about it.
+
+        When this was written, browsers did not understand
+        OAuth authentication on the browser side, and hence
+        the helpful template we render. Maybe some day in the
+        future, browsers will take care of this stuff for us
+        and understand the 401 with the realm we give it.
+        """
+        response = HttpResponse()
+        response.status_code = 401
+
+        for k, v in oauth.build_authenticate_header(realm=self.realm).iteritems():
+            response[k] = v
+
+        tmpl = loader.render_to_string('piston/oauth/challenge.html',
+            { 'MEDIA_URL': settings.MEDIA_URL })
+
+        response.content = tmpl
+
+        return response

piston/authentication/oauth/forms.py

+from django import forms
+
+
+class AuthorizeRequestTokenForm(forms.Form):
+    oauth_token = forms.CharField(widget=forms.HiddenInput)
+    authorize_access = forms.BooleanField(required=True)

piston/authentication/oauth/store/__init__.py

+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils import importlib
+
+
+class Error(Exception):
+    """Base class for Store exceptions."""
+
+
+class InvalidConsumerError(Error):
+    """Invalid consumer."""
+
+
+class InvalidTokenError(Error):
+    """Invalid token."""
+
+
+class Store(object):
+    """
+    The Store class is the backbone of piston's OAuth implementation. It is used
+    by the views and the authentication backend to get consumers and tokens, and
+    to create tokens. The following terms are used in the documentation of the
+    API:
+
+    Consumer:
+        A class defining at minimum `key` and `secret` attributes. Both of these
+        attributes must be either str or unicode.
+
+    Token:
+        A class defining at minimum `key` and `secret` attributes. Both of these
+        attributes must be either str or unicode.
+
+    User:
+        A `django.contrib.auth.models.User` instance.
+
+    Any API that takes a consumer or token will be passed a Consumer or Token
+    instance it itself returned at an earlier stage from one of the methods that
+    take a key and return a Consumer or Token. This means if your store
+    implementation uses tokens that keep a reference to its Consumer on the
+    Token itself, `get_consumer_for_request_token` can simply return
+    `request_token.consumer`.
+    """
+    def get_consumer(self, request, oauth_request, consumer_key):
+        """
+        Return the Consumer for `consumer_key` or raise `InvalidConsumerError`.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer_key`: The consumer key.
+        """
+        raise NotImplementedError
+
+    def get_consumer_for_request_token(self, request, oauth_request, request_token):
+        """
+        Return the Consumer associated with the `request_token` Token.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `request_token`: The request token to get the consumer for.
+        """
+        raise NotImplementedError
+
+    def get_consumer_for_access_token(self, request, oauth_request, access_token):
+        """
+        Return the Consumer associated with the `access_token` Token.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `access_token`: The access Token to get the consumer for.
+        """
+        raise NotImplementedError
+
+    def create_request_token(self, request, oauth_request, consumer, callback):
+        """
+        Generate and return a Token.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        """
+        raise NotImplementedError
+
+    def get_request_token(self, request, oauth_request, request_token_key):
+        """
+        Return the Token for `request_token_key` or raise `InvalidTokenError`.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        `request_token_key`: The request token key.
+        """
+        raise NotImplementedError
+
+    def authorize_request_token(self, request, oauth_request, request_token):
+        """
+        Authorize the `request_token` Token and return it.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `request_token`: The request token to authorize.
+        """
+        raise NotImplementedError
+
+    def create_access_token(self, request, oauth_request, consumer, request_token):
+        """
+        Generate and return a Token.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        `request_token`: The Token used to make the request.
+        """
+        raise NotImplementedError
+
+    def get_access_token(self, request, oauth_request, consumer, access_token_key):
+        """
+        Return the Token for `access_token_key` or raise `InvalidTokenError`.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        `access_token_key`: The token key used to make the request.
+        """
+        raise NotImplementedError
+
+    def get_user_for_access_token(self, request, oauth_request, access_token):
+        """
+        Return the associated User for `access_token`.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        `access_token`: The Token used to make the request.
+        """
+        raise NotImplementedError
+
+    def get_user_for_consumer(self, request, oauth_request, consumer):
+        """
+        Return the associated User for `consumer`.
+        
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `consumer`: The Consumer that made the request.
+        """
+        raise NotImplementedError
+
+    def check_nonce(self, request, oauth_request, nonce):
+        """
+        Return `True` if the nonce has not yet been used, `False` otherwise.
+
+        `request`: The Django request object.
+        `oauth_request`: The `oauth2.Request` object.
+        `nonce`: The nonce to check.
+        """
+        raise NotImplementedError
+
+
+def get_store(path='piston.authentication.oauth.store.db.ModelStore'):
+    """
+    Load the piston oauth store. Should not be called directly unless testing.
+    """
+    path = getattr(settings, 'PISTON_OAUTH_STORE', path)
+
+    try:
+        module, attr = path.rsplit('.', 1)
+        store_class = getattr(importlib.import_module(module), attr)
+    except ValueError:
+        raise ImproperlyConfigured('Invalid piston oauth store string: "%s"' % path)
+    except ImportError, e:
+        raise ImproperlyConfigured('Error loading piston oauth store module "%s": "%s"' % (module, e))
+    except AttributeError:
+        raise ImproperlyConfigured('Module "%s" does not define a piston oauth store named "%s"' % (module, attr))
+
+    return store_class()
+
+
+store = get_store()

piston/authentication/oauth/store/db.py

+import oauth2 as oauth
+
+from piston.authentication.oauth.store import InvalidConsumerError, InvalidTokenError, Store
+from piston.models import Nonce, Token, Consumer, VERIFIER_SIZE
+
+
+class ModelStore(Store):
+    """
+    Store implementation using the Django models defined in `piston.models`.
+    """
+    def get_consumer(self, request, oauth_request, consumer_key):
+        try:
+            return Consumer.objects.get(key=consumer_key)
+        except Consumer.DoesNotExist:
+            raise InvalidConsumerError()
+
+    def get_consumer_for_request_token(self, request, oauth_request, request_token):
+        return request_token.consumer
+
+    def get_consumer_for_access_token(self, request, oauth_request, access_token):
+        return access_token.consumer
+
+    def create_request_token(self, request, oauth_request, consumer, callback):
+        token = Token.objects.create_token(
+            token_type=Token.REQUEST,
+            consumer=Consumer.objects.get(key=oauth_request['oauth_consumer_key']),
+            timestamp=oauth_request['oauth_timestamp']
+        )
+        token.set_callback(callback)
+        token.save()
+
+        return token
+
+    def get_request_token(self, request, oauth_request, request_token_key):
+        try:
+            return Token.objects.get(key=request_token_key, token_type=Token.REQUEST)
+        except Token.DoesNotExist:
+            raise InvalidTokenError()
+
+    def authorize_request_token(self, request, oauth_request, request_token):
+        request_token.is_approved = True
+        request_token.user = request.user
+        request_token.verifier = oauth.generate_verifier(VERIFIER_SIZE)
+        request_token.save()
+        return request_token
+
+    def create_access_token(self, request, oauth_request, consumer, request_token):
+        access_token = Token.objects.create_token(
+            token_type=Token.ACCESS,
+            timestamp=oauth_request['oauth_timestamp'],
+            consumer=Consumer.objects.get(key=consumer.key),
+            user=request_token.user,
+        )
+        request_token.delete()
+        return access_token
+
+    def get_access_token(self, request, oauth_request, consumer, access_token_key):
+        try:
+            return Token.objects.get(key=access_token_key, token_type=Token.ACCESS)
+        except Token.DoesNotExist:
+            raise InvalidTokenError()
+
+    def get_user_for_access_token(self, request, oauth_request, access_token):
+        return access_token.user
+
+    def get_user_for_consumer(self, request, oauth_request, consumer):
+        return consumer.user
+
+    def check_nonce(self, request, oauth_request, nonce):
+        nonce, created = Nonce.objects.get_or_create(
+            consumer_key=oauth_request['oauth_consumer_key'],
+            token_key=oauth_request.get('oauth_token', ''),
+            key=nonce
+        )
+        return created

piston/authentication/oauth/urls.py

+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('piston.authentication.oauth.views',
+    (r'^get_request_token', 'get_request_token'),
+    (r'^authorize_request_token', 'authorize_request_token'),
+    (r'^get_access_token', 'get_access_token'),
+)

piston/authentication/oauth/utils.py

+import oauth2 as oauth
+from django.contrib.auth.models import User
+from django.http import HttpResponseBadRequest
+
+
+def get_oauth_request(request):
+    """ Converts a Django request object into an `oauth2.Request` object. """
+    headers = {}
+    if 'HTTP_AUTHORIZATION' in request.META:
+        headers['Authorization'] = request.META['HTTP_AUTHORIZATION']
+    return oauth.Request.from_request(request.method, request.build_absolute_uri(request.path), headers, dict(request.REQUEST))
+
+
+def verify_oauth_request(request, oauth_request, consumer, token=None):
+    """ Helper function to verify requests. """
+    from piston.authentication.oauth.store import store
+
+    # Check nonce
+    if not store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']):
+        return False
+
+    # Verify request
+    try:
+        oauth_server = oauth.Server()
+        oauth_server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+        oauth_server.add_signature_method(oauth.SignatureMethod_PLAINTEXT())
+
+        # Ensure the passed keys and secrets are ascii, or HMAC will complain.
+        consumer = oauth.Consumer(consumer.key.encode('ascii', 'ignore'), consumer.secret.encode('ascii', 'ignore'))
+        if token is not None:
+            token = oauth.Token(token.key.encode('ascii', 'ignore'), token.secret.encode('ascii', 'ignore'))
+
+        oauth_server.verify_request(oauth_request, consumer, token)
+    except oauth.Error:
+        return False
+
+    return True
+
+
+def require_params(oauth_request, parameters=[]):
+    """ Ensures that the request contains all required parameters. """
+    params = [
+        'oauth_consumer_key',
+        'oauth_nonce',
+        'oauth_signature',
+        'oauth_signature_method',
+        'oauth_timestamp'
+    ]
+    params.extend(parameters)
+
+    missing = list(param for param in params if param not in oauth_request)
+    if missing:
+        return HttpResponseBadRequest('Missing OAuth parameters: %s' % (', '.join(missing)))
+
+    return None

piston/authentication/oauth/views.py

+from urllib import urlencode
+
+import oauth2 as oauth
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.views.decorators.csrf import csrf_exempt
+
+from piston.authentication.oauth.forms import AuthorizeRequestTokenForm
+from piston.authentication.oauth.store import store, InvalidConsumerError, InvalidTokenError
+from piston.authentication.oauth.utils import verify_oauth_request, get_oauth_request, require_params
+
+
+@csrf_exempt
+def get_request_token(request):
+    oauth_request = get_oauth_request(request)
+
+    missing_params = require_params(oauth_request, ('oauth_callback',))
+    if missing_params is not None:
+        return missing_params
+
+    try:
+        consumer = store.get_consumer(request, oauth_request, oauth_request['oauth_consumer_key'])
+    except InvalidConsumerError:
+        return HttpResponseBadRequest('Invalid Consumer.')
+
+    if not verify_oauth_request(request, oauth_request, consumer):
+        return HttpResponseBadRequest('Could not verify OAuth request.')
+
+    request_token = store.create_request_token(request, oauth_request, consumer, oauth_request['oauth_callback'])
+
+    ret = urlencode({
+        'oauth_token': request_token.key,
+        'oauth_token_secret': request_token.secret,
+        'oauth_callback_confirmed': 'true'
+    })
+    return HttpResponse(ret, content_type='application/x-www-form-urlencoded')
+
+
+@login_required
+def authorize_request_token(request, form_class=AuthorizeRequestTokenForm, template_name='piston/oauth/authorize.html', verification_template_name='piston/oauth/authorize_verification_code.html'):
+    if 'oauth_token' not in request.REQUEST:
+        return HttpResponseBadRequest('No request token specified.')
+
+    oauth_request = get_oauth_request(request)
+
+    try:
+        request_token = store.get_request_token(request, oauth_request, request.REQUEST['oauth_token'])
+    except InvalidTokenError:
+        return HttpResponseBadRequest('Invalid request token.')
+
+    consumer = store.get_consumer_for_request_token(request, oauth_request, request_token)
+
+    if request.method == 'POST':
+        form = form_class(request.POST)
+        if form.is_valid() and form.cleaned_data['authorize_access']:
+            request_token = store.authorize_request_token(request, oauth_request, request_token)
+            if request_token.callback is not None and request_token.callback != 'oob':
+                return HttpResponseRedirect('%s&%s' % (request_token.get_callback_url(), urlencode({'oauth_token': request_token.key})))
+            else:
+                return render_to_response(verification_template_name, {'consumer': consumer, 'verification_code': request_token.verifier}, RequestContext(request))
+    else:
+        form = form_class(initial={'oauth_token': request_token.key})
+
+    return render_to_response(template_name, {'consumer': consumer, 'form': form}, RequestContext(request))
+
+
+@csrf_exempt
+def get_access_token(request):
+    oauth_request = get_oauth_request(request)
+
+    missing_params = require_params(oauth_request, ('oauth_token', 'oauth_verifier'))
+    if missing_params is not None:
+        return missing_params
+
+    try:
+        consumer = store.get_consumer(request, oauth_request, oauth_request['oauth_consumer_key'])
+        request_token = store.get_request_token(request, oauth_request, oauth_request['oauth_token'])
+    except InvalidTokenError:
+        return HttpResponseBadRequest('Invalid consumer.')
+    except InvalidConsumerError:
+        return HttpResponseBadRequest('Invalid request token.')
+
+    if not verify_oauth_request(request, oauth_request, consumer, request_token):
+        return HttpResponseBadRequest('Could not verify OAuth request.')
+
+    if oauth_request.get('oauth_verifier', None) != request_token.verifier:
+        return HttpResponseBadRequest('Invalid OAuth verifier.')
+
+    access_token = store.create_access_token(request, oauth_request, consumer, request_token)
+
+    ret = urlencode({
+        'oauth_token': access_token.key,
+        'oauth_token_secret': access_token.secret
+    })
+    return HttpResponse(ret, content_type='application/x-www-form-urlencoded')
         filt = lambda v: v not in self.data.keys()
         for field in filter(filt, getattr(self.Meta, 'fields', ())):
             self.data[field] = self.initial.get(field, None)
-
-
-class OAuthAuthenticationForm(forms.Form):
-    oauth_token = forms.CharField(widget=forms.HiddenInput)
-    oauth_callback = forms.CharField(widget=forms.HiddenInput, required=False)
-    authorize_access = forms.BooleanField(required=True)
-    csrf_signature = forms.CharField(widget=forms.HiddenInput)
-
-    def __init__(self, *args, **kwargs):
-        forms.Form.__init__(self, *args, **kwargs)
-
-        self.fields['csrf_signature'].initial = self.initial_csrf_signature
-
-    def clean_csrf_signature(self):
-        sig = self.cleaned_data['csrf_signature']
-        token = self.cleaned_data['oauth_token']
-
-        sig1 = OAuthAuthenticationForm.get_csrf_signature(settings.SECRET_KEY, token)
-
-        if sig != sig1:
-            raise forms.ValidationError("CSRF signature is not valid")
-
-        return sig
-
-    def initial_csrf_signature(self):
-        token = self.initial['oauth_token']
-        return OAuthAuthenticationForm.get_csrf_signature(settings.SECRET_KEY, token)
-
-    @staticmethod
-    def get_csrf_signature(key, token):
-        # Check signature...
-        try:
-            import hashlib # 2.5
-            hashed = hmac.new(key, token, hashlib.sha1)
-        except:
-            import sha # deprecated
-            hashed = hmac.new(key, token, sha)
-
-        # calculate the digest base 64
-        return base64.b64encode(hashed.digest())
-

piston/oauth.py

-"""
-The MIT License
-
-Copyright (c) 2007 Leah Culver
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-"""
-
-import cgi
-import urllib
-import time
-import random
-import urlparse
-import hmac
-import binascii
-
-
-VERSION = '1.0' # Hi Blaine!
-HTTP_METHOD = 'GET'
-SIGNATURE_METHOD = 'PLAINTEXT'
-
-
-class OAuthError(RuntimeError):
-    """Generic exception class."""
-    def __init__(self, message='OAuth error occured.'):
-        self.message = message
-
-def build_authenticate_header(realm=''):
-    """Optional WWW-Authenticate header (401 error)"""
-    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
-
-def escape(s):
-    """Escape a URL including any /."""
-    return urllib.quote(s, safe='~')
-
-def _utf8_str(s):
-    """Convert unicode to utf-8."""
-    if isinstance(s, unicode):
-        return s.encode("utf-8")
-    else:
-        return str(s)
-
-def generate_timestamp():
-    """Get seconds since epoch (UTC)."""
-    return int(time.time())
-
-def generate_nonce(length=8):
-    """Generate pseudorandom number."""
-    return ''.join([str(random.randint(0, 9)) for i in range(length)])
-
-def generate_verifier(length=8):
-    """Generate pseudorandom number."""
-    return ''.join([str(random.randint(0, 9)) for i in range(length)])
-
-
-class OAuthConsumer(object):
-    """Consumer of OAuth authentication.
-
-    OAuthConsumer is a data type that represents the identity of the Consumer
-    via its shared secret with the Service Provider.
-
-    """
-    key = None
-    secret = None
-
-    def __init__(self, key, secret):
-        self.key = key
-        self.secret = secret
-
-
-class OAuthToken(object):
-    """OAuthToken is a data type that represents an End User via either an access
-    or request token.
-    
-    key -- the token
-    secret -- the token secret
-
-    """
-    key = None
-    secret = None
-    callback = None
-    callback_confirmed = None
-    verifier = None
-
-    def __init__(self, key, secret):
-        self.key = key
-        self.secret = secret
-
-    def set_callback(self, callback):
-        self.callback = callback
-        self.callback_confirmed = 'true'
-
-    def set_verifier(self, verifier=None):
-        if verifier is not None:
-            self.verifier = verifier
-        else:
-            self.verifier = generate_verifier()
-
-    def get_callback_url(self):
-        if self.callback and self.verifier:
-            # Append the oauth_verifier.
-            parts = urlparse.urlparse(self.callback)
-            scheme, netloc, path, params, query, fragment = parts[:6]
-            if query:
-                query = '%s&oauth_verifier=%s' % (query, self.verifier)
-            else:
-                query = 'oauth_verifier=%s' % self.verifier
-            return urlparse.urlunparse((scheme, netloc, path, params,
-                query, fragment))
-        return self.callback
-
-    def to_string(self):
-        data = {
-            'oauth_token': self.key,
-            'oauth_token_secret': self.secret,
-        }
-        if self.callback_confirmed is not None:
-            data['oauth_callback_confirmed'] = self.callback_confirmed
-        return urllib.urlencode(data)
- 
-    def from_string(s):
-        """ Returns a token from something like:
-        oauth_token_secret=xxx&oauth_token=xxx
-        """
-        params = cgi.parse_qs(s, keep_blank_values=False)
-        key = params['oauth_token'][0]
-        secret = params['oauth_token_secret'][0]
-        token = OAuthToken(key, secret)
-        try:
-            token.callback_confirmed = params['oauth_callback_confirmed'][0]
-        except KeyError:
-            pass # 1.0, no callback confirmed.
-        return token
-    from_string = staticmethod(from_string)
-
-    def __str__(self):
-        return self.to_string()
-
-
-class OAuthRequest(object):
-    """OAuthRequest represents the request and can be serialized.
-
-    OAuth parameters:
-        - oauth_consumer_key 
-        - oauth_token
-        - oauth_signature_method
-        - oauth_signature 
-        - oauth_timestamp 
-        - oauth_nonce
-        - oauth_version
-        - oauth_verifier
-        ... any additional parameters, as defined by the Service Provider.
-    """
-    parameters = None # OAuth parameters.
-    http_method = HTTP_METHOD
-    http_url = None
-    version = VERSION
-
-    def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
-        self.http_method = http_method
-        self.http_url = http_url
-        self.parameters = parameters or {}
-
-    def set_parameter(self, parameter, value):
-        self.parameters[parameter] = value
-
-    def get_parameter(self, parameter):
-        try:
-            return self.parameters[parameter]
-        except:
-            raise OAuthError('Parameter not found: %s' % parameter)
-
-    def _get_timestamp_nonce(self):
-        return self.get_parameter('oauth_timestamp'), self.get_parameter(
-            'oauth_nonce')
-
-    def get_nonoauth_parameters(self):
-        """Get any non-OAuth parameters."""
-        parameters = {}
-        for k, v in self.parameters.iteritems():
-            # Ignore oauth parameters.
-            if k.find('oauth_') < 0:
-                parameters[k] = v
-        return parameters
-
-    def to_header(self, realm=''):
-        """Serialize as a header for an HTTPAuth request."""
-        auth_header = 'OAuth realm="%s"' % realm
-        # Add the oauth parameters.
-        if self.parameters:
-            for k, v in self.parameters.iteritems():
-                if k[:6] == 'oauth_':
-                    auth_header += ', %s="%s"' % (k, escape(str(v)))
-        return {'Authorization': auth_header}
-
-    def to_postdata(self):
-        """Serialize as post data for a POST request."""
-        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \
-            for k, v in self.parameters.iteritems()])
-
-    def to_url(self):
-        """Serialize as a URL for a GET request."""
-        return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
-
-    def get_normalized_parameters(self):
-        """Return a string that contains the parameters that must be signed."""
-        params = self.parameters
-        try:
-            # Exclude the signature if it exists.
-            del params['oauth_signature']
-        except:
-            pass
-        # Escape key values before sorting.
-        key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \
-            for k,v in params.items()]
-        # Sort lexicographically, first after key, then after value.
-        key_values.sort()
-        # Combine key value pairs into a string.
-        return '&'.join(['%s=%s' % (k, v) for k, v in key_values])
-
-    def get_normalized_http_method(self):
-        """Uppercases the http method."""
-        return self.http_method.upper()
-
-    def get_normalized_http_url(self):
-        """Parses the URL and rebuilds it to be scheme://host/path."""
-        parts = urlparse.urlparse(self.http_url)
-        scheme, netloc, path = parts[:3]
-        # Exclude default port numbers.
-        if scheme == 'http' and netloc[-3:] == ':80':
-            netloc = netloc[:-3]
-        elif scheme == 'https' and netloc[-4:] == ':443':
-            netloc = netloc[:-4]
-        return '%s://%s%s' % (scheme, netloc, path)
-
-    def sign_request(self, signature_method, consumer, token):
-        """Set the signature parameter to the result of build_signature."""
-        # Set the signature method.
-        self.set_parameter('oauth_signature_method',
-            signature_method.get_name())
-        # Set the signature.
-        self.set_parameter('oauth_signature',
-            self.build_signature(signature_method, consumer, token))
-
-    def build_signature(self, signature_method, consumer, token):
-        """Calls the build signature method within the signature method."""
-        return signature_method.build_signature(self, consumer, token)
-
-    def from_request(http_method, http_url, headers=None, parameters=None,
-            query_string=None):
-        """Combines multiple parameter sources."""
-        if parameters is None:
-            parameters = {}
-
-        # Headers
-        if headers and 'Authorization' in headers:
-            auth_header = headers['Authorization']
-            # Check that the authorization header is OAuth.
-            if auth_header[:6] == 'OAuth ':
-                auth_header = auth_header[6:]
-                try:
-                    # Get the parameters from the header.
-                    header_params = OAuthRequest._split_header(auth_header)
-                    parameters.update(header_params)
-                except:
-                    raise OAuthError('Unable to parse OAuth parameters from '
-                        'Authorization header.')
-
-        # GET or POST query string.
-        if query_string:
-            query_params = OAuthRequest._split_url_string(query_string)
-            parameters.update(query_params)
-
-        # URL parameters.
-        param_str = urlparse.urlparse(http_url)[4] # query
-        url_params = OAuthRequest._split_url_string(param_str)
-        parameters.update(url_params)
-
-        if parameters:
-            return OAuthRequest(http_method, http_url, parameters)
-
-        return None
-    from_request = staticmethod(from_request)
-
-    def from_consumer_and_token(oauth_consumer, token=None,
-            callback=None, verifier=None, http_method=HTTP_METHOD,
-            http_url=None, parameters=None):
-        if not parameters:
-            parameters = {}
-
-        defaults = {
-            'oauth_consumer_key': oauth_consumer.key,
-            'oauth_timestamp': generate_timestamp(),
-            'oauth_nonce': generate_nonce(),
-            'oauth_version': OAuthRequest.version,
-        }
-
-        defaults.update(parameters)
-        parameters = defaults
-
-        if token:
-            parameters['oauth_token'] = token.key
-            parameters['oauth_callback'] = token.callback
-            # 1.0a support for verifier.
-            parameters['oauth_verifier'] = verifier
-        elif callback:
-            # 1.0a support for callback in the request token request.
-            parameters['oauth_callback'] = callback
-
-        return OAuthRequest(http_method, http_url, parameters)
-    from_consumer_and_token = staticmethod(from_consumer_and_token)
-
-    def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD,
-            http_url=None, parameters=None):
-        if not parameters:
-            parameters = {}
-
-        parameters['oauth_token'] = token.key
-
-        if callback:
-            parameters['oauth_callback'] = callback
-
-        return OAuthRequest(http_method, http_url, parameters)
-    from_token_and_callback = staticmethod(from_token_and_callback)
-
-    def _split_header(header):
-        """Turn Authorization: header into parameters."""
-        params = {}
-        parts = header.split(',')
-        for param in parts:
-            # Ignore realm parameter.
-            if param.find('realm') > -1:
-                continue
-            # Remove whitespace.
-            param = param.strip()
-            # Split key-value.
-            param_parts = param.split('=', 1)
-            # Remove quotes and unescape the value.
-            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
-        return params
-    _split_header = staticmethod(_split_header)
-
-    def _split_url_string(param_str):
-        """Turn URL string into parameters."""
-        parameters = cgi.parse_qs(param_str, keep_blank_values=False)
-        for k, v in parameters.iteritems():
-            parameters[k] = urllib.unquote(v[0])
-        return parameters
-    _split_url_string = staticmethod(_split_url_string)
-
-class OAuthServer(object):
-    """A worker to check the validity of a request against a data store."""
-    timestamp_threshold = 300 # In seconds, five minutes.
-    version = VERSION
-    signature_methods = None
-    data_store = None
-
-    def __init__(self, data_store=None, signature_methods=None):
-        self.data_store = data_store
-        self.signature_methods = signature_methods or {}
-
-    def set_data_store(self, data_store):
-        self.data_store = data_store
-
-    def get_data_store(self):
-        return self.data_store
-
-    def add_signature_method(self, signature_method):
-        self.signature_methods[signature_method.get_name()] = signature_method
-        return self.signature_methods
-
-    def fetch_request_token(self, oauth_request):
-        """Processes a request_token request and returns the
-        request token on success.
-        """
-        try:
-            # Get the request token for authorization.
-            token = self._get_token(oauth_request, 'request')
-        except OAuthError:
-            # No token required for the initial token request.
-            version = self._get_version(oauth_request)
-            consumer = self._get_consumer(oauth_request)
-            try:
-                callback = self.get_callback(oauth_request)
-            except OAuthError:
-                callback = None # 1.0, no callback specified.
-            self._check_signature(oauth_request, consumer, None)
-            # Fetch a new token.
-            token = self.data_store.fetch_request_token(consumer, callback)
-        return token
-
-    def fetch_access_token(self, oauth_request):
-        """Processes an access_token request and returns the
-        access token on success.
-        """
-        version = self._get_version(oauth_request)
-        consumer = self._get_consumer(oauth_request)
-        verifier = self._get_verifier(oauth_request)
-        # Get the request token.
-        token = self._get_token(oauth_request, 'request')
-        self._check_signature(oauth_request, consumer, token)
-        new_token = self.data_store.fetch_access_token(consumer, token, verifier)
-        return new_token
-
-    def verify_request(self, oauth_request):
-        """Verifies an api call and checks all the parameters."""
-        # -> consumer and token
-        version = self._get_version(oauth_request)
-        consumer = self._get_consumer(oauth_request)
-        # Get the access token.
-        token = self._get_token(oauth_request, 'access')
-        self._check_signature(oauth_request, consumer, token)
-        parameters = oauth_request.get_nonoauth_parameters()
-        return consumer, token, parameters
-
-    def authorize_token(self, token, user):
-        """Authorize a request token."""
-        return self.data_store.authorize_request_token(token, user)
-
-    def get_callback(self, oauth_request):
-        """Get the callback URL."""
-        return oauth_request.get_parameter('oauth_callback')
- 
-    def build_authenticate_header(self, realm=''):
-        """Optional support for the authenticate header."""
-        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
-
-    def _get_version(self, oauth_request):
-        """Verify the correct version request for this server."""
-        try:
-            version = oauth_request.get_parameter('oauth_version')
-        except:
-            version = VERSION
-        if version and version != self.version:
-            raise OAuthError('OAuth version %s not supported.' % str(version))
-        return version
-
-    def _get_signature_method(self, oauth_request):
-        """Figure out the signature with some defaults."""
-        try:
-            signature_method = oauth_request.get_parameter(
-                'oauth_signature_method')
-        except:
-            signature_method = SIGNATURE_METHOD
-        try:
-            # Get the signature method object.
-            signature_method = self.signature_methods[signature_method]
-        except:
-            signature_method_names = ', '.join(self.signature_methods.keys())
-            raise OAuthError('Signature method %s not supported try one of the '
-                'following: %s' % (signature_method, signature_method_names))
-
-        return signature_method
-
-    def _get_consumer(self, oauth_request):
-        consumer_key = oauth_request.get_parameter('oauth_consumer_key')
-        consumer = self.data_store.lookup_consumer(consumer_key)
-        if not consumer:
-            raise OAuthError('Invalid consumer.')
-        return consumer
-
-    def _get_token(self, oauth_request, token_type='access'):
-        """Try to find the token for the provided request token key."""
-        token_field = oauth_request.get_parameter('oauth_token')
-        token = self.data_store.lookup_token(token_type, token_field)
-        if not token:
-            raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
-        return token
-    
-    def _get_verifier(self, oauth_request):
-        return oauth_request.get_parameter('oauth_verifier')
-
-    def _check_signature(self, oauth_request, consumer, token):
-        timestamp, nonce = oauth_request._get_timestamp_nonce()
-        self._check_timestamp(timestamp)
-        self._check_nonce(consumer, token, nonce)
-        signature_method = self._get_signature_method(oauth_request)
-        try:
-            signature = oauth_request.get_parameter('oauth_signature')
-        except:
-            raise OAuthError('Missing signature.')
-        # Validate the signature.
-        valid_sig = signature_method.check_signature(oauth_request, consumer,
-            token, signature)
-        if not valid_sig:
-            key, base = signature_method.build_signature_base_string(
-                oauth_request, consumer, token)
-            raise OAuthError('Invalid signature. Expected signature base '
-                'string: %s' % base)
-        built = signature_method.build_signature(oauth_request, consumer, token)
-
-    def _check_timestamp(self, timestamp):
-        """Verify that timestamp is recentish."""
-        timestamp = int(timestamp)
-        now = int(time.time())
-        lapsed = now - timestamp
-        if lapsed > self.timestamp_threshold:
-            raise OAuthError('Expired timestamp: given %d and now %s has a '
-                'greater difference than threshold %d' %
-                (timestamp, now, self.timestamp_threshold))
-
-    def _check_nonce(self, consumer, token, nonce):
-        """Verify that the nonce is uniqueish."""
-        nonce = self.data_store.lookup_nonce(consumer, token, nonce)
-        if nonce:
-            raise OAuthError('Nonce already used: %s' % str(nonce))
-
-
-class OAuthClient(object):
-    """OAuthClient is a worker to attempt to execute a request."""
-    consumer = None
-    token = None
-
-    def __init__(self, oauth_consumer, oauth_token):
-        self.consumer = oauth_consumer
-        self.token = oauth_token
-
-    def get_consumer(self):
-        return self.consumer
-
-    def get_token(self):
-        return self.token
-
-    def fetch_request_token(self, oauth_request):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def fetch_access_token(self, oauth_request):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def access_resource(self, oauth_request):
-        """-> Some protected resource."""
-        raise NotImplementedError
-
-
-class OAuthDataStore(object):
-    """A database abstraction used to lookup consumers and tokens."""
-
-    def lookup_consumer(self, key):
-        """-> OAuthConsumer."""
-        raise NotImplementedError
-
-    def lookup_token(self, oauth_consumer, token_type, token_token):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def fetch_request_token(self, oauth_consumer, oauth_callback):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-    def authorize_request_token(self, oauth_token, user):
-        """-> OAuthToken."""
-        raise NotImplementedError
-
-
-class OAuthSignatureMethod(object):
-    """A strategy class that implements a signature method."""
-    def get_name(self):
-        """-> str."""
-        raise NotImplementedError
-
-    def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
-        """-> str key, str raw."""
-        raise NotImplementedError
-
-    def build_signature(self, oauth_request, oauth_consumer, oauth_token):
-        """-> str."""
-        raise NotImplementedError
-
-    def check_signature(self, oauth_request, consumer, token, signature):
-        built = self.build_signature(oauth_request, consumer, token)
-        return built == signature
-
-
-class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
-
-    def get_name(self):
-        return 'HMAC-SHA1'
-        
-    def build_signature_base_string(self, oauth_request, consumer, token):
-        sig = (
-            escape(oauth_request.get_normalized_http_method()),
-            escape(oauth_request.get_normalized_http_url()),
-            escape(oauth_request.get_normalized_parameters()),
-        )
-
-        key = '%s&' % escape(consumer.secret)
-        if token:
-            key += escape(token.secret)
-        raw = '&'.join(sig)
-        return key, raw
-
-    def build_signature(self, oauth_request, consumer, token):
-        """Builds the base signature string."""
-        key, raw = self.build_signature_base_string(oauth_request, consumer,
-            token)
-
-        # HMAC object.
-        try:
-            import hashlib # 2.5
-            hashed = hmac.new(key, raw, hashlib.sha1)
-        except:
-            import sha # Deprecated
-            hashed = hmac.new(key, raw, sha)
-
-        # Calculate the digest base 64.
-        return binascii.b2a_base64(hashed.digest())[:-1]
-
-
-class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
-
-    def get_name(self):
-        return 'PLAINTEXT'
-
-    def build_signature_base_string(self, oauth_request, consumer, token):
-        """Concatenates the consumer key and secret."""
-        sig = '%s&' % escape(consumer.secret)
-        if token:
-            sig = sig + escape(token.secret)
-        return sig, sig
-
-    def build_signature(self, oauth_request, consumer, token):
-        key, raw = self.build_signature_base_string(oauth_request, consumer,
-            token)
-        return key

piston/store.py

-import oauth
-
-from models import Nonce, Token, Consumer
-from models import generate_random, VERIFIER_SIZE
-
-class DataStore(oauth.OAuthDataStore):
-    """Layer between Python OAuth and Django database."""
-    def __init__(self, oauth_request):
-        self.signature = oauth_request.parameters.get('oauth_signature', None)
-        self.timestamp = oauth_request.parameters.get('oauth_timestamp', None)
-        self.scope = oauth_request.parameters.get('scope', 'all')
-
-    def lookup_consumer(self, key):
-        try:
-            self.consumer = Consumer.objects.get(key=key)
-            return self.consumer
-        except Consumer.DoesNotExist:
-            return None
-
-    def lookup_token(self, token_type, token):
-        if token_type == 'request':
-            token_type = Token.REQUEST
-        elif token_type == 'access':
-            token_type = Token.ACCESS
-        try:
-            self.request_token = Token.objects.get(key=token, 
-                                                   token_type=token_type)
-            return self.request_token
-        except Token.DoesNotExist:
-            return None
-
-    def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
-        if oauth_token is None:
-            return None
-        nonce, created = Nonce.objects.get_or_create(consumer_key=oauth_consumer.key, 
-                                                     token_key=oauth_token.key,
-                                                     key=nonce)
-        if created:
-            return None
-        else:
-            return nonce.key
-
-    def fetch_request_token(self, oauth_consumer, oauth_callback):
-        if oauth_consumer.key == self.consumer.key:
-            self.request_token = Token.objects.create_token(consumer=self.consumer,
-                                                            token_type=Token.REQUEST,
-                                                            timestamp=self.timestamp)
-            
-            if oauth_callback:
-                self.request_token.set_callback(oauth_callback)
-            
-            return self.request_token
-        return None
-
-    def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
-        if oauth_consumer.key == self.consumer.key \
-        and oauth_token.key == self.request_token.key \
-        and oauth_verifier == self.request_token.verifier \
-        and self.request_token.is_approved:
-            self.access_token = Token.objects.create_token(consumer=self.consumer,
-                                                           token_type=Token.ACCESS,
-                                                           timestamp=self.timestamp,
-                                                           user=self.request_token.user)
-            return self.access_token
-        return None
-
-    def authorize_request_token(self, oauth_token, user):
-        if oauth_token.key == self.request_token.key:
-            # authorize the request token in the store
-            self.request_token.is_approved = True
-            self.request_token.user = user
-            self.request_token.verifier = generate_random(VERIFIER_SIZE)
-            self.request_token.save()
-            return self.request_token
-        return None

piston/templates/piston/authorize_token.html

-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
-"http://www.w3.org/TR/html4/strict.dtd">
-<html>
-	<head>
-		<title>Authorize Token</title>
-	</head>
-	<body>
-		<h1>Authorize Token</h1>
-		
-        <form action="{% url piston.authentication.oauth_user_auth %}" method="POST">
-            {{ form.as_table }}
-        </form>
-
-	</body>
-</html>
 from django.utils.http import urlencode
 
 # Piston imports
-from piston import oauth
 from piston.models import Consumer, Token
 
 # 3rd/Python party imports
 
 URLENCODED_FORM_CONTENT = 'application/x-www-form-urlencoded'
 
-class OAuthClient(client.Client):
-    def __init__(self, consumer, token):
-        self.token = oauth.OAuthToken(token.key, token.secret)
-        self.consumer = oauth.OAuthConsumer(consumer.key, consumer.secret)
-        self.signature = oauth.OAuthSignatureMethod_HMAC_SHA1()
-
-        super(OAuthClient, self).__init__()
-
-    def request(self, **request):
-        # Figure out parameters from request['QUERY_STRING'] and FakePayload
-        params = {}
-        if request['REQUEST_METHOD'] in ('POST', 'PUT'):
-            if request['CONTENT_TYPE'] == URLENCODED_FORM_CONTENT:
-                payload = request['wsgi.input'].read()
-                request['wsgi.input'] = client.FakePayload(payload)
-                params = cgi.parse_qs(payload)
-
-        url = "http://testserver" + request['PATH_INFO']
-
-        req = oauth.OAuthRequest.from_consumer_and_token(
-            self.consumer, token=self.token, 
-            http_method=request['REQUEST_METHOD'], http_url=url, 
-            parameters=params
-        )
-
-        req.sign_request(self.signature, self.consumer, self.token)
-        headers = req.to_header()
-        request['HTTP_AUTHORIZATION'] = headers['Authorization']
-
-        return super(OAuthClient, self).request(**request)
-
-    def post(self, path, data={}, content_type=None, follow=False, **extra):
-        if content_type is None:
-            content_type = URLENCODED_FORM_CONTENT
-
-        if isinstance(data, dict):
-            data = urlencode(data)
-        
-        return super(OAuthClient, self).post(path, data, content_type, follow, **extra)
-
 class TestCase(test.TestCase):
     pass
 
-class OAuthTestCase(TestCase):
-    @property
-    def oauth(self):
-        return OAuthClient(self.consumer, self.token)
-
 
 from datetime import datetime, timedelta
 
-__version__ = '0.2.3rc1'
+__version__ = '0.3dev'
 
 def get_version():
     return __version__
 
 setup(
     name = "django-piston",
-    version = "0.2.3rc1",
+    version = "0.3dev",
     url = 'http://bitbucket.org/jespern/django-piston/wiki/Home',
 	download_url = 'http://bitbucket.org/jespern/django-piston/downloads/',
     license = 'BSD',

tests/buildout.cfg

 [buildout]
-parts = django-1.1 django-1.0
+parts = django-1.2
 develop = ..
-eggs = 
-  django-piston
-  pyyaml
+eggs =
+    django-piston
+    PyYAML
+    oauth2
 
-[django-1.1]
+[django-1.2]
 recipe = djangorecipe
-version = 1.1
+version = 1.2.1
 project = test_project
 settings = settings
 test = testapp
 eggs = ${buildout:eggs}
-testrunner = test-1.1
-
-[django-1.0]
-recipe = djangorecipe
-version = 1.0.2
-project = test_project
-settings = settings
-test = testapp
-eggs = ${buildout:eggs}
-testrunner = test-1.0
+testrunner = test-1.2

tests/test_project/apps/testapp/tests.py

+import urlparse
+
+import oauth2 as oauth
 from django.test import TestCase
 from django.contrib.auth.models import User
 from django.utils import simplejson
 from django.conf import settings
 
-from piston import oauth
 from piston.models import Consumer, Token
-from piston.forms import OAuthAuthenticationForm
 
 try:
     import yaml
 from test_project.apps.testapp.models import TestModel, ExpressiveTestModel, Comment, InheritedModel, Issue58Model, ListFieldsModel
 from test_project.apps.testapp import signals
 
+
 class MainTests(TestCase):
     def setUp(self):
         self.user = User.objects.create_user('admin', 'admin@world.com', 'admin')
 
 
 class OAuthTests(MainTests):
-    signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+    signature_method = oauth.SignatureMethod_HMAC_SHA1()
+    callback_url = 'http://example.com/cb'
+    request_token_url = 'http://testserver/api/oauth/get_request_token'
+    authorize_url = 'http://testserver/api/oauth/authorize_request_token'
+    access_token_url = 'http://testserver/api/oauth/get_access_token'
+    two_legged_api_url = 'http://testserver/api/oauth/two_legged_api'
+    three_legged_api_url = 'http://testserver/api/oauth/three_legged_api'
 
     def setUp(self):
         super(OAuthTests, self).setUp()
-
         self.consumer = Consumer.objects.create_consumer('Test Consumer')
         self.consumer.status = 'accepted'
         self.consumer.save()
 
     def tearDown(self):
         super(OAuthTests, self).tearDown()
-        self.consumer.delete()
+        #self.consumer.delete()
 
-    def test_handshake(self):
-        '''Test the OAuth handshake procedure
-        '''
-        oaconsumer = oauth.OAuthConsumer(self.consumer.key, self.consumer.secret)
+    def test_get_request_token(self, callback='oob'):
+        request = oauth.Request.from_consumer_and_token(self.consumer, None, 'GET', self.request_token_url, {'oauth_callback': callback})
+        request.sign_request(self.signature_method, self.consumer, None)
 
-        # Get a request key...
-        request = oauth.OAuthRequest.from_consumer_and_token(oaconsumer,
-                http_url='http://testserver/api/oauth/request_token')
-        request.sign_request(self.signature_method, oaconsumer, None)
+        response = self.client.get(self.request_token_url, request)
+        self.assertEquals(response.status_code, 200)
 
-        response = self.client.get('/api/oauth/request_token', request.parameters)
-        oatoken = oauth.OAuthToken.from_string(response.content)
+        params = dict(urlparse.parse_qsl(response.content))
+        return oauth.Token(params['oauth_token'], params['oauth_token_secret'])
 
-        token = Token.objects.get(key=oatoken.key, token_type=Token.REQUEST)
-        self.assertEqual(token.secret, oatoken.secret)
+    def authorize_request_token(self, request_token_key):
+        self.client.login(username='admin', password='admin')
+        return self.client.post(self.authorize_url, {'oauth_token': request_token_key, 'authorize_access': None})
 
-        # Simulate user authentication...
-        self.failUnless(self.client.login(username='admin', password='admin'))
-        request = oauth.OAuthRequest.from_token_and_callback(token=oatoken,
-                callback='http://printer.example.com/request_token_ready',
-                http_url='http://testserver/api/oauth/authorize')
-        request.sign_request(self.signature_method, oaconsumer, oatoken)
+    def test_authorize_request_token_without_callback(self):
+        request_token = self.test_get_request_token('oob')
+        response = self.authorize_request_token(request_token.key)
 
-        # Request the login page
-# TODO: Parse the response to make sure all the fields exist
-#        response = self.client.get('/api/oauth/authorize', {
-#            'oauth_token': oatoken.key,
-#            'oauth_callback': 'http://printer.example.com/request_token_ready',
-#            })
+        self.assertEquals(response.status_code, 200)
 
-        response = self.client.post('/api/oauth/authorize', {
-            'oauth_token': oatoken.key,
-            'oauth_callback': 'http://printer.example.com/request_token_ready',
-            'csrf_signature': OAuthAuthenticationForm.get_csrf_signature(settings.SECRET_KEY, oatoken.key),
-            'authorize_access': 1,
-            })
+    def test_authorize_request_token_with_callback(self):
+        request_token = self.test_get_request_token(self.callback_url)
+        response = self.authorize_request_token(request_token.key)
 
-        # Response should be a redirect...
-        self.assertEqual(302, response.status_code)
-        self.failUnless(response['Location'].startswith("http://printer.example.com/request_token_ready?"))
-        self.failUnless(('oauth_token='+oatoken.key in response['Location']))
+        self.assertEquals(response.status_code, 302)
+        self.assert_(response['Location'].startswith(self.callback_url))
+
+    def test_get_access_token(self):
+        request_token = self.test_get_request_token(self.callback_url)
+        response = self.authorize_request_token(request_token.key)
+        params = dict(urlparse.parse_qsl(response['Location'][len(self.callback_url)+1:]))
         
-        # Actually we can't test this last part, since it's 1.0a.
-        # Obtain access token...
-#        request = oauth.OAuthRequest.from_consumer_and_token(oaconsumer, token=oatoken,
-#                http_url='http://testserver/api/oauth/access_token')
-#        request.sign_request(self.signature_method, oaconsumer, oatoken)
-#        response = self.client.get('/api/oauth/access_token', request.parameters)
+        request_token.set_verifier(params['oauth_verifier'])
+        
+        request = oauth.Request.from_consumer_and_token(self.consumer, request_token, 'POST', self.access_token_url)
+        request.sign_request(self.signature_method, self.consumer, request_token)
 
-#        oa_atoken = oauth.OAuthToken.from_string(response.content)
-#        atoken = Token.objects.get(key=oa_atoken.key, token_type=Token.ACCESS)
-#        self.assertEqual(atoken.secret, oa_atoken.secret)
+        response = self.client.post(self.access_token_url, request)
+        self.assertEquals(response.status_code, 200)
+        
+        params = dict(urlparse.parse_qsl(response.content))
+        return oauth.Token(params['oauth_token'], params['oauth_token_secret'])
+
+    def test_two_legged_api(self):
+        request = oauth.Request.from_consumer_and_token(self.consumer, None, 'GET', self.two_legged_api_url, {'msg': 'expected response'})
+        request.sign_request(self.signature_method, self.consumer, None)
+
+        response = self.client.get(self.two_legged_api_url, request)
+        self.assertEquals(response.status_code, 200)
+        self.assert_('expected response' in response.content)
+
+    def test_three_legged_api(self):
+        access_token = self.test_get_access_token()
+
+        request = oauth.Request.from_consumer_and_token(self.consumer, access_token, 'GET', self.three_legged_api_url, {'msg': 'expected response'})
+        request.sign_request(self.signature_method, self.consumer, access_token)
+
+        response = self.client.get(self.three_legged_api_url, request)
+        self.assertEquals(response.status_code, 200)
+        self.assert_('expected response' in response.content)
+
 
 class BasicAuthTest(MainTests):
 

tests/test_project/apps/testapp/urls.py

 from django.conf.urls.defaults import *
 from piston.resource import Resource
 from piston.authentication import HttpBasicAuthentication, HttpBasicSimple
+from piston.authentication.oauth import OAuthAuthentication
 
 from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler, ListFieldsHandler
 
 multiauth = Resource(handler=PlainOldObjectHandler, 
                         authentication=AUTHENTICATORS)
 
+ouath_two_legged_api = Resource(handler=EchoHandler, authentication=OAuthAuthentication(realm='TestApplication', two_legged=True))
+ouath_three_legged_api = Resource(handler=EchoHandler, authentication=OAuthAuthentication(realm='TestApplication'))
+
 urlpatterns = patterns('',
     url(r'^entries/$', entries),
     url(r'^entries/(?P<pk>.+)/$', entries),
 
     url(r'^multiauth/$', multiauth),
 
-    # oauth entrypoints
-    url(r'^oauth/request_token$', 'piston.authentication.oauth_request_token'),
-    url(r'^oauth/authorize$', 'piston.authentication.oauth_user_auth'),
-    url(r'^oauth/access_token$', 'piston.authentication.oauth_access_token'),
+    # OAuth
+    url(r'^oauth/', include('piston.authentication.oauth.urls')),
+    url(r'^oauth/two_legged_api$', ouath_two_legged_api),
+    url(r'^oauth/three_legged_api$', ouath_three_legged_api),
 
     url(r'^list_fields$', list_fields),
     url(r'^list_fields/(?P<id>.+)$', list_fields),

tests/test_project/settings.py

 import os
 DEBUG = True
 DATABASE_ENGINE = 'sqlite3'
-DATABASE_NAME = '/tmp/piston.db'
+DATABASE_NAME = os.path.join(os.path.dirname(__file__), 'data.db')
 INSTALLED_APPS = (
     'django.contrib.auth', 
     'django.contrib.contenttypes', 

tests/test_project/templates/piston/oauth/authorize.html

+<html>
+    <head>
+        <title>Authorize Access</title>
+    </head>