# HG changeset patch # User ymh # Date 1358309362 -3600 # Node ID 1bee39cbd1eabcd2e30331e83a3eadc31f39a35f # Parent d8b2af693c6916dd458804e8300122b6bfbf7823 add authentication to roject resource diff -r d8b2af693c69 -r 1bee39cbd1ea src/ldt/ldt/api/ldt/authentication.py --- 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 diff -r d8b2af693c69 -r 1bee39cbd1ea src/ldt/ldt/api/ldt/resources/project.py --- 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() diff -r d8b2af693c69 -r 1bee39cbd1ea src/ldt/ldt/user/admin.py --- 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)