Commits

Thomas Johansson committed bd45255

Rewrite and massively improve error handling for bad requests.

Comments (0)

Files changed (5)

piston/authentication/oauth/__init__.py

 from django.http import HttpResponse
 from django.template import loader
 
-from piston.authentication.oauth.store import store, InvalidAccessToken, InvalidConsumer
+from piston.authentication.oauth.store import store, InvalidConsumerError, InvalidTokenError
 from piston.authentication.oauth.utils import get_oauth_request, verify_oauth_request
 
 
         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 (InvalidConsumer, InvalidAccessToken):
+        except (InvalidConsumerError, InvalidTokenError):
             return False
     
         if not verify_oauth_request(request, oauth_request, consumer, access_token):

piston/authentication/oauth/store/__init__.py

 from django.utils import importlib
 
 
-class InvalidConsumer(RuntimeError):
+class Error(Exception):
+    """Base class for Store exceptions."""
+
+
+class InvalidConsumerError(Error):
     """Invalid consumer."""
 
 
-class InvalidToken(RuntimeError):
+class InvalidTokenError(Error):
     """Invalid token."""
 
 
-class InvalidRequestToken(InvalidToken):
-    """Invalid request token."""
-
-
-class InvalidAccessToken(InvalidToken):
-    """Invalid access token."""
-
-
 class Store(object):
     """
     The Store class is the backbone of piston's OAuth implementation. It is used
     """
     def get_consumer(self, request, oauth_request, consumer_key):
         """
-        Return the Consumer for `consumer_key` or raise `InvalidConsumer`.
+        Return the Consumer for `consumer_key` or raise `InvalidConsumerError`.
         
         `request`: The Django request object.
         `oauth_request`: The `oauth2.Request` object.
     
     def get_consumer_for_request_token(self, request, oauth_request, request_token):
         """
-        Return the Consumer associated with the `request_token` Token, or raise
-        `InvalidConsumer`.
-        
+        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.
     
     def get_consumer_for_access_token(self, request, oauth_request, access_token):
         """
-        Return the Consumer associated with the `access_token` Token, or raise
-        `InvalidConsumer`.
+        Return the Consumer associated with the `access_token` Token.
         
         `request`: The Django request object.
         `oauth_request`: The `oauth2.Request` object.
 
     def get_request_token(self, request, oauth_request, request_token_key):
         """
-        Return the Token for `request_token_key` or raise `InvalidRequestToken`.
+        Return the Token for `request_token_key` or raise `InvalidTokenError`.
         
         `request`: The Django request object.
         `oauth_request`: The `oauth2.Request` object.
 
     def authorize_request_token(self, request, oauth_request, request_token):
         """ 
-        Authorize the `request_token` Token and return it, or raise
-        `InvalidRequestToken`.
+        Authorize the `request_token` Token and return it.
         
         `request`: The Django request object.
         `oauth_request`: The `oauth2.Request` object.
 
     def get_access_token(self, request, oauth_request, consumer, access_token_key):
         """
-        Return the Token for `access_token_key` or raise `InvalidAccessToken`.
+        Return the Token for `access_token_key` or raise `InvalidTokenError`.
         
         `request`: The Django request object.
         `oauth_request`: The `oauth2.Request` object.

piston/authentication/oauth/store/db.py

 import oauth2 as oauth
 
-from piston.authentication.oauth.store import InvalidAccessToken, InvalidConsumer, InvalidRequestToken, Store
+from piston.authentication.oauth.store import InvalidConsumerError, InvalidTokenError, Store
 from piston.models import Nonce, Token, Consumer, VERIFIER_SIZE
 
 
         try:
             return Consumer.objects.get(key=consumer_key)
         except Consumer.DoesNotExist:
-            raise InvalidConsumer
+            raise InvalidConsumer()
 
     def get_consumer_for_request_token(self, request, oauth_request, request_token):
         return request_token.consumer
         try:
             return Token.objects.get(key=request_token_key, token_type=Token.REQUEST)
         except Token.DoesNotExist:
-            raise InvalidRequestToken
+            raise InvalidTokenError()
 
     def authorize_request_token(self, request, oauth_request, request_token):    
         request_token.is_approved = True
         try:
             return Token.objects.get(key=access_token_key, token_type=Token.ACCESS)
         except Token.DoesNotExist:
-            raise InvalidAccessToken
+            raise InvalidTokenError()
 
     def get_user_for_access_token(self, request, oauth_request, access_token):
         return access_token.user

piston/authentication/oauth/utils.py

 import oauth2 as oauth
 from django.contrib.auth.models import User
-
-
-def generate_random(length=8):
-    return User.objects.make_random_password(length=length)
+from django.http import HttpResponseBadRequest
 
 
 def get_oauth_request(request):
         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 Exception, e:
+    except oauth.Error:
         return False
     
     return True
+
+
+def require_paramaters(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 django.views.decorators.csrf import csrf_exempt
 
 from piston.authentication.oauth.forms import AuthorizeRequestTokenForm
-from piston.authentication.oauth.store import store
-from piston.authentication.oauth.utils import verify_oauth_request, get_oauth_request
+from piston.authentication.oauth.store import store, InvalidConsumerError, InvalidTokenError
+from piston.authentication.oauth.utils import verify_oauth_request, get_oauth_request, require_paramaters
 
 
 @csrf_exempt
 def get_request_token(request):
     oauth_request = get_oauth_request(request)
-    consumer = store.get_consumer(request, oauth_request, oauth_request['oauth_consumer_key'])
 
-    if 'oauth_callback' not in oauth_request:
-        return HttpResponseBadRequest('OAuth 1.0a is required.')
+    missing_params = require_paramaters(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.')
 @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 token specified.')
+        return HttpResponseBadRequest('No request token specified.')
 
     oauth_request = get_oauth_request(request)
-    request_token = store.get_request_token(request, oauth_request, request.REQUEST['oauth_token'])
+
+    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':
 @csrf_exempt
 def get_access_token(request):
     oauth_request = get_oauth_request(request)
-    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'])
+    missing_params = require_paramaters(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.')