Implement the oauth2 auth with tests

This commit is contained in:
nikolark committed 2013-12-12 23:30:30 +01:00
1 parent ec91fb6903
commit 16db46e496
7 files changed
+119 -109

No files matched your search

+3 -5
View File
@@ -12,13 +12,12 @@ from tastypie.resources import ModelResource, ALL
from tastypie.validation import CleanedDataFormValidation
from dusken.api.groupsbymember import GroupsByMemberResource
from dusken.authentication import MyApiKeyAuthentication, ServiceAuthentication
from dusken.authentication import ServiceAuthentication, OAuth20Authentication
from dusken.authorization import MyDjangoAuthorization
from dusken.models import *
from dusken.utils.api import generate_username, random_string
from dusken.forms import MemberCreateForm
class MemberResource(ModelResource):
"""
This class provides following endpoints:
@@ -30,7 +29,7 @@ class MemberResource(ModelResource):
resource_name = 'member'
list_allowed_methods = [ 'get' ]
detail_allowed_methods = [ 'get', 'patch', 'delete' ]
authentication = MyApiKeyAuthentication() # Who are you?
authentication = OAuth20Authentication() # Who are you?
authorization = MyDjangoAuthorization() # What are you allowed to do?
excludes = [ 'date_joined', 'password', 'is_active', 'is_staff', 'is_superuser', 'last_login' ]
filtering = {
@@ -95,7 +94,6 @@ class MemberResource(ModelResource):
try:
country = Country.objects.get(name=new_address['country'])
except Country.DoesNotExist, e:
# FIXME change to badrequest?
raise ImmediateHttpResponse(HttpForbidden(
'The country "{}" does not exist.'.format(new_address['country']))
)
@@ -167,7 +165,7 @@ class MemberCreateResource(ModelResource):
detail_allowed_methods = []
default_format = "application/json"
queryset = Member.objects.all()
authentication = ServiceAuthentication() # anyone
authentication = OAuth20Authentication() # anyone
authorization = Authorization() # can do what they want with anything
always_return_data = True
excludes = [ 'date_joined', 'password', 'is_active', 'is_staff', 'is_superuser', 'last_login' ]
+2 -2
View File
@@ -3,7 +3,7 @@ from dusken.api.member import MemberResource
from dusken.models import *
from tastypie import fields
from dusken.authorization import MyDjangoAuthorization
from dusken.authentication import MyApiKeyAuthentication
from dusken.authentication import OAuth20Authentication
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
@@ -21,7 +21,7 @@ class MembershipResource(ModelResource):
list_allowed_methods = [ 'get', 'post' ]
detail_allowed_methods = [ 'get', 'patch' ]
authorization = MyDjangoAuthorization()
authentication = MyApiKeyAuthentication()
authentication = OAuth20Authentication()
excludes = [ 'end_date']
filtering = {
'member' : ALL_WITH_RELATIONS,
+102 -2
View File
@@ -1,8 +1,12 @@
import logging
import provider.oauth2
from django.contrib.auth.models import AnonymousUser, User
from django.utils import timezone
from provider.oauth2.models import AccessToken, Client
from tastypie.authentication import ApiKeyAuthentication, Authentication
from tastypie.http import HttpUnauthorized
# TODO replace with Oauth2 http://django-tastypie.readthedocs.org/en/latest/cookbook.html#creating-a-full-oauth-2-0-api
class MyApiKeyAuthentication(ApiKeyAuthentication):
"""
Override method below to use our custom User model
@@ -53,3 +57,99 @@ class ServiceAuthentication(Authentication):
# return True
# return False
"""
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].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
def create_access_token(user):
# Create a Oauth2 Client (each test gets a new one)
oauth2_client = Client.objects.create(
user=user,
name="example.com testclient",
client_type=1, # ??
url="http://example.com")
# Create a Oauth2 AccessToken (each test gets a new one)
access_token = AccessToken(user=user, client=oauth2_client)
access_token.save()
# Return HTTP authorization header
return "Authorization: OAuth {}".format(access_token.token)
-90
View File
@@ -1,10 +1,4 @@
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
@@ -80,87 +74,3 @@ class MyDjangoAuthorization(DjangoAuthorization):
raise Unauthorized("You are not allowed to access that resource.")
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
+2
View File
@@ -122,6 +122,8 @@ INSTALLED_APPS = (
'south',
'django_extensions',
'corsheaders',
'provider',
'provider.oauth2',
)
+5 -4
View File
@@ -1,9 +1,10 @@
import logging
from tastypie.models import ApiKey
from tastypie.test import ResourceTestCase
from dusken.models import Address, Country, Member
from tastypie.test import ResourceTestCase
from dusken.utils.tests import test_fixtures_member
from dusken.authentication import create_access_token
class MemberTestBase(ResourceTestCase):
@@ -16,8 +17,8 @@ class MemberTestBase(ResourceTestCase):
# Get the preloaded member, which will be used for
# comparison with fetched object.
self.member = Member.objects.get(pk=2)
## create credentials
self.creds = self.create_apikey(username=self.member.username, api_key=ApiKey.objects.get(user=self.member).key)
# Create a OAuth2 Authorization HTTP header
self.creds = create_access_token(self.member)
# URIs to the member api
self.all_members_url = '/api/v1/member/'
+5 -6
View File
@@ -1,9 +1,10 @@
from tastypie.models import ApiKey
from dusken.authentication import create_access_token
from dusken.models import Member
from tastypie.test import ResourceTestCase
from dusken.utils.tests import test_fixtures_membership, do_get_request, do_post_request
from tastypie.models import ApiKey
from tastypie.test import ResourceTestCase
class MembershipTest(ResourceTestCase):
fixtures = test_fixtures_membership()
@@ -17,9 +18,7 @@ class MembershipTest(ResourceTestCase):
def setUp(self):
self.member = Member.objects.get(pk=2)
self.creds = self.create_apikey(
username=self.member.username,
api_key=ApiKey.objects.get(user=self.member).key)
self.creds = create_access_token(self.member)
super(MembershipTest, self).setUp()