Skip to content
This repository has been archived by the owner on Feb 18, 2019. It is now read-only.

Commit

Permalink
Added CSRF protection to OAuth authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
james-emerton committed Jun 1, 2009
1 parent 989d39b commit 96865c3
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 29 deletions.
1 change: 1 addition & 0 deletions .hgignore
Expand Up @@ -11,3 +11,4 @@ downloads
develop-eggs
.DS_Store
.svn
*.swp
57 changes: 32 additions & 25 deletions piston/authentication.py
Expand Up @@ -6,9 +6,12 @@
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):
"""
Expand Down Expand Up @@ -136,7 +139,12 @@ def oauth_request_token(request):
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):
Expand All @@ -156,35 +164,34 @@ 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)
Expand Down Expand Up @@ -278,4 +285,4 @@ def is_valid_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)

44 changes: 44 additions & 0 deletions piston/forms.py
@@ -1,4 +1,8 @@
import hmac
import base64

from django import forms
from django.conf import settings

class Form(forms.Form):
pass
Expand All @@ -17,3 +21,43 @@ def merge_from_initial(self):
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())

15 changes: 15 additions & 0 deletions piston/templates/piston/authorize_token.html
@@ -0,0 +1,15 @@
<!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>
14 changes: 10 additions & 4 deletions tests/test_project/apps/testapp/tests.py
Expand Up @@ -71,13 +71,19 @@ def test_handshake(self):
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,
})

Expand Down

0 comments on commit 96865c3

Please sign in to comment.