Default Authorization BackEnd Denying Permissions if Object Provided

278 views
Skip to first unread message

Mehmet Dogan

unread,
Jan 11, 2018, 5:35:15 PM1/11/18
to Django developers (Contributions to Django itself)
Hello all,

I had opened a ticket re issue noted in subject, which happened to be a duplicate, anyways, the text is here:

Tim Graham told that it needs to be discussed here. Seems this is a long going issue, with several related issues (see also this one). I propose a solution in the first link:

If receives public support, I can also start working on a patch. 

Regards,

Mehmet

Mehmet Dogan

unread,
Jan 11, 2018, 6:07:33 PM1/11/18
to Django developers (Contributions to Django itself)
Here is the text of linked stuff for convenience:

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. 

Mehmet Dogan

unread,
Jan 11, 2018, 6:08:26 PM1/11/18
to Django developers (Contributions to Django itself)
And the other:

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. 

Mehmet Dogan

unread,
Jan 11, 2018, 10:16:20 PM1/11/18
to Django developers (Contributions to Django itself)

Mehmet Dogan

unread,
Jan 11, 2018, 10:28:18 PM1/11/18
to Django developers (Contributions to Django itself)

Based on this patch: the following 3 methods in the custom authorization backends will have to admit a fallback_to_model keyword argument:

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

Mehmet Dogan

unread,
Jan 12, 2018, 6:55:18 PM1/12/18
to Django developers (Contributions to Django itself)
Created a pull request: https://github.com/django/django/pull/9581

Mehmet

Mehmet Dogan

unread,
Jan 16, 2018, 10:50:33 AM1/16/18
to Django developers (Contributions to Django itself)

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:

  • One will not have to use package specific API for checking just the object permissions, for example.
  • Cleaner and generic: allows backends re-use each other. For example, I am writing a RoleBackend and can easily make calls to model or object permission backends, in order not to reinvent the wheel.

Mehmet Dogan

unread,
Jan 16, 2018, 11:15:58 AM1/16/18
to Django developers (Contributions to Django itself)
And I forgot; 3rd advantage:

  • The 3 backend methods mentioned above won't have to take an extra kwarg such as fallback_to_model; thus backward compatible there.

Mehmet Dogan

unread,
Jan 16, 2018, 1:22:00 PM1/16/18
to Django developers (Contributions to Django itself)

Carlton Gibson

unread,
Jan 17, 2018, 5:45:05 AM1/17/18
to Django developers (Contributions to Django itself)
Hi Mehmet, 

Due to the BC issues, this is fairly in-depth. 

Having looked at the history, here are my initial thoughts. 



The initial issue here is this behaviour from `ModelBackend`:

```
user.has_perm('foo.change_bar', obj)
False
user.has_perm('for.change_bar')
True
```

Although the long-standing behaviour, this is considered _unexpected_.


There are two related tickets regarding permission-checking in the Admin:


Currently, I see three options:

1. Close as "Won't Fix", perhaps with a review of the documentation to see if we
   can't clarify/emphasise the behaviour somewhere.

    This is the path of least resistance. It conforms to the original design
    decision. It preserves the long-standing behaviour. (Whilst, yes, some find the
    behaviour unexpected, it has a sense; it just depends how you look at it.)

    The objection is to this kind of check:

        if user.has_perm('foo.change_bar', obj=bar) or user.has_perm('foo.change_bar'):
            ...

    * Whilst, granted, it's a little clumsy, there's no reason this couldn't be
      wrapped in a utility function. (There's a suggestion to that effect on the
      Django-Guardian issue tracker[^1]. This seems like a good idea, simple enough
      to live in user code.)
    * `ModelBackend` permission lookups are cached[^2] so the performance worry
      here should be negligible.

2. Implement the (straight) backwards incompatible change.

    The difficulty here is (we guess) why this ticket has been open so long.

    If we are convinced this is the right way to go — i.e. that the current
    behaviour is in fact **wrong** — then we should go ahead despite the
    difficulty.

    Working out what that entails is non-trivial. That's why it needs a decent
    discussion and consensus here.

    Which leads to...

3. Break out the permissions aspect of `ModelBackend` in order to make it 
   pluggable in some way. Then allow users to opt-in to a new version with the 
   adjusted behaviour.
   
   There is some discussion of this on one of the related tickets[^3].
   
   Again, exactly what the migration path is needs some planning. 

I'm not sure what the correct answer is. 



Kind Regards,

Carlton

Florian Apolloner

unread,
Jan 17, 2018, 11:31:10 AM1/17/18
to Django developers (Contributions to Django itself)


On Wednesday, January 17, 2018 at 11:45:05 AM UTC+1, Carlton Gibson wrote:
    The objection is to this kind of check:

        if user.has_perm('foo.change_bar', obj=bar) or user.has_perm('foo.change_bar'):
            ...

FWIW  I would never write code like this. `user.has_perm('foo.change_bar', obj=bar)` would be enough, in the worst case the user would have to change the permission backend which is easy enough…

Mehmet Dogan

unread,
Jan 17, 2018, 11:48:03 AM1/17/18
to django-d...@googlegroups.com
Florian,

Can you clarify this part, I am not sure what you meant:

in the worst case the user would have to change the permission backend which is easy enough…
--
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 Apolloner

unread,
Jan 17, 2018, 1:45:06 PM1/17/18
to Django developers (Contributions to Django itself)
On Wednesday, January 17, 2018 at 5:48:03 PM UTC+1, Mehmet Dogan wrote:
Florian,

Can you clarify this part, I am not sure what you meant:


class MyBackend(ModelBackend):

   def has_perm(…, obj=None,…):
     if obj: obj = None
     return super().has_perm(…)

something along the lines of this should do it. 

Andrew Standley

unread,
Jan 17, 2018, 1:58:10 PM1/17/18
to django-d...@googlegroups.com

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'

Additionally if Mehmet wanted to finalize an API that allowed users to specify a subset of backends to check against, this approach would support that.

Cheers,
    Andrew
--
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.

--
Andrew Standley
Senior Software Engineer
Linear Systems
(909) 899-4345 *225
asta...@linear-systems.com

Carlton Gibson

unread,
Jan 17, 2018, 1:58:17 PM1/17/18
to Django developers (Contributions to Django itself)
Hi Florian, 

I was hoping you'd post. You've been active on the entire (very-long) history of this issue. 

Can I ask for your take? 

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?

Thanks!

Kind Regards,

Carlton

Mehmet Dogan

unread,
Jan 17, 2018, 2:46:27 PM1/17/18
to django-d...@googlegroups.com

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.

Mehmet Dogan

unread,
Jan 17, 2018, 3:04:30 PM1/17/18
to django-d...@googlegroups.com

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.

Carlton Gibson

unread,
Jan 17, 2018, 3:04:38 PM1/17/18
to Django developers (Contributions to Django itself)
Hi. 

@Andrew: I'll look at your post anon, as it's longer. 


On Wednesday, 17 January 2018 20:46:27 UTC+1, Mehmet Dogan wrote:

Can you give an example of what you mean by option 3. 


Well, I don't a concrete suggestion in mind, but the general idea would be to have ModelBackend proxy to another class (a Strategy ?) that did the actual permission check. 

We'd then have (at least) two versions: one with the current implementation, and one with the alternative. 

Exactly how the user would configure which version to use (etc) would need thinking about. 

The question is whether it's worth the price of admission, vs just, say, subclassing ModelBackend. 

C.

Mehmet Dogan

unread,
Jan 17, 2018, 3:52:50 PM1/17/18
to django-d...@googlegroups.com

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.

Florian Apolloner

unread,
Jan 17, 2018, 4:38:12 PM1/17/18
to Django developers (Contributions to Django itself)
On Wednesday, January 17, 2018 at 7:58:17 PM UTC+1, Carlton Gibson wrote:
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?

My comment https://github.com/django/django/pull/9581#pullrequestreview-88665130 sums up my thoughts. We should decide what the expected behavior for auth backends should be and then work towards that (if that means it should return True instead of False for obj!=None so be it). I am not happy with any of the proposed solutions so far (be that new arguments or settings).

Btw, it would be nice if you could be on IRC if your time permits it.

Cheers,
Florian 

Florian Apolloner

unread,
Jan 17, 2018, 4:39:49 PM1/17/18
to Django developers (Contributions to Django itself)


On Wednesday, January 17, 2018 at 9:04:30 PM UTC+1, Mehmet Dogan wrote:

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.


Funny that you would say that, because your proposed setting OBJECT_PERMISSION_FALLBACK_TO_MODEL does exactly the same thing ;)

Mehmet Dogan

unread,
Jan 17, 2018, 4:55:09 PM1/17/18
to django-d...@googlegroups.com
Yea :) I just figured that after a few emails. I am learning a lot!

--
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.

Mehmet Dogan

unread,
Jan 17, 2018, 8:35:03 PM1/17/18
to django-d...@googlegroups.com
Andrew,

Thank you for the input. Having options is good. My concern about that is, it may divide the already small backends population. If a backend relies on PermissionAuthorizationBackend, and another require the ModelOnlyPermissionAuthorizationBackend; then one cannot use both. Guardian, at its core, is agnostic of the default backend behavior, however, relies on it in some utilities. However, I am working on a simple RolePermissionsBackend now that leverages existing Object and Model backends at its core. Currently, if I do `user.has_perm('foo.change_bar')` or `user.has_perm('foo.change_bar', obj)` I know one is the model and the other is the object permissions. In case of plug-ins, that will go away, and it will be even harder to leverage existing backends. Also, the logic that polls backends is in auth/models.py, I do not see how would it be possible to design an API that will let picking and choosing. 

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.

Mehmet Dogan

unread,
Jan 17, 2018, 8:55:27 PM1/17/18
to django-d...@googlegroups.com
The "expected behavior" is that one has permission on an entire table would also have permission on a row of it. This seems to be the one thing that everyone can agree on. And, I am yet to see a person that argues otherwise. But, it seems, we just need someone or some people to make that hard decision. 

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 :)

If I could pick and choose the backends, or types of backends, such as:

user.has_perm('foo.change_bar', obj, backends=('object', 'role', 'model'))  
that would be lovely. However, I think I can live without it. 

Cheers, 

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.

Andrew Standley

unread,
Jan 17, 2018, 9:42:03 PM1/17/18
to django-d...@googlegroups.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.

Regarding an API that will allow picking and choosing I already have some ideas, but I think Carlton made an excellent point that that is another discussion all together.

Cheers,
    Andrew
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 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.
--
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.

Florian Apolloner

unread,
Jan 18, 2018, 11:14:28 AM1/18/18
to Django developers (Contributions to Django itself)


On Thursday, January 18, 2018 at 2:55:27 AM UTC+1, Mehmet Dogan wrote:
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 :)

Personally I think it is (though I haven't had hours to think about it). That said, the backwards compat and migration has to be considered carefully.

Cheers,
Florian

Mehmet Dogan

unread,
Jan 18, 2018, 11:25:55 AM1/18/18
to django-d...@googlegroups.com
Andrew,

Yes, I think we can safely assume apps would be backend agnostic. I was actually referring to the backends themselves, if they use user.has_perm(...). This might sound counter-intuitive at first but I think it is possible, and actually I am working on one right now (see my long answer for details). 

Long Answer: I am trying to write a Roles backend with, and without reinventing the wheels for, object and model permissions. The logic is very simple. 
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 False
1) 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. 
2) In order to prevent infinite loops, I have to strictly separate permissions that represent Roles (namely delegates) and
permissions in the roles. If I could specify backends as I proposed earlier, I could even take this even one step further 
and allow roles include other roles (that would be awesome).

3) I have not tested or used this. And one might see it as a hairy design to be disregarded. But I claim it has a fair chance to be
otherwise. When it comes to backends we don't have plenty of choices, and allowing them leverage each other will just make
it easier to write them. 

Thanks for reading so far! :)


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.

Mehmet Dogan

unread,
Jan 18, 2018, 11:40:37 AM1/18/18
to django-d...@googlegroups.com
For the migration, I think most sensible way is to extend the current backend and provide it as an option, until the other is depreciated and removed. This would act as a global setting in the meantime. But eventually, I think, we should have just 1 default backend for the sake of consistency overall. People can always extend it and put it above it in the setting to alter default behavior, or just replace it, if they need. 

But I would also like to point out one disadvantage of the new way: it will make harder, and maybe impossible in some cases, to pull object permissions over the common API. For example in the existing scheme: user.has_perm('foo.change_bar') and user.has_perm('foo.change_bar', obj) are two exclusive sets.  Easy to add if needed, however, in the new scheme, the latter will include the former, and set subtraction will cause loss of data if there was overlap (i.e., intersection was not empty). This (again) will bring us back to specifying backends for control and flexibility. 

Andrew Standley

unread,
Jan 18, 2018, 2:29:23 PM1/18/18
to django-d...@googlegroups.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

Mehmet Dogan

unread,
Jan 18, 2018, 3:04:44 PM1/18/18
to django-d...@googlegroups.com

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,

Mehmet Dogan

unread,
Jan 18, 2018, 3:54:36 PM1/18/18
to django-d...@googlegroups.com

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

Mehmet Dogan

unread,
Jan 19, 2018, 11:54:07 AM1/19/18
to Django developers (Contributions to Django itself)

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. 

Aymeric Augustin

unread,
Jan 19, 2018, 3:33:51 PM1/19/18
to django-d...@googlegroups.com
2018-01-19 17:54 GMT+01:00 Mehmet Dogan <meh...@edgle.com>:

I propose throwing out the proposed behavior as Option B, and see what happens.


"Make changes and see what happens" doesn't sound like the way Django is maintained in general.

Unfortunately I don't have enough motivation to go through the 21 emails you sent over the last week so I can't offer more constructive advice.

Keep in mind that each email to this list is sent to thousands of people. You'll get more help if you demonstrate respect for other people's time.

Thanks for your understanding!

--
Aymeric.

Mehmet Dogan

unread,
Jan 19, 2018, 4:53:43 PM1/19/18
to django-d...@googlegroups.com
Aymeric,

If one doesn’t have time to read 21 emails, then should also not have time to judge them.

Regards,
--
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.

Carlton Gibson

unread,
Jan 22, 2018, 3:12:58 AM1/22/18
to Django developers (Contributions to Django itself)
Hi Mehmet, 

Sorry for the slow reply. I didn't have capacity to return to this last week. 

> Hey Carlton ... What do you say?

Well, my first thought is that I don't see any great consensus for a change here.
I don't see it as **my** decision to make. 

This has been open a while, however, so...

I've used this API, with Django Guardian and without. It never occurred to me 
that there was a problem with ModelBackend's behaviour. 

For me the it (i.e. the docs) says:

> I don't do object permissions so I always return `False` if you ask. 

That seems clear, safe, and unambiguous. 

I see users read the API as hierarchical. Having thought about this issue,
ultimately, I think it's a misunderstanding (and so a docs issue).

I come back to Florian's point about what the right API should be? 

`user.has_perm("for.change_bar", obj=some_bar)`

What are we offering here? 
The ability to cycle through a number of backends checking permissions, **plus**
a (moderately) simple default permissions system. That's it. 
(We're not offering the most full-featured ACL-powered goodness. That's deliberate.)

Is this a good API for that? I think probably yes. 

Short of looking at ≈all the major frameworks out there and seeing what they 
offer instead, I don't see a ground for change. Not currently. 

I do see two ways forward: 

* A possible change to the docs: highlight what we're doing (again) 
  — provide the example of an alternate backend, with the other behaviour. 
  
* Possible backwards compatible refactoring of the authentication and authorization 
  roles of the authentication backends. Right now we have a class with two 
  responsibilities, so splitting that may make sense. (It may make future steps 
  clearer.) I think that would need to be done piecemeal and in PRs, with tests 
  and docs etc to be sensibly assessed. (It's impossible to assess code on the 
  mailing list, at least for me.)
  
Given the scope of the permissions system, I'm not convinced we need to make an 
actual code change here. (i.e. I'm not convinced about need for the second 
refactoring part.) The goal is to provide a simple but extensible system. We 
have that. I don't see any limitation that can't be worked around in user code 
if it doesn't suit. 

For me, I'd close as won't fix.

Kind Regards,

Carlton




Mehmet Dogan

unread,
Jan 23, 2018, 9:05:06 AM1/23/18
to django-d...@googlegroups.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.

Carlton Gibson

unread,
Jan 24, 2018, 6:26:16 AM1/24/18
to Django developers (Contributions to Django itself)
Hi Mehmet

On Tuesday, 23 January 2018 15:05:06 UTC+1, Mehmet Dogan wrote:

Do you think what Florian or I sent is a good example to include in the docs for the way #1?


Yes.

I was just looking for where. Candidates: 


None of these are quite right. (Although top one is favourite. The last one has this line: "If you wish to provide custom behavior for only part of the backend API, you can take advantage of Python inheritance and subclass ModelBackend instead of implementing the complete API in a custom backend..." — which is about as close as we get.)

There's also this discussion: 


Looking at this, I don't think we're quite saying what I took us to be saying. i.e. we're not clearly spelling out the object-permissions situation. 
(Beyond those pages all the top search results are from DRF, and Django Guardian, which must be where people are picking up this knowledge.)

As such, if you wanted to work on a docs focused PR here I'd be happy to review and input on that on GitHub. 
(I take it this would say a bit more than currently on how User.has_perms() proxies to the backends, such that some may handle object-level permissions.)

Thanks for the effort! 

Kind Regards,

Carlton

Reply all
Reply to author
Forward
0 new messages