+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 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
+
+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
+
+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)
+ 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)
+