doubt about api design for users with special case

Hey there,

I have a project that uses the default authentication system and besides normal Users there is a special kind of user called “Agent” that is basically a normal User with a specific permission attached.

Now firstly, I will have an endpoint /users that requires the permission can_view_users that returns all the users.

Secondly I will have a permission can_view_agents that means this person should be able to see only the users that have the permission that makes them agents.

With the situation above the doubt is if it’s correct to have the /users endpoint handle both cases, so in the view logic it would check the permission of the user requesting and would return results based on that permission. So a user with can_view_users would receive a certain result meanwhile an user with can_view_agents would receive a different one when calling GET /users. If the user with the first permission would want to get the result of the second it could probably call GET /users?agents=True.

I wonder if this is a good design because implementing it doesn’t feel clean to have the same /users endpoint returning different results based on permission althou I could be wrong.

A possible alternative that I thought of was having the users endpoint as described in the beginning and then a new endpoint /agents that would return the users that have the permission that make them agents and subsequent paths would only do work on this kind of users.
This means that who has the permission can_view_agents would be only able to access the /agents endpoint and not the /users one nor it has to know it exists.
Would this be a good design or incorrect that both those endpoint would exists ?

I appreciate your feedback,
Cheers

Hello,

I like this question and it is something which I have also thought about. I don’t have a straight answer for you, but I admit that I do a similar thing in my project, but not identical to what you are asking.

I have a users DRF viewset which returns either a single user representing the user object of the person making the request, or, if the user.is_admin == True then I return a list of all users. Doing this in DRF is relatively straightforward. Here is an example of my permission and my view.

# my IsUser permission
class IsUser(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.user:
            if request.user.is_superuser:
                return True
            else:
                return obj == request.user
        else:
            return False

# my IsSuperUser permission
class IsSuperUser(BasePermission):
    def has_permission(self, request, view):
        return request.user and request.user.is_superuser

# my users viewset
class UserViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]

    def get_permissions(self):
        if self.action == "list":
            self.permission_classes = [IsSuperUser, IsAuthenticated]
        elif self.action == "retrieve":
            self.permission_classes = [IsUser, IsAuthenticated]

        return super(self.__class__, self).get_permissions()

If anyone sees anything that is wrong with the above, please don’t hesitate to say so.

Thankfully, DRF handles a lot of the logic here and I’m comfortable with DRF’s maturity and the simple view + permissions that I’m happy to go down this path as it meets the needs of the particular app I am developing. I should also note that this endpoint is a read-only endpoint and that I have another endpoint for writing to the user object which is exclusively available to admins. This at least separates the concerns a little.

I also think that it is ideal to keep one’s views as simple as can be. Therefore I would think if you can have a logic that is relatively simple, then why not represent the single User object at a single endpoint?

I would also think that the key question to ask yourself is: Do you have control over the use of the API? i.e. is it being used by an app/frontend over which you have control, or is it a publicly accessible API?

If the former, then it is straightforward to direct one part of the app to /users and the other to /view_user_agents and you then maintain separation of concerns, which is also a win.

Apologies if that doesn’t answer your question, but I certainly think it is an interesting question and I would only be too happy to also hear what others think.

I’ll chime in here with my two cents…

Personally, I don’t think it really matters either way. There’s nothing in this description that says “right” or “wrong” for either design. That leads me to believe that you want to look at this decision in the context of the other API endpoints being created.

If I were creating something like this using the default page-style Django (not using DRF), I would be using two separate CBVs accessed via two separate URLs. (It’s quite possible that I might create the ListView for /agents as a subclass of the ListView for /users, just adding the additional security check and QuerySet filter.)

But, in this case, you’re building this behind an API - which assumes some type of front-end or API access code that is going to be accessing this. In this case, I’d be wanting to look at this holistically - how does this pattern fit in with the rest of the application? Does this need to add the query parameter ?agents=True create a logical wart in my API? Or does it seem to fit with the other API endpoints being created.

Ken

Both replies helped me get a better understanding of the problem and that it is mostly a design choice, each one correct and each one with their pro and cons.

Having the /agents endpoint definitely helps with making a separation of concerns and keeping their custom logic specific to them and to not taint the /users endpoint. It also would allow to have endpoints such as /agents/id/events and similiar that would have less sense on normal users.

Thank you