Commits

James Emerton committed a7f81ea

Added CSRF protection to OAuth authorization

Comments (0)

Files changed (5)

 develop-eggs
 .DS_Store
 .svn
+*.swp

piston/authentication.py

 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 django.utils.importlib import import_module
 
 import oauth
+from piston import forms
 
 class NoAuthentication(object):
     """
     return response
 
 def oauth_auth_view(request, token, callback, params):
-    return HttpResponse("Just a fake view for auth. %s, %s, %s" % (token, callback, params))
+    form = forms.OAuthAuthenticationForm(initial={
+        'oauth_token': token.key,
+        'oauth_callback': callback,
+        })
+    return render_to_response('piston/authorize_token.html',
+            { 'form': form }, RequestContext(request))
 
 @login_required
 def oauth_user_auth(request):
         callback = None
         
     if request.method == "GET":
-        request.session['oauth'] = token.key
         params = oauth_request.get_normalized_parameters()
 
-        oauth_view = getattr(settings, 'OAUTH_AUTH_VIEW', 'oauth_auth_view')
-
-        return get_callable(oauth_view)(request, token, callback, params)
+        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":
-        if request.session.get('oauth', '') == token.key:
-            request.session['oauth'] = ''
+        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.'
             
-            try:
-                if int(request.POST.get('authorize_access', '0')):
-                    token = oauth_server.authorize_token(token, request.user)
-                    args = '?'+token.to_string(only_key=True)
-                else:
-                    args = '?error=%s' % 'Access not granted by user.'
+            if not callback:
+                callback = getattr(settings, 'OAUTH_CALLBACK_VIEW')
+                return get_callable(callback)(request, token)
                 
-                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.')
+            response = HttpResponseRedirect(callback+args)
+                
+        except oauth.OAuthError, err:
+            response = send_oauth_error(err)
+    else:
+        response = HttpResponse('Action not allowed.')
             
-        return response
+    return response
 
 def oauth_access_token(request):
     oauth_server, oauth_request = initialize_server_request(request)
     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)
-        
+
+import hmac
+import base64
+
 from django import forms
+from django.conf import settings
 
 class Form(forms.Form):
     pass
         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.URLField(widget=forms.HiddenInput)
+    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/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>

tests/test_project/apps/testapp/tests.py

                 http_url='http://testserver/api/oauth/authorize')
         request.sign_request(self.signature_method, oaconsumer, oatoken)
 
-        # Place the token into the client session...
-        session = self.client.session
-        session['oauth'] = oatoken.key
-        session.save()
+        # 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',
+#            })
+
+        from piston.forms import OAuthAuthenticationForm
+        from django.conf import settings
         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,
             })