Archived
Implement the oauth2 auth with tests
This commit is contained in:
1 parent
ec91fb6903
commit
16db46e496
7 files changed
+119
-109
No files matched your search
@@ -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' ]
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -122,6 +122,8 @@ INSTALLED_APPS = (
|
||||
'south',
|
||||
'django_extensions',
|
||||
'corsheaders',
|
||||
'provider',
|
||||
'provider.oauth2',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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/'
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user