mirror of
https://github.com/kennethreitz-archive/django-piston-xauth.git
synced 2026-06-05 23:40:17 +00:00
323 lines
11 KiB
Python
323 lines
11 KiB
Python
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 django.views.decorators.csrf import csrf_exempt
|
|
|
|
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
|
|
|
|
@csrf_exempt
|
|
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
|
|
|
|
@csrf_exempt
|
|
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)
|
|
if token is None:
|
|
return HttpResponse('')
|
|
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)
|
|
|