Description

I implemented a custom authentication backend for providing object level permissions. It's all working fine, except the delete confirmation page for a particular object.
I found that when building the related objects list for the confirmation page, the permissions are checked only for the model itself and not the object being processed.
In django/contrib/admin/util.py at the 77th line you can see this check:

if not user.has_perm(p):

which should be:

if not user.has_perm(p, obj):

I'm attaching a patch for this. I hope that this fix will be included in the 1.2 final release.
Thanks!

While investigating the failing test, I came across an interesting thing: if you have a backend that does not support object-level permissions (like the default ModelBackend) a user will not have permissions for an object even if that user have permissions for the model itself. I think this is weird, because philosophically if I have delete permissions for MyModel I should also have delete permissions for an object of MyModel if my authentication back-end does not enforce object-level permissions.

Attached you can find a patch for this issue (r16544_13539.diff​). Note that this patch make the following tests to fail:

I think the reason for this is that those tests were written with this assumption in mind. I can fix the failing tests and create additional tests to regress this issue, but I want to discuss first. Any thoughts?

I took some time today to examine this issue and found that we're still confronted to the problem mentioned by cyrus on comment:6. If we suddenly pass an object to has_perm, False is returned because the default behavior of the ModelBackend is to return False when any object is passed (see #12462).

So the first step to solve this issue would be to revert that, which might bring backwards incompatibilities. Unfortunately, ticket #12462 doesn't explain the rationale behind that code.

Discussed with apollo13 on IRC, summary:
We should have always returned True in the first place, agreed. Now the path forward is very tricky. Just changing the return value to True now is very dangerous in a security point of view, because if someone has a different backend added to the default ModelBackend, has_perm() will suddenly change its behavior and always return True as the True value has priority (unless the custom backend is first and returns PermissionDenied (#15716), but we can count on that being always implemented this way).

In the longer term, splitting the authentication/authorization steps in different backends might allow us to change the current situation.

One possible solution at "shorter" term would be to add a new default backend (say ModelBackendNG) which always return True for object permissions, and deprecate the previous ModelBackend. Then only after the ModelBackend is removed at the end of the deprecation period, we could then pass obj to has_perm in the admin.

Another solution: create a new DefaultObjPermsBackend (returning True for object permissions) which is added by default in AUTHENTICATION_BACKENDS. With that solution, we could add the obj param to has_perm in the admin immediately.

Projects with default AUTHENTICATION_BACKENDS will work fine as before.
Projects with customized AUTHENTICATION_BACKENDS will probably see some unauthorized object access in the admin, and will have to manually add the DefaultObjPermsBackend in their customized AUTHENTICATION_BACKENDS setting. This is a bit annoying for them, but at least they will have to think about the issue and won't get unexpected new object permissions if they implemented object permissions in their custom backend.

Can't the check be changed to user.has_perm(p) or user.has_perm(p, obj) ?
Default backend will work as expected and custom object-level backends will work as long as they return False for obj=None, which should probably be the case.
This would not have any performance costs.