--- a/src/ldt/ldt/api/ldt/authentication.py Mon Jan 14 15:48:45 2013 +0100
+++ b/src/ldt/ldt/api/ldt/authentication.py Wed Jan 16 05:09:22 2013 +0100
@@ -2,6 +2,12 @@
from django.middleware.csrf import _sanitize_token, constant_time_compare
from django.utils.http import same_origin
from tastypie.authentication import Authentication
+from tastypie.http import HttpUnauthorized
+from django.contrib.auth.models import User
+from django.contrib.auth import login
+from ldt.security import set_current_user
+
+
# imported from tastypie's next version 0.9.12
class SessionAuthentication(Authentication):
@@ -58,4 +64,138 @@
if request.user:
return request.user.username
else:
- return "anon."
\ No newline at end of file
+ return "anon."
+
+# imported from tastypie's next version 1.0.0
+class MultiAuthentication(object):
+ """
+ An authentication backend that tries a number of backends in order.
+ """
+ def __init__(self, *backends, **kwargs):
+ super(MultiAuthentication, self).__init__(**kwargs)
+ self.backends = backends
+
+ def is_authenticated(self, request, **kwargs):
+ """
+ Identifies if the user is authenticated to continue or not.
+
+ Should return either ``True`` if allowed, ``False`` if not or an
+ ``HttpResponse`` if you need something custom.
+ """
+ unauthorized = False
+
+ for backend in self.backends:
+ check = backend.is_authenticated(request, **kwargs)
+
+ if check:
+ if isinstance(check, HttpUnauthorized):
+ unauthorized = unauthorized or check
+ else:
+ request._authentication_backend = backend
+ return check
+
+ return unauthorized
+
+ def get_identifier(self, request):
+ """
+ Provides a unique string identifier for the requestor.
+
+ This implementation returns a combination of IP address and hostname.
+ """
+ try:
+ return request._authentication_backend.get_identifier(request)
+ except AttributeError:
+ return 'anon.'
+
+class ApiKeyAuthentication(Authentication):
+ """
+ Handles API key auth, in which a user provides a username & API key.
+
+ Uses the ``ApiKey`` model that ships with tastypie. If you wish to use
+ a different model, override the ``get_key`` method to perform the key check
+ as suits your needs.
+ """
+ def __init__(self, require_active=True):
+ self.require_active = require_active
+
+ def _unauthorized(self):
+ return HttpUnauthorized()
+
+ def extract_credentials(self, request):
+ if request.META.get('HTTP_AUTHORIZATION') and request.META['HTTP_AUTHORIZATION'].lower().startswith('apikey '):
+ (auth_type, data) = request.META['HTTP_AUTHORIZATION'].split()
+
+ if auth_type.lower() != 'apikey':
+ raise ValueError("Incorrect authorization header.")
+
+ username, api_key = data.split(':', 1)
+ else:
+ username = request.GET.get('username') or request.POST.get('username')
+ api_key = request.GET.get('api_key') or request.POST.get('api_key')
+
+ return username, api_key
+
+ def is_authenticated(self, request, **kwargs):
+ """
+ Finds the user and checks their API key.
+
+ Should return either ``True`` if allowed, ``False`` if not or an
+ ``HttpResponse`` if you need something custom.
+ """
+
+ try:
+ username, api_key = self.extract_credentials(request)
+ except ValueError:
+ return self._unauthorized()
+
+ if not username or not api_key:
+ return self._unauthorized()
+
+ try:
+ user = User.objects.get(username=username)
+ except (User.DoesNotExist, User.MultipleObjectsReturned):
+ return self._unauthorized()
+
+ if not self.check_active(user):
+ return False
+ user.backend = "django.contrib.auth.backends.ModelBackend"
+ request.user = user
+ login(request,user)
+ set_current_user(user)
+ return self.get_key(user, api_key)
+
+ def check_active(self, user):
+ """
+ Ensures the user has an active account.
+
+ Optimized for the ``django.contrib.auth.models.User`` case.
+ """
+ if not self.require_active:
+ # Ignore & move on.
+ return True
+
+ return user.is_active
+
+
+ def get_key(self, user, api_key):
+ """
+ Attempts to find the API key for the user. Uses ``ApiKey`` by default
+ but can be overridden.
+ """
+ from tastypie.models import ApiKey
+
+ try:
+ ApiKey.objects.get(user=user, key=api_key)
+ except ApiKey.DoesNotExist:
+ return self._unauthorized()
+
+ return True
+
+ def get_identifier(self, request):
+ """
+ Provides a unique string identifier for the requestor.
+
+ This implementation returns the user's username.
+ """
+ username, api_key = self.extract_credentials(request)
+ return username or 'nouser'
\ No newline at end of file
--- a/src/ldt/ldt/api/ldt/resources/project.py Mon Jan 14 15:48:45 2013 +0100
+++ b/src/ldt/ldt/api/ldt/resources/project.py Wed Jan 16 05:09:22 2013 +0100
@@ -3,7 +3,7 @@
from django.contrib.auth.models import Group
from guardian.shortcuts import assign
from ldt.ldt_utils.models import Project
-from ldt.api.ldt.authentication import SessionAuthentication
+from ldt.api.ldt.authentication import SessionAuthentication, MultiAuthentication, ApiKeyAuthentication
from ldt.api.ldt.serializers.cinelabserializer import CinelabSerializer
from ldt.api.ldt.resources import ContentResource
from ldt.api.ldt.resources.user import UserResource
@@ -17,9 +17,9 @@
contents = fields.ManyToManyField(ContentResource, 'contents')
owner = fields.ForeignKey(UserResource, 'owner')
class Meta:
- allowed_methods = ['get', 'post']
+ allowed_methods = ['get', 'post', 'put']
authorization = Authorization() # BE CAREFUL WITH THAT, it's unsecure
- authentication = SessionAuthentication()
+ authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
resource_name = 'projects'
queryset = Project.objects.all()
serializer = CinelabSerializer()
--- a/src/ldt/ldt/user/admin.py Mon Jan 14 15:48:45 2013 +0100
+++ b/src/ldt/ldt/user/admin.py Wed Jan 16 05:09:22 2013 +0100
@@ -6,6 +6,9 @@
from forms import LdtForm
from guardian.admin import GuardedModelAdmin
from models import Ldt, UserProfile, GroupProfile
+from tastypie.admin import ApiKeyInline
+from tastypie.models import ApiAccess, ApiKey
+
class GroupProfileInline(admin.StackedInline):
model = GroupProfile
@@ -38,7 +41,7 @@
form = LdtForm
model = Ldt
filter_horizontal = ('user_permissions',)
- inlines = [UserProfileInline, ]
+ inlines = [UserProfileInline, ApiKeyInline]
def get_fieldsets(self, request, obj=None):
fieldsets = deepcopy(self.fieldsets)
@@ -58,5 +61,8 @@
admin.site.unregister(Ldt)
admin.site.register(Ldt, LdtAdmin)
+admin.site.register(ApiKey)
+admin.site.register(ApiAccess)
+
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)