[PATCH] Require OAuth and patch piston to do OAuth properly
- From: Stuart Langridge <aquarius dell-desktop example com>
- Subject: [PATCH] Require OAuth and patch piston to do OAuth properly
- Date: Fri, 5 Jun 2009 11:20:04 +0100
---
api/urls.py | 5 +-
lib/piston/authentication.py | 116 ++++++++++++++++++++++++++----------------
lib/piston/forms.py | 44 ++++++++++++++++
notes/views.py | 10 ++++
settings.py | 1 +
urls.py | 8 +++
6 files changed, 138 insertions(+), 46 deletions(-)
diff --git a/api/urls.py b/api/urls.py
index 060dd42..02df555 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -17,13 +17,14 @@
from django.conf.urls.defaults import *
-from piston.authentication import HttpBasicAuthentication
+from piston.authentication import HttpBasicAuthentication, OAuthAuthentication
from piston.resource import Resource
from snowy.api.handlers import *
auth = HttpBasicAuthentication(realm='Snowy')
-ad = {'authentication': auth}
+authoauth = OAuthAuthentication(realm='Snowy')
+ad = {'authentication': authoauth}
user_handler = Resource(UserHandler)
notes_handler = Resource(handler=NotesHandler, **ad)
diff --git a/lib/piston/authentication.py b/lib/piston/authentication.py
index e3fcdaf..2f325fa 100644
--- a/lib/piston/authentication.py
+++ b/lib/piston/authentication.py
@@ -1,29 +1,26 @@
from django.http import HttpResponse, HttpResponseRedirect
-from django.contrib.auth.models import User
+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 django.utils.importlib import import_module
import oauth
-from store import DataStore
+from piston import forms
-def django_auth(username, password):
+class NoAuthentication(object):
"""
- Basic callback for `HttpBasicAuthentication`
- which checks the username and password up
- against Djangos built-in authentication system.
-
- On success, returns the `User`, *not* boolean!
+ Authentication handler that always returns
+ True, so no authentication is needed, nor
+ initiated (`challenge` is missing.)
"""
- try:
- user = User.objects.get(username=username)
- if user.check_password(password):
- return user
- else:
- return False
- except User.DoesNotExist:
- return False
+ def is_authenticated(self, request):
+ return True
class HttpBasicAuthentication(object):
"""
@@ -39,7 +36,7 @@ class HttpBasicAuthentication(object):
This will usually be a `HttpResponse` object with
some kind of challenge headers and 401 code on it.
"""
- def __init__(self, auth_func=django_auth, realm='API'):
+ def __init__(self, auth_func=authenticate, realm='API'):
self.auth_func = auth_func
self.realm = realm
@@ -57,9 +54,10 @@ class HttpBasicAuthentication(object):
auth = auth.strip().decode('base64')
(username, password) = auth.split(':', 1)
- request.user = self.auth_func(username, password)
+ request.user = self.auth_func(username=username, password=password) \
+ or AnonymousUser()
- return not request.user is False
+ return not request.user in (False, None, AnonymousUser())
def challenge(self):
resp = HttpResponse("Authorization Required")
@@ -67,6 +65,28 @@ class HttpBasicAuthentication(object):
resp.status_code = 401
return resp
+DataStore = None
+
+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(module)
+ 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
+
def initialize_server_request(request):
"""
Shortcut for initialization.
@@ -77,6 +97,10 @@ def initialize_server_request(request):
query_string=request.environ.get('QUERY_STRING', ''))
if oauth_request:
+ global DataStore
+ if DataStore is None:
+ DataStore = load_data_store()
+
oauth_server = oauth.OAuthServer(DataStore(oauth_request))
oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
@@ -115,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):
@@ -135,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)
@@ -257,4 +285,4 @@ class OAuthAuthentication(object):
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)
-
\ No newline at end of file
+
diff --git a/lib/piston/forms.py b/lib/piston/forms.py
index 727f997..8f1f1d7 100644
--- a/lib/piston/forms.py
+++ b/lib/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
@@ -17,3 +21,43 @@ class ModelForm(forms.ModelForm):
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())
+
diff --git a/notes/views.py b/notes/views.py
index 8eb3977..ba722ec 100644
--- a/notes/views.py
+++ b/notes/views.py
@@ -24,6 +24,8 @@ from snowy.notes.templates import CONTENT_TEMPLATES, DEFAULT_CONTENT_TEMPLATE
from snowy.notes.models import *
from snowy import settings
+from piston import forms as piston_forms
+
def note_index(request, username,
template_name='note/note_index.html'):
user = get_object_or_404(User, username=username)
@@ -86,3 +88,11 @@ def note_detail(request, username, note_id, slug='',
'all_notes': all_notes,
'all_notebooks': all_notebooks},
context_instance=RequestContext(request))
+
+def note_oauth_auth_view(request, token, callback, params):
+ form = piston_forms.OAuthAuthenticationForm(initial={
+ 'oauth_token': token.key,
+ 'oauth_callback': callback,
+ })
+ return render_to_response('notes/note_authorize_token.html',
+ { 'form': form }, RequestContext(request))
diff --git a/settings.py b/settings.py
index ef7ec1e..0b31b89 100644
--- a/settings.py
+++ b/settings.py
@@ -99,6 +99,7 @@ INSTALLED_APPS = (
'reversion',
'gravatar',
'autoslug',
+ 'piston',
# Local apps
'notes',
diff --git a/urls.py b/urls.py
index c39ce8c..cb797a0 100644
--- a/urls.py
+++ b/urls.py
@@ -47,3 +47,11 @@ if settings.DEBUG:
'document_root': settings.MEDIA_ROOT,
'show_indexes': True
}))
+
+# OAuth
+urlpatterns += patterns('piston.authentication',
+ url(r'^oauth/request_token/$', 'oauth_request_token'),
+ url(r'^oauth/authenticate/$', 'oauth_user_auth'),
+ url(r'^oauth/access_token/$', 'oauth_access_token'),
+)
+
--
1.6.0.4
--------------030909080107080905060704--
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]