diff --git a/dusken/authorization.py b/dusken/authorization.py index 208eff8..2b9d0f8 100644 --- a/dusken/authorization.py +++ b/dusken/authorization.py @@ -1,3 +1,11 @@ +import logging +import provider.oauth2 + +from django.http import HttpResponse +from django.contrib.auth.models import AnonymousUser, User +from django.utils import timezone +from provider.oauth2.models import AccessToken +from tastypie.authentication import Authentication from tastypie.authorization import DjangoAuthorization from tastypie.exceptions import Unauthorized @@ -73,3 +81,86 @@ class MyDjangoAuthorization(DjangoAuthorization): return True + +""" +This is a simple OAuth 2.0 authentication model for tastypie + +Copied nearly verbatim from amrox's example + - https://github.com/amrox/django-tastypie-two-legged-oauth + +Dependencies: + - django-oauth2-provider: https://github.com/caffeinehit/django-oauth2-provider + +Example: + - http://ianalexandr.com +""" + +# stolen from piston +class OAuthError(RuntimeError): + """Generic exception class.""" + def __init__(self, message='OAuth error occured.'): + self.message = message + + +class OAuth20Authentication(Authentication): + """ + OAuth authenticator. + + This Authentication method checks for a provided HTTP_AUTHORIZATION + and looks up to see if this is a valid OAuth Access Token + """ + def __init__(self, realm='API'): + self.realm = realm + + def is_authenticated(self, request, **kwargs): + """ + Verify 2-legged oauth request. Parameters accepted as + values in "Authorization" header, or as a GET request + or in a POST body. + """ + logging.info("OAuth20Authentication") + + try: + key = request.GET.get('oauth_consumer_key') + if not key: + key = request.POST.get('oauth_consumer_key') + if not key: + auth_header_value = request.META.get('HTTP_AUTHORIZATION') + if auth_header_value: + key = auth_header_value.split(' ')[1] + if not key: + logging.error('OAuth20Authentication. No consumer_key found.') + return None + """ + If verify_access_token() does not pass, it will raise an error + """ + token = verify_access_token(key) + + # If OAuth authentication is successful, set the request user to the token user for authorization + request.user = token.user + + # If OAuth authentication is successful, set oauth_consumer_key on request in case we need it later + request.META['oauth_consumer_key'] = key + return True + except KeyError, e: + logging.exception("Error in OAuth20Authentication.") + request.user = AnonymousUser() + return False + except Exception, e: + logging.exception("Error in OAuth20Authentication.") + return False + return True + +def verify_access_token(key): + # Check if key is in AccessToken key + try: + token = AccessToken.objects.get(token=key) + + # Check if token has expired + if token.expires < timezone.now(): + raise OAuthError('AccessToken has expired.') + except AccessToken.DoesNotExist, e: + raise OAuthError("AccessToken not found at all.") + + logging.info('Valid access') + return token diff --git a/dusken/settings-sample.py b/dusken/settings-sample.py index 68758b4..9f6ea8d 100644 --- a/dusken/settings-sample.py +++ b/dusken/settings-sample.py @@ -115,6 +115,8 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', + 'provider', + 'provider.oauth2', 'dusken', 'tastypie', 'south', diff --git a/dusken/urls.py b/dusken/urls.py index c919ad6..cdd4183 100644 --- a/dusken/urls.py +++ b/dusken/urls.py @@ -18,5 +18,6 @@ v1_api.register(InstitutionResource()) urlpatterns = patterns('', url(r'^admin/', include(admin.site.urls)), url(r'^api/', include(v1_api.urls)), - url(r'^authenticate/', 'dusken.views.authenticate') + url(r'^authenticate/', 'dusken.views.authenticate'), + url(r'oauth2/', include('provider.oauth2.urls', namespace='oauth2')), ) diff --git a/requirements.txt b/requirements.txt index e4b2e5e..4dcd8f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Django==1.5.4 django-cors-headers==0.12 django-extensions==1.2.2 +django-oauth2-provider==0.2.6 django-sphinxdoc==1.2.0 django-tastypie==0.10.0 psycopg2==2.5.1