Well hello there!...have a look around my web portfolio...


Tastypie Authentication and Authorization

(Comments)

Tastypie has proven to be extremely customizable. Specifically important to the current implementation I am working on is the Authentication and Authorization.

For our Authentication we have two custom handlers AffiliateAuthentication and ClientAuthentication and then we fall back on BasicAuthentication as a last resort. The Affiliate Authentication piece is for 3rd Party Affiliates trying to access data and the Client Authentication piece is for Clients trying to access their own data. Basically in order to create a new Authentication piece you can use the following snippet:

from tastypie.authentication import ApiKeyAuthentication
from tastypie.http import HttpUnauthorized
from customtastypie.models import AffiliateApiKey


class MyAffiliateAuthentication(ApiKeyAuthentication):
    """
    Handles API key auth for affiliates.
    """
    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.")

            affiliate_id, api_key = data.split(':', 1)
        else:
            affiliate_id = request.GET.get('affiliate_id') \
                or request.POST.get('affiliate_id')
            api_key = request.GET.get('api_key') or request.POST.get('api_key')

        return affiliate_id, api_key

    def check_active(self, affiliate):
        return affiliate.is_active

    def is_authenticated(self, request, **kwargs):
        """
        Finds the affiliate and checks their API key.

        Should return either ``True`` if allowed, ``False`` if not or an
        ``HttpResponse`` if you need something custom.
        """
        try:
            affiliate_id, api_key = self.extract_credentials(request)
        except ValueError:
            return self._unauthorized()

        if not affiliate_id or not api_key:
            return self._unauthorized()

        try:
            affiliate = AffiliateApiKey.objects.get(id=affiliate_id)
        except (AffiliateApiKey.DoesNotExist,
                AffiliateApiKey.MultipleObjectsReturned):
            return self._unauthorized()

        if not self.check_active(affiliate):
            return False

        key_auth_check = self.get_key(affiliate, api_key)
        if key_auth_check and not isinstance(key_auth_check, HttpUnauthorized):
            request.affiliate = affiliate

        return key_auth_check

    def get_key(self, affiliate, api_key):
        """
        Attempts to find the API key for the affiliate.
        """
        try:
            AffiliateApiKey.objects.get(id=affiliate.id, key=api_key)
        except AffiliateApiKey.DoesNotExist:
            return self._unauthorized()

        return True

    def get_identifier(self, request):
        """
        Provides a unique string identifier for the requestor.

        This implementation returns the affiliate's id.
        """
        affiliate_id, api_key = self.extract_credentials(request)
        return affiliate_id

As for the authorization portion to ensure the correct users can access the correct data you can use the following format:

from tastypie.authorization import ReadOnlyAuthorization
from tastypie.exceptions import Unauthorized


class MyAuthorization(ReadOnlyAuthorization):
    """
    Handles authorization for affiliates and clients.
    """
    def read_list(self, object_list, bundle):
        """
        Only returns objects clients/affiliates are allowed to see.
        """
        try:
            affiliate = bundle.request.affiliate
        except AttributeError:
            affiliate = False

        try:
            client = bundle.request.client
        except AttributeError:
            client = False

        if affiliate:
            # Do special filtering on object_list here.
            return object_list
        elif client:
            # Do special filtering on object_list here.
            return object_list
        elif bundle.request.user:
            return object_list
        else:
            return []

    def read_detail(self, object_list, bundle):
        """
        Only returns objects affiliates/clients are allowed to see.
        """
        try:
            affiliate = bundle.request.affiliate
        except AttributeError:
            affiliate = False

        try:
            client = bundle.request.client
        except AttributeError:
            client = False

        if affiliate:
            # Can do extra permission checks here.
            return True
        elif client:
            # Can do extra permission checks here.
            return True
        elif bundle.request.user:
            # Returns true for actual system users.
            return True
        else:
            raise Unauthorized("You are not allowed to access that resource.")

    def update_detail(self, object_list, bundle):
        """
        Returns either ``True`` if the user is allowed to update the object in
        question or throw ``Unauthorized`` if they are not.
        Returns ``True`` by default.
        """
        if bundle.request.user:
            # Returns true for actual system users.
            return True
        else:
            raise Unauthorized("You are not allowed to access that resource.")

These can then both be used when defining your authentication and authorization in your api resource as follows:

from tastypie.resources import ModelResource


class MyResource(ModelResource):
    """
    My Resource
    """
    ...

    class Meta:
        ...
        authentication = MyAffiliateAuthentication()
        authorization = MyAuthorization()

Comments