Opened 6 years ago

Last modified 2 weeks ago

#13539 new Bug

The delete confirmation page does not check for object-level permissions when building the related list

Reported by: Ion Scerbatiuc Owned by:
Component: contrib.admin Version: 1.8
Severity: Normal Keywords: delete object-level permissions
Cc: slav0nic@… Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no


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

Attachments (2)

delete_object_permissions.diff (555 bytes) - added by Ion Scerbatiuc 6 years ago.
r16544_13539.diff (2.0 KB) - added by cyrus 5 years ago.

Download all attachments as: .zip

Change History (16)

Changed 6 years ago by Ion Scerbatiuc

comment:1 Changed 6 years ago by Russell Keith-Magee

Patch needs improvement: set
Triage Stage: UnreviewedAccepted

This isn't going into 1.2 unless it has tests; plus, your proposed patch breaks existing tests for deletion in admin_views.

comment:2 Changed 6 years ago by Julien Phalip

#15139 was a dupe.

comment:3 Changed 6 years ago by Julien Phalip

Related issue: #11383.

comment:4 Changed 6 years ago by Julien Phalip

Severity: Normal
Type: Bug

comment:5 Changed 5 years ago by cyrus

Easy pickings: unset
Owner: changed from nobody to cyrus
Status: newassigned
UI/UX: unset

Changed 5 years ago by cyrus

Attachment: r16544_13539.diff added

comment:6 Changed 5 years ago by cyrus

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:

  • django.contrib.auth.tests.auth_backends.BackendTest.test_has_no_object_perm
  • django.contrib.auth.tests.auth_backends.NoInActiveUserBackendTest.test_has_perm
  • django.contrib.auth.tests.auth_backends.RowlevelBackendTest.test_has_perm

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?

comment:12 Changed 4 years ago by cyrus

Owner: cyrus deleted
Status: assignednew

comment:13 Changed 14 months ago by Tim Graham

Version: 1.2-beta1.8

#16862 was a duplicate (also had a patch).

comment:14 Changed 14 months ago by Sergey Maranchuk

Cc: slav0nic@… added

comment:15 Changed 13 months ago by Claude Paroz

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.

comment:16 Changed 13 months ago by Claude Paroz

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.

comment:17 Changed 13 months ago by Claude Paroz

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.

comment:18 Changed 13 months ago by Claude Paroz

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.

comment:19 Changed 2 weeks ago by Virtosu Bogdan

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.

Note: See TracTickets for help on using tickets.
Back to Top