Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Django REST Framework Security and Customization Permissions

Anders Prytz
Anders Prytz
26,193 Points

Problem with access for non superusers?

In the code the following solution is presented for the permission on DELETE request the none super users are not allowe to GET, POST etc after the last fix in the video. The following solution was to ensure that only superusers could delete courses:

class IsSuperUser(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.user.is_superuser:
            return True
        else:
            if request.method == 'DELETE':
                return False

class CourseViewSet(viewsets.ModelViewSet):
    permission_classes = (
        IsSuperUser,
        permissions.DjangoModelPermissions,
    )

After this I could not do any GET requests with any other user (with permissions for this). It seems that the IsSuperUser is only running and that there are not fallback to the permissions.DjangoModelPermissions.

I there a good way to gain access for the other such that non superusers could send a GET request with this setup? Should there be more code in the IsSuperUser class for handeling other permissions or is it something I am missing to get the DjangoModelPermissions to work if there are no returns from the IsSuperUser?

I'm also having the same issue! Would love to know of a fix that would allow non-superusers with valid token to send a GET request (among other things they are permissioned to do) .

2 Answers

Alex Koumparos
seal-mask
.a{fill-rule:evenodd;}techdegree
Alex Koumparos
Python Development Techdegree Student 36,887 Points

Hi Anders,

The way that the REST Framework's permissions work is that if any permissions check evaluates to False, permission will be denied, i.e., all the options are ANDed together. So given the following tuple (assume the individual permissions objects work like their names would suggest):

permission_classes = (IsSuperUser, permissions.DjangoModelPermissions)

If the user is a superuser, they will pass the first test (its test returns True). Since superusers have all permissions according to DjangoModelPermissions, they will also pass the second test (returns True). Since there has been no test that returned False, the user will pass authorisation and be allowed to perform the action.

If the user is not a superuser, they will fail the first test (its test returns False). Since we have a False, and False AND any value = False, the permissions system will raise an exception and permission will be denied.

Lots of people have a mental model where the tuple should represent a list of 'fallback' options (options are ORed together). This is not how the comma separators apply in this system. However, you can use the | operator to OR together permissions. Consider the following tuple:

permission_classes = (IsSuperUser|permissions.DjangoModelPermissions,)

Now, if the user is a superuser they pass the test. Since we have a True and any value OR True = True, permission is granted.

If the user is not a superuser the IsSuperUser will return False. But, if they do have permissions according to DjangoModelPermissions, then the DjangoModelPermissions test will return True. Since False OR True = True, permission will be granted. Only if the user is neither superuser nor approved according to DjangoModelPermissions will permission be denied.

Now that we know how to compose permissions: comma (or &) for AND, | for OR, let's consider what we want the behaviour to be:

  • if the method is DELETE, only allow superuser (regardless of whether they are permitted in the DjangoModelPermissions);
  • otherwise, apply DjangoModelPermissions

We can express this as:

(IsSuperuser OR NOT DELETE) AND DjangoModelPermissions

This becomes the following:

permission_classes = (SuperUserOrNotDelete, permissions.DjangoModelPermissions)

We can then write a permissions class to describe (IsSuperuser OR NOT DELETE):

class SuperUserOrNotDelete(permissions.BasePermission):

    def has_permission(self, request, view):
        if request.method != 'DELETE' or request.user.is_superuser:
            return True
        return False

Hope that clears everything up,

Cheers

Alex

There are several problems with this code. First, if the user is not a superuser and the method is not DELETE, has_permission will return None. This will evaluate to False when tested in a conditional expression. It's also strange that the class is called IsSuperUser when it clearly does more than test for superuser status — it also tests for the request method. I think the has_permission method should just test for superuser like this:

def has_permission(self, request, view):
    return request.user.is_superuser

If this works, then I'm not sure why the pre-defined IsAdminUser permission class is not good enough.

If we really do need to check the method, then I suggest renaming the class to something like IsSuperUserOrSafeMethod, and has_permission should work like this:

def has_permission(self, request, view):
    return request.user.is_useruser or request in self.SAFE_METHODS

Hope this helps.