#11154: Inconsistency with permissions for proxy models
-------------------------------------+-------------------------------------
     Reporter:  Dave Hall            |                    Owner:  Arthur
                                     |  Rio
         Type:  Bug                  |                   Status:  assigned
    Component:  contrib.auth         |                  Version:  master
     Severity:  Normal               |               Resolution:
     Keywords:  proxy contenttype    |             Triage Stage:  Accepted
  permission                         |
    Has patch:  1                    |      Needs documentation:  1
  Needs tests:  0                    |  Patch needs improvement:  1
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Arthur Rio):

 * status:  new => assigned
 * owner:  (none) => Arthur Rio


Comment:

 >The patch for this issue was rejected due to security concerns.
 Specifically, the patch was designed to create permissions for proxy
 models (a necessary condition for solving this issue). Since the behavior
 was "automatic", a proxy model that was "disabled" due to the bug could be
 unexpectedly "enabled" by the patch (at least that was the concern).

 If I'm not mistaken, I don't think it's entirely true. The patch was
 designed to recreate permissions using the content type of the proxy model
 instead of the one of the concrete model. The permissions for the proxy
 models are already automatically generated. For example, if you proxy a
 model in the same app (i.e. with the same `app_label`), you can create and
 admin page for it and use its permissions just fine. It's only if you
 create a proxy model in a different app (most common use case is to proxy
 `auth.User`), that you can't use the admin + permissions, because of that
 `app_label` mismatch when the admin is building the string to lookup the
 permission.

 ----

 >In order to reconcile this, there appear to be two options:
 >  - Allow each proxy model to have it's own ContentType.
 >  - Make ProxyModel._meta.app_label return the app label of the parent
 model.

 The first approach would fix it altogether but is a larger discussion that
 I think is preventing this ticket to be closed (it's the approach used by
 the patch).
 Instead, I think we should focus on the second recommandation (with some
 slight modifications) so that the ticket can be closed and the discussion
 about
 content types can happen separately.

 ----

 To help understand the original problem and some of the approaches we can
 take, I want to make some clarifications with a simple example:

 {{{
 # myapp/models.py
 from django.db import models
 from django.contrib.auth import get_user_model


 class Vehicle(models.Model):
     pass

 class Car(Vehicle):
     class Meta:
         proxy = True

 class Driver(get_user_model()):
     class Meta:
         proxy = True
 }}}

 The current behavior is to use the concrete model content type to generate
 the default permissions, so for `view` it would be:
 - `myapp.view_vehicle`
 - `myapp.view_car`
 - `auth.view_driver`

 As you can see the permission for `driver` is what makes the admin fail to
 lookup the correct permissions for the admin page of `Driver`, only a
 `superuser` would be able to access it.

 ----

 What I suggest is to refactor `django/contrib/admin/options.py` to use the
 following function,  that builds the proper `app_label` from an `opts`
 parameter (to follow the same pattern as `get_permission_codename`).

 {{{
 # django/contrib/auth/__init__.py

 def get_permission_app_label(opts):
     """
     Return the app_label of the permission for the specified model.
     Proxy models use the content type of the concrete model.
     """
     return opts.concrete_model._meta.app_label


 # django/contrib/admin/options.py
 # Replace the many similar calls ('view', 'change', 'add', 'delete', etc.)

 - codename = get_permission_codename('add', opts)
 - return request.user.has_perm("%s.%s" % (opts.app_label, codename))
 + app_label = get_permission_app_label(opts)
 + codename = get_permission_codename(action, opts)
 + return request.user.has_perm("%s.%s" % (app_label, codename))
 }}}


 By looking at the concrete model (`opts.concrete_model._meta.app_label.`
 as opposed to `opts.app_label`), it would keep the existing behavior
 intact for other models
 (For a concrete model, `opts.concrete_model._meta == opts`), and make the
 admin work with proxy model inheriting concrete models from a different
 app (i.e. with a different `app_label`).

 The other benefit of using this approach as first step to fix the broader
 issue, is that it centralizes where the change would occur if we decided
 to go with having a `ContentType` per proxy model and using that to
 generate the initial permissions instead of the one of the concrete type.

 Finally, regarding the security concerns previously raised, the only
 scenario that I can think of is the following:

 **Pre-conditions:**
 - You have a proxy model whose concrete model has a different `app_label`
 - You gave the permissions for that proxy model to a user
 - You registered the model on the admin

 **Before the patch:**
 The user wouldn't see or be able to access the admin page, only superusers
 could.

 **After the patch:**
 The user would be able to access the admin page based on its permissions.

 ----

 I will create a PR with this approach, let me know if there are any corner
 case or security concerns I'm missing.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/11154#comment:59>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/065.2b92d135aea21fa3b9cdfa3246f3e47d%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to