For authorization backends checking object level permissions (like guardian) usually requires calling the django's default authorization backend as a fallback to the more general set of permissions:
if user.has_perm('foo.change_bar', obj=bar) or user.has_perm('foo.change_bar'): ...
However, this not only looks ugly, but also requires polling of all the backends twice, and thus, is a performance loss.
First, and possibly the best, solution to this is that, django does not deny permission if obj argument is provided, but just ignores it. This is also very logical, one who has a permission for the entire model/table, would also have it for an instance/row. This way by properly ordering backends in the settings, it could be a fallback solution for the lower level checkers. This might be the move in the right direction, although it is backwards incompatible.
A second solution is a keyword argument, such as fallback_to_model=None
, that will allow lower-level checkers mimic the model level permissions that django does. Obviously, this is not DRY. But is needed if the first solution is not accepted to get the necessary permissions with one round of polling, and without cluttering the code. If it was accepted, it would still be a useful addition since it would allow backends to prefer to handle the fallback by themselves. Or, it would allow users who fallback by default override that behavior and not fallback (via a value of False
), i.e., when object level permissions are definitive.
Here is what I propose in terms of working around the backward compatibility that seems to have kept it from being solved for so long.
1) define a global setting, say: OBJECT_PERMISSION_FALLBACK_TO_MODEL=False
. This is to help maintain the default behavior (unless the setting is changed of course).
2) (as mentioned in the above comment) define a keyword argument at the method level for occasional override, say: fallback_to_model=None
. Default value of None
means it will be ignored in favor of the global setting, otherwise, it will take precedence.
I can work on a patch if found reasonable.
def has_perm(self, user_obj, perm, obj=None, fallback_to_model=None)
def get_group_permissions(self, user_obj, obj=None, fallback_to_model=None)
def get_all_permissions(self, user_obj, obj=None, fallback_to_model=None)this is the backward incompatible part
Seems like I found a better keyword argument than fallback_to_model
. For the following backends setting:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectBackend',
'roles.backends.RoleBackend',
)
And the ways to check:
user.has_perm('foo.change_bar', obj)
# default: check all, model checker expected to NOT disown her child
user.has_perm('foo.change_bar', obj, backends=('object', 'role', ))
# just check backends specified; 'role' for *.RoleBackend
Advantages:
RoleBackend
and can easily make calls to model or object permission backends, in order not to reinvent the wheel.fallback_to_model
; thus backward compatible there.The objection is to this kind of check:if user.has_perm('foo.change_bar', obj=bar) or user.has_perm('foo.change_bar'):...
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/242593f2-b218-43b7-bb17-bd5ca7e2634f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Florian,Can you clarify this part, I am not sure what you meant:
Hi Carlton,
Thanks for the thoughts. I just wanted to share my opinion on
your options.
1. "Won't Fix"
I have yet to find anywhere the original design decisions were
documented. Based on what I can find, it appears that object level
permissions where a bit of an after-though for the initial auth
framework. Unless we can find the motivation for the original
design decision it seems foolish to leave unexpected
behaviour simply because that's how it has always been.
I understand the motivation for wanting to only check object level
permissions, but I would argue that the design of the API
insinuates a hierarchical behaviour which is part of the reason
the current behaviour is unexpected.
Specifically why use an 'obj' kwarg instead of having two methods?
To me if the API where:
```
has_perm('foo.change_bar')
has_obj_perm('foo.change_bar', bar_obj)
```
I would expect `has_perm` to only deal with model level
permissions and `has_obj_perm` to only deal with object level
permissions
To me the current API:
```
has_perm('foo.change_bar', obj=bar_obj)
```
Implies that the method will always check model level permissions
and can optionally check object level permissions.
Having to always call `if user.has_perm('foo.change_bar', obj=bar)
or user.has_perm('foo.change_bar')` both seems ugly, and relies on
backend authors following ModelBackend's example of caching
permission look-ups. Otherwise there may be a performance cost.
The AUTH_BACKEND system's strength is the ability to plug in
third-party and custom backends, it seems dangerous to make
assumptions based on ModelBackend unless those assumptions are
clearly documented.
2. Change ModelBackend
Initially this would have been my preferred option. However having
given the issue significant thought, I think that it is important
that users are still given the option of the current behaviour.
Having an API return only object level permissions when called
with obj, while returning only model level permissions when called
without obj is a legitimate use case.
Which leads me to
3. Break out permissions aspects.
I agree this would require some planning for migration. It also
requires discussion on whether the desire would be to separate
authentication from authorization or if the auth framework should
just offer more default options.
However overall I think this is the best option. It leverages the
flexibility of the backend system and offers the option of
maintaining BC if desired.
For example it would make sense to me to plan on restructuring the
auth.backends to something like:
```
ModelAuthenticationBackend(object):
# defines get_user and authenticate
PermissionAuthorizationBackend(object):
# defines get_user_permissions, get_group_permissions,
get_all_permissions, has_perm, has_module_perms
# 'New' behaviour: checks model level permissions even
when obj is None
ModelOnlyPermissionAuthorizationBackend(object):
# defines get_user_permissions, get_group_permissions,
get_all_permissions, has_perm, has_module_perms
# Current behaviour: checks model level permissions only
when obj is Non
ModelPermissionBackend(ModelAuthenticationBackend,
PermissionAuthorizationBackend):
pass
ModelBackend(ModelAuthenticationBackend,
ModelOnlyPermissionAuthorizationBackend):
pass
```
This offers users more options for their
authorization/authentication needs, and should make migration
easier as you could gradually depreciate the name 'ModelBackend'
in favour of something like 'ModelPermissionOnlyBackend'
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/group/django-developers.
To view this discussion on the web visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/msgid/django-developers/cab981b8-7dc7-4e9d-9dcc-442b36820cdf%40googlegroups.com.
For more options, visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/optout.
Carlton,
First, thanks for stirring the conversation. Can you give an example of what you mean by option 3. The comment you linked did not have much detail. Thanks,
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/cab981b8-7dc7-4e9d-9dcc-442b36820cdf%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Although I found it very interesting at first, this looks dangerous since it changes how the API work for all apps installed. Example, guardian makes calls to user.has_perm(perm) in several places to check model permissions. Although periphery features, they would be broken.
From: Florian Apolloner
Sent: Wednesday, January 17, 2018 12:45 PM
To: Django developers (Contributions to Django itself)
Subject: Re: Default Authorization BackEnd Denying Permissions if ObjectProvided
On Wednesday, January 17, 2018 at 5:48:03 PM UTC+1, Mehmet Dogan wrote:
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/ff0142a5-cca0-4d8c-8497-b988b04ba4ca%40googlegroups.com.
Can you give an example of what you mean by option 3.
Or, extend ModelBackend to override the object-denying behavior, and provided that as an option. Am I getting you right?
Seems like options for changing the global behavior is growing. However, I still think there is need for local override. Otherwise, re-usable apps will have to adopt either of the global defaults, and so will the app developers, since it will not be possible to mix the two exclusive groups.
From: Carlton Gibson
Sent: Wednesday, January 17, 2018 2:04 PM
To: Django developers (Contributions to Django itself)
Subject: Re: Default Authorization BackEnd Denying Permissions ifObjectProvided
Hi.
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/fc4b4544-dd15-4672-8a5b-27e9bcb7d4a9%40googlegroups.com.
Given your comment, would it be along the lines of "Close as "Won't Fix", perhaps with a review of the documentation", to point users to subclassing ModelBackend if they need the alternate behaviour?
Although I found it very interesting at first, this looks dangerous since it changes how the API work for all apps installed. Example, guardian makes calls to user.has_perm(perm) in several places to check model permissions. Although periphery features, they would be broken.
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/6f2b5df3-84dd-4bc2-b945-4cc80a838372%40googlegroups.com.
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/cfcefc08-e8a6-71d3-6269-31ccd9eaa52d%40linear-systems.com.
user.has_perm('foo.change_bar', obj, backends=('object', 'role', 'model'))
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/c981a74f-4dd1-418f-b1f1-a2d6d383466c%40googlegroups.com.
Mehmet,
Can you explain to me what the situation would be that an app
could simply not be backend agnostic?
Given that AUTHENTICATION_BACKENDS is a user
configuration, it seems odd that an app would ever design around
specific backends being installed. I'm not sure I follow your
logic on this.
Visit this group at MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/group/django-developers.
To view this discussion on the web visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/msgid/django-developers/cab981b8-7dc7-4e9d-9dcc-442b36820cdf%40googlegroups.com.
For more options, visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/optout.
--
Andrew Standley
Senior Software Engineer
Linear Systems
(909) 899-4345 *225
asta...@linear-systems.com
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
To view this discussion on the web visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/msgid/django-developers/cfcefc08-e8a6-71d3-6269-31ccd9eaa52d%40linear-systems.com.
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/group/django-developers.
To view this discussion on the web visit MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: MailScanner has detected definite fraud in the website at "groups.google.com". Do not trust this website: https://groups.google.com/d/msgid/django-developers/CAFdefwM82z1MRfPh%3Dgaq0k%3D7X3dfaFMuPyPYzGA1Ggtjydmtug%40mail.gmail.com.
If that is the way forward, I will volunteer for the code. But, as you also point out, there needs to be a path out. That I don't know :)
class Role(models.Model):
delegate = models.OneToOneField(Permission, ...)
perms = models.ManyToManyField(Permission, ...) ...
Delegate is a permission for each role that lets a user use it, e.g., 'roles.use_role_xxxxxx'; and the `perms` are the actual permission in the Role. And my backend (pseudo code):
def has_perm(self, user_obj, perm, obj=None):
if perm is delegate: ignore;delegates = get_delegates_representing(perm)for delegate in delegates:
if user_obj.has_perm(delegate, obj): # refer to existing other backends
return True
return False1) Now, as seen above user_obj.has_perm(delegate, obj) is written for the proposed way, and would not work with the current way as model permissions would be excluded.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/5ac40a89-55a6-4f98-f9cf-0a7d371c45e8%40linear-systems.com.
Hey Mehmet,
If a backend relies on PermissionAuthorizationBackend, and another require the ModelOnlyPermissionAuthorizationBackend
So I think this is the point that confuses me. Why you would
use the user API if you cared about a specific backend?
Using your example of the RolesBackend, either
A) You want to leave it up to the user whether a role grants
object level permissions or not.
B) You want to have consistent behavior for your backend.
Explanation:
A) In this case you would use your pseudo code example.
To return true (proposed) for an object when a user has the model
level permission (ie a role with this permission) the user would
configure their setting so
```
AUTHENTICATION_BACKENDS = [
'PermissionAuthorizationBackend',
'RolesBackend',
]
```
To return false (current) for an object when a user has the model
level permission the user would configure their setting so
```
AUTHENTICATION_BACKENDS = [
'ModelOnlyPermissionAuthorizationBackend',
'RolesBackend',
]
```
In either case the backend order does not particularly matter for
this example.
B) In this case the backend should derive or call the backend
whose behaviour it requires.
```
class RolesBackend(PermissionAuthorizationBackend):
def has_perm(self, user_obj, perm, obj=None):
...
for delegate in delegates:
if super(RolesBackend, self).has_perm(user_obj,
perm, obj):
return True
return False
```
Cheers,
Andrew
Andrew,
> Why you would use the user API if you cared about a specific backend?
True. I wouldn’t.
> Using your example of the RolesBackend, either
> A) You want to leave it up to the user whether a role grants object level permissions or not.
> B) You want to have consistent behavior for your backend.
Seemed like I wanted consistent behavior without relying on any particular backend. But, I see this restricts the design choices elsewhere or for others. Whether advantages outweigh disadvantages, or vice versa, I am not sure.
I was envisioning that one who uses multiple backends, say at least 3, would get everything needed through the common API, as well as seamless reusability of backends within each other.
But your scenario B gave me some ideas for backup. Thanks 😊
Mehmet
From: Andrew Standley
Sent: Thursday, January 18, 2018 1:29 PM
To: django-d...@googlegroups.com
Subject: Re: Default Authorization BackEnd Denying Permissions if ObjectProvided
Hey Mehmet,
Andrew,
After further thought I came to think that, even if multiple options kept in the framework forever, the disadvantages will not be considerable. I still think it will limit inter-changeability (what is the word for this?), but most people/backends would follow the one that makes more sense, so it will should not be an issue.
Sent from Mail for Windows 10
Hey Carlton,
I think everybody said what they would. What do you say?
I propose throwing out the proposed behavior as Option B, and see what happens. Something along these lines:
class ModelBackendIgnoreObject(ModelBackend):
def get_user_permissions(self, user_obj, obj=None):
if obj is None:
obj = "NOT_NONE"
super().get_user_permissions(user_obj, obj)
def get_group_permissions(self, user_obj, obj=None):
if obj is None:
obj = "NOT_NONE"
super().get_group_permissions(user_obj, obj)
def get_all_permissions(self, user_obj, obj=None):
if obj is None:
obj = "NOT_NONE"
super().get_all_permissions(user_obj, obj)
def has_perm(self, user_obj, perm, obj=None):
if obj is None:
obj = "NOT_NONE"
super().has_perm(user_obj, obj)
By the time, I am getting acclimated to the idea of "do not fix" but I think this is good for new comers to the framework, and general population.
I propose throwing out the proposed behavior as Option B, and see what happens.
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CANE-7mViGxVs%3DvP3-%2BkZxuHyuNSHeAaU5Pfg2Ts7Z957rFmK8g%40mail.gmail.com.
Thanks for the response. Do you think what Florian or I sent is a good example to include in the docs for the way #1?
From: Carlton Gibson
Sent: Monday, January 22, 2018 2:13 AM
To: Django developers (Contributions to Django itself)
Subject: Re: Default Authorization BackEnd Denying Permissions if ObjectProvided
Hi Mehmet,
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/MLWfvPPVwDk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/1fb3ac66-8d78-4393-b3ca-83cba330bccf%40googlegroups.com.
Do you think what Florian or I sent is a good example to include in the docs for the way #1?