Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tastypie : Authentication for GET and Anonymous for POST

I use Django/Tastypie to manage my user collection.

Is it possible to allow anonymous users to POST in the API (when creating a new user at some endpoint) and restrict authenticated users to GET only their own user, but not all the users ?

Thanks for your help.

like image 663
Thomas K Avatar asked Oct 24 '12 14:10

Thomas K


2 Answers

I found the easiest thing to do was subclass the Authentication class I'm using. Just override the is_authenticated method to return True when the method is POST.

class AnonymousPostAuthentication(BasicAuthentication):
    """ No auth on post / for user creation """

    def is_authenticated(self, request, **kwargs):
        """ If POST, don't check auth, otherwise fall back to parent """

        if request.method == "POST":
            return True
        else:
            return super(AnonymousPostAuthentication, self).is_authenticated(request, **kwargs)

I put my validation in a subclass of Validation and override is_valid.

I do the GET filtering the same way Sampson does it above.

like image 134
bkreed Avatar answered Nov 15 '22 18:11

bkreed


Yes, it's possible to do both.

Here's a simple example of how you would let an authenticated user GET only their own user JSON and not from all the other users: (Assuming you are using Django's built-in user infrastructure):

# In api.py (or resource.py):
...
from tastypie.resources import ModelResource
from tastypie import fields
from models import *
from django.contrib.auth.models import User, Group
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization
...

# REST endpoint for authenticating user accounts
class UserResource(ModelResource):

    class Meta:
        queryset = User.objects.all()
        resource_name = 'auth/user'
        list_allowed_methods = ['get']
        detail_allowed_methods = ['get']
        authentication = BasicAuthentication()
        authorization = DjangoAuthorization()

    def apply_authorization_limits(self, request, object_list):
        return object_list.filter(username=request.user)

And here is a simple example of how you would let an anonymous user POST to create a new user (Caveat: this doesn't use Tastypie, strictly speaking)

# In views.py:
...
from django.http import HttpResponse
from django.contrib.auth.models import User, Group
from django.contrib.auth import authenticate
from django.http import Http404
from django.utils import timezone
from models import *
from api import *
from django.utils import simplejson
...

# REST endpoint for user registration
# Enforces server-side mediation (input validation)
# On failure, raises Http404
# On success, redirects to registration success page
def register_user(request):
    if request.method != 'POST':
        raise Http404('Only POSTs are allowed')

    # acquire params
    username = request.POST['username']
    password = request.POST['password']
    repeatpw = request.POST['repeatpw']
    first_name = request.POST['first_name']
    last_name = request.POST['last_name']

    # Server-side mediation to check for invalid input
    if username == '' or username is None:
        raise Http404('Server-side mediation: Invalid Username')

    if len(username) > 30:
        raise Http404('Server-side mediation: username must be 30 characters or fewer')

    if len(first_name) > 30:
        raise Http404('Server-side mediation: first name must be 30 characters or fewer')

    if len(last_name) > 30:
        raise Http404('Server-side mediation: last name msut be 30 characters or fewer')

    if len(password) < 4:
        raise Http404('Server-side mediation: Password too short')

    if password != repeatpw:
        raise Http404('Server-side mediation: Password Mismatch')

    # This try-except block checks existence of username conflict
    try:
        test_user_exists = User.objects.get(username__exact=username)
        if test_user_exists != None:
            raise Http404('Server-side mediation: Username exists')
    except User.DoesNotExist:
        pass

    # Input passes all tests, proceed with user creation
    user = User.objects.create_user(username, '[email protected]', password)
    group = Group.objects.get(name='Standard')
    user.first_name = first_name
    user.last_name = last_name
    user.groups.add(group)
    user.is_staff = False    
    user.save()

    # Build confirmation JSON
    confirmation = {
            'action': 'register_user',
            'username': username,
            'success': 'yes',
    }

    json_return = simplejson.dumps(confirmation)

    # return JSON of the success confirmation
    return HttpResponse(json_return, mimetype='application/json')

Based on that, here's a script of how you might create a new user through the REST endpoint using curl:

#!/bin/bash
echo "Usage: ./register_user username password repeatpw first_name last_name"
curl -v -d "username=$1&password=$2&repeatpw=$3&first_name=$4&last_name=$5" http://127.0.0.1:8000/register_user/ > result
like image 31
sampson-chen Avatar answered Nov 15 '22 18:11

sampson-chen