7

I am building a quite complex Django application to be used on top of and email scanning service. The Django application is written using Python 3.5+

This application primarily uses Django Rest Framework to handle communication with the frontend in the browser.

The issue that I am currently having is that I try to implement the concept of a System Administrator, Domain Administrator and Application User

The System Administrator is basically the "normal" django superuser and is therefore capable of doing everything and see every record in the system.

The Domain Administrator is user who manages one or more email domains. I keep track of this using a Many2Many relationship between the users and the domains. The idea would then be to predefine a filter, so that the log of messages processed, will be automatically filtered to show only messages where the sender domain or the recipient domain equal a domain in the list of domains that the given user is assigned to.

The same would be true for blacklisting/whitelisting policies.

If the Domain Administrator is not assigned to any domains, then no data is shown.

The Application User is basically any authenticated user with one or more domains assigned to them, using the same Many2Many relationship as the Domain Administrator. If no domains are assigned, then no data is shown.

I have found some other solution here on Stackoverflow on making the request.user available to the QuerySet in the ModelManager, but that does not seem like the correct way to handle it.

I have looked at django-guardian, django-authority and django-permissions, but none of them seem to be affecting the QuerySet or the resulting list of objects.

Does anyone have a suggestion for Django package/addon that can be used to handle this or maybe an idea for how this could be handled?

Kenneth_H
  • 141
  • 2
  • 13
  • Can't the deal with it in the view? – addohm May 18 '18 at 15:37
  • If it can be done inside a `ViewSet`, then yes. The entire `API` is using `ViewSets`. A `Permission` class would normally be an option, but according to the documentation, it does not modify the queryset or the results – Kenneth_H May 18 '18 at 16:00
  • Why not override `get_queryset`? Do you want to avoid writing custom `get_queryset` for each of these roles and have something generic? – rtindru May 18 '18 at 17:56

4 Answers4

4

I'm the author of django-cancan library https://github.com/pgorecki/django-cancan which strives to solve the exact problem you are describing.

The philosophy is as following: first, you determine per-user abilities, then in a view, you can check user abilities for a given object, model, or you can retrieve a queryset based on those abilities.

The declaration part looks like this:

def declare_abilities(user, ability):
    if not user.is_authenticated:
        # Allow anonymous users to view only published articles
        ability.can('view', Article, published=True)
    else:
        # logged in user can view any article...
        ability.can('view', Article)
        # ... and change his own
        ability.can('change', Article, author=user)
        # ... and add new ones
        ability.can('add', Article)

    if user.is_superuser:
        # Allow superuser to view and change any article
        ability.can('view', Article)
        ability.can('change', Article)

Then you can you can check for abilites on a per-object level:

def article_detail_view(request, pk):
    article = Article.objects.get(pk=pk)
    if request.ability.can('view', article):
        ...

or on a model level:

def article_create_view(request, pk):
    if request.ability.can('add', Article):
        ...

or get a queryset with accessible objects:

def another_list_view(request, pk):
    articles = request.ability.queryset_for('view', Article)
    ...
pgorecki
  • 639
  • 6
  • 8
2

DRF's GenericAPIView has a get_queryset method that you can override to perform custom filtering:

def get_queryset(self):
    qs = super(YourView, self).get_queryset()
    return self.filter_queryset_for_user(qs, request.user)


def filter_queryset_for_user(self, qs, user):
    pass  # Your logic here

This is not necessarily a bad idea; DRF docstrings recommend overriding this:

You may want to override this if you need to provide different querysets depending on the incoming request.

rtindru
  • 5,107
  • 9
  • 41
  • 59
  • This could be a solution, but I will need to do some testing, as I have some reporting functionality that allows the user to filter the data of certain tables based on parameters like the sender and recipient. – Kenneth_H May 20 '18 at 21:21
  • As one of the comment mentions, this is more of a filtering problem than a permissions problem. Lmk if the solution I proposed is helpful. – rtindru May 21 '18 at 09:07
0

I think you are misunderstanding the concept of permission in Django. django-guardian, django-authority and django-permissions these all packages are for handling permission inside your Django application. What permission does is it checks certain model or instance of model, if the user has permission to view that particular model or object, otherwise it will return 403 Unauthorized response. Permission does not change or filter your queryset to return only the valid results.

Rather if you want to apply filter your queryset, you can do so by the above answer, or you can move that code to a Mixin to follow DRY Style. For Mixin Reference you can see this link:

SK. Fazlee Rabby
  • 344
  • 4
  • 14
0

My answer to this question also provides an alternative to filter your queryset by subclassing rest_framework.filters.BaseFilterBackend and implement filter_queryset() based on your permission pattern , which would be suitable for more complicated use cases.

Ham
  • 703
  • 8
  • 17