Decoupling Permission-Check from Calling the View

90 views
Skip to first unread message

guettli

unread,
Apr 26, 2016, 9:17:37 AM4/26/16
to Django developers (Contributions to Django itself)
I would like to decouple the permission checking in django.

Current draw-back: If you use decorators like  [login_required][1], then you can't know in advance whether a user has the permission to do so or not.

I would like to split this into two steps:

 1. check permissions
 1. call the view.


# Use Case 1: Admin tool
I want an tool for admins where they can check the access-permissions of users. This requires:

 1. The check must not use the current `request.user` since this is the wrong user object.
 1. The check must not actually call the view, since this might alter data.

# Use Case 2: Show Link as disabled.

I want to show links as disabled (grayed out and without "href") if a user does not have the permission to see linked page.

# Dream
Returning a boolean for "ok" and "permission denied" is nice. But the big benefit would be if the admin could get a **reason**.

Example:

 1. Admin opens "Check Perm Tool"
 1. He selects a view/URL
 1. The admin hits "submit"


Result:

    ------------------------------
    | User     | Allowed | Reason
    ------------------------------
    | fooadmin |  Yes    | is_superuser
    | foouser  |  No     | missing permission "view-bar-at-midnight"
    | foobar   |  Yes    | User has permission "view-bar-at-midnight"

# Question
How to get this dream come true?


  [1]: https://docs.djangoproject.com/en/1.9/topics/auth/default/#django.contrib.auth.decorators.login_required







Alasdair Nicol

unread,
Apr 26, 2016, 10:27:32 AM4/26/16
to Django developers (Contributions to Django itself)
I haven't needed to explain why permission has been granted, but I have had admins asking me why a user is getting permission denied for a particular view. To answer that, you would

1. Get the url
2. Resolve that to a view
3. Look up the view in the correct views.py, and check which permissions the permissions_required decorator was using

We managed to automate 1. and 2. but not 3. 

In Django 1.9, the permission_denied view takes the exception as the second argument. If the permission_denied decorator included the permission names in the message when raising PermissionDenied, we would be able to display the list of permissions in the template. Even better, you could show the missing permissions, although that would require a larger patch.

Another option would be for the permission_required decorator to set a _permissions attribute on the decorated view, containing the list of permissions. A custom permission_denied view could then introspect the view.

cheers,
Alasdair

guettli

unread,
Apr 27, 2016, 4:55:57 AM4/27/16
to Django developers (Contributions to Django itself)


Am Dienstag, 26. April 2016 16:27:32 UTC+2 schrieb Alasdair Nicol:
I haven't needed to explain why permission has been granted, but I have had admins asking me why a user is getting permission denied for a particular view. To answer that, you would

1. Get the url
2. Resolve that to a view
3. Look up the view in the correct views.py, and check which permissions the permissions_required decorator was using

We managed to automate 1. and 2. but not 3. 

In Django 1.9, the permission_denied view takes the exception as the second argument. If the permission_denied decorator included the permission names in the message when raising PermissionDenied, we would be able to display the list of permissions in the template. Even better, you could show the missing permissions, although that would require a larger patch.


I am not 100% sure I understood you. Do you mean this:

1. Users/Browser sends http request to django
2. App checks the permissions
3. App denies the access
4. App renders a custom Permission-Denied page which includes the missing perms.
    Example: "You must not access this page since you don't have the permissions "See Guido naked"

I would like to have this, too. But security experts will tell you: "Don't show more than 'permission denied'. Otherwise
evil hackers get more information and systems get hacked more easily."

My steps are like this:

1, 2, 3 like above

4. App renders "permission denied. Ask you admin why you must not access the url https://example.com/...."
5. User goes to admin crying: "I must not access this url, but I want to see ...."
6. Admin enters username and URL in an admin tool, and there he sees: Ah, user is missing perm "See ....".
  One mouse-click by admin: Now you have the perm :-)



 

James Pic

unread,
Apr 27, 2016, 5:09:29 AM4/27/16
to django-d...@googlegroups.com
Hi all,

I agree with Thomas here, we shouldn't give any detail to the user
blocked because of permission configuration. We should however log
that somewhere like django-rules-light for the admin.

BTW This proposal looks great, keep up the good work B)

James

alasdai...@spatialbuzz.com

unread,
Apr 27, 2016, 6:22:53 AM4/27/16
to Django developers (Contributions to Django itself)
On Wednesday, April 27, 2016 at 9:55:57 AM UTC+1, guettli wrote:
Am Dienstag, 26. April 2016 16:27:32 UTC+2 schrieb Alasdair Nicol:
I haven't needed to explain why permission has been granted, but I have had admins asking me why a user is getting permission denied for a particular view. To answer that, you would

1. Get the url
2. Resolve that to a view
3. Look up the view in the correct views.py, and check which permissions the permissions_required decorator was using

We managed to automate 1. and 2. but not 3. 

In Django 1.9, the permission_denied view takes the exception as the second argument. If the permission_denied decorator included the permission names in the message when raising PermissionDenied, we would be able to display the list of permissions in the template. Even better, you could show the missing permissions, although that would require a larger patch.


I am not 100% sure I understood you. Do you mean this:

1. Users/Browser sends http request to django
2. App checks the permissions
3. App denies the access
4. App renders a custom Permission-Denied page which includes the missing perms.
    Example: "You must not access this page since you don't have the permissions "See Guido naked"

Yes, that's what I was trying to build. I would be able to do this in 1.9 if the decorator did (say)

    raise PermissionDenied(permission='See Guido naked')

instead of the current

    raise PermissionDenied

I would like to have this, too. But security experts will tell you: "Don't show more than 'permission denied'. Otherwise
evil hackers get more information and systems get hacked more easily."

I understand that, and I am not suggesting that we should show more than 'permission denied' by default. There was a similar discussion [1] for ticket 24733 when discussing whether or not the exception should be passed to the view. It's up to the developer what they do with the exception. If you were being cautious then you don't need to show the exception information to the user. You could log it, or send an email to the admin instead.
 
My steps are like this:

1, 2, 3 like above

4. App renders "permission denied. Ask you admin why you must not access the url https://example.com/...."
5. User goes to admin crying: "I must not access this url, but I want to see ...."
6. Admin enters username and URL in an admin tool, and there he sees: Ah, user is missing perm "See ....".
  One mouse-click by admin: Now you have the perm :-)

I'm not sure that this tool needs to be in Django itself. However, I think Django might need a couple of hooks to make it possible to build this tool.

If the login_required decorator set func._login_required = True, and permission_required set func._permissions_required = ['see_guido_naked'], then it would be possible to resolve a url to a view then introspect the view for the _login_required or _permissions_required.

This wouldn't work in all cases (e.g. if we called if has_perm() inside the view rather than using the decorator) but I think it would still be useful.

cheers,
Alasdair

Reply all
Reply to author
Forward
0 new messages