I'm certainly interested in it, although I'm not a Django core developer.

Have you given any thought to allowing users to only read or update a
subset of the fields of a given model?  We have a very complex
home-grown authorization system at the company I work for that I'm
very interested in replacing with something more standard/mainstream.

Also, with module-level permissions (which may be more like app-level
permissions if I understand what you're proposing correctly), is there
a possibility for one app to make use a permission defined in another?
 For instance, I may have one app (say, united_states_polling) with
DemocratPoll and RepublicanPoll models as described in your
description and another app (say, great_britain_polling) with ToryPoll
and LabourPoll models.  I would then possibly want to make use of a
module-level permission defined for the united_states_polling app in
the great_britain_polling app.

Thanks for working on this problem.

Peace,
Andrew

On Tue, Jul 13, 2010 at 12:08 PM, Nate <nathaniel.soa...@gmail.com> wrote:
> Hello. My name is Nate. Myself and my friend Marco would like to
> upgrade django permissions. We would like to brainstorm here first to
> make sure that we design something that fits with django.
>
> If there are any other discussions about upgrading django permissions,
> kindly point me to them. We have already some of the permissions
> threads on django-dev, but we thought that it would be best to make a
> new thread for our proposal.
>
> We are aware of the fact that django 1.2 added some hooks for row-
> level permissions, but this proposal covers an even broader scope.
>
>
> # The Proposal #
>
> In a nutshell, we would like to add a PermissionManager to django. It
> will be similar in concept to the 'objects' queryset Manager that
> already exists on models. Each model will have a default
> PermissionManager attached. People can override this PermissionManager
> with their own PermissionManager to change the permissions for a
> particular model.
>
> The default PermissionManager works like the current permissions
> system: it checks all of the Backends to see if the user has the given
> permission. (This, in turn, checks the records of auth.Permission by
> default.) The simplest use case of the PermissionManager is overriding
> it to add new permissions, equivalent to setting the Meta.permissions
> attribute. For example:
>
> class Poll(models.Model):
>    permissions = PermissionManagerWith(vote='Can Vote')
>    ...
>
> Note that PermissionManagerWith would be a factory function to create
> a new PermissionManager class with a vote permission. More on this
> later.
>
> Unlike the current system, PermissionManagers make a distinction
> between two types of permissions: object-level (row-level) and model-
> level permissions. Model-level permissions are similar to the current
> permissions that django has, while object-level permissions add more
> fine-grained control.
>
>
> ## Model Level Permissions ##
>
> Each PermissionManager has an 'allows_PERM' classmethod for each model-
> level permission. For example, a PermissionManager with the model-
> level permissions 'add', 'remove', and 'edit' will have the functions
> 'allows_add', 'allows_remove', and 'allows_edit'. These functions take
> a User object as a parameter and return a boolean as to whether or not
> the user has the given permission. So, for instance, a user could do
> the following:
>
> if not Poll.permissions.allows_vote(user):
>    raise PermissionError
>
> By default, this will check all of the authentication Backends to see
> if the user has the given permission. Users could, of course, extend
> PermissionManager and override this behavior to use other methods of
> checking the permissions.
>
> Note, however, that these functions are defined on a Class level.
> Users overriding these functions can not do any computation concerning
> a model instance, only a model class. This does not allow people to do
> things like restrict edits to only models that the editor created. For
> that, you need object-level permissions.
>
>
> ## Object Level Permissions ##
>
> Each PermissionManager has a 'PERM_list' classmethod for each object-
> level permission. For example, a PermissionManager with the object-
> level permissions of 'remove' and 'edit' will have the functions
> 'remove_list' and 'edit_list'. These functions take a User object as a
> parameter and returns a queryset of all the objects for which the user
> has the specified permissions. So, for instance, you might see
> something like this in contrib.admin:
>
> queryset = Book.permissions.edit_list(user)
>
> By default, this is either all of the objects or none of the objects,
> depending upon the authentication backends. However, this is what is
> overridden to get more fine-grained permission control. For example,
> consider the following:
>
> class BookPermissionManager(PermissionManager):
>    def edit_list(self, user):
>        return super(BookPermissionManager,
> self).edit_list(user).filter(author=user)
>
> class Book(models.Model):
>    author = models.ForeignKey(User)
>    permissions = BookPermissionManager
>    ...
>
> This is an example of using PermissionManagers to only allow users to
> edit books that they themselves authored. Note that this proposed
> permissions system does not require that we add any tracking or meta-
> data to the database. If users would like to base permissions on the
> meta-data of objects, then they need to store such meta-data
> themselves. This permissions system will by default use only the
> existing auth.Permission system for permissions.
>
> For symmetry and ease of use, each PermissionManager instance has an
> 'allows_PERM' method for each object-level permission. The default
> implementation of allows_PERM is this:
>
> def allows_PERM(user):
>    return self.model in self.PERM_list(user)
>
> and it is strongly recommended that allows_PERM not be overridden for
> object-level permissions, because changes in allows_PERM will not be
> reflected in PERM_list.
>
> ### Adding Permissions ###
>
> The most common overriding of PermissionManagers will be adding new
> object-level or model-level permissions. This use case is currently
> the only use case supported by Django, and is done through the
> Meta.permissions attribute. The PermissionManager system would allow
> infinitely more flexibility, allowing users to roll their own
> permission-checking system if necessary. However, we need a simple
> shorthand for the common case.
>
> One possible method is to continue to configure permissions through
> Meta.permissions. However, it would be cleaner if we could find a way
> to combine extension (i.e. adding new permissions) with replacement
> (i.e. changing the PermissionManager like you change a queryset
> Manager).
>
> I'm still struggling to find a clean PermissionManager override syntax
> that easily allows the defining of new object-level and model-level
> permissions. One possible method would be through a factory method,
> such as the following:
>
> class Poll(models.Model):
>    permissions = PermissionManagerWith(
>        object={'vote': 'Can Vote'},
>        model={'create': 'Can Create'})
>
> However, I am not convinced that this is the clearest method. Any
> other ideas?
>
> Also, you may notice that the tense of permission names has changed.
> The PermissionManager system implies that we give permissions names
> such as 'add', 'edit', 'remove', or 'vote' rather than 'can_add',
> 'can_edit', 'can_remove', or 'can_vote'. I personally think that these
> semantics are cleaner and more concise, but of course, it should be
> given some consideration.
>
> ## Module Level Permissions ##
>
> When you pass a string to has_perm or permission_required (or any
> similar function), you pass a permission as
> 'app_label.permisison_name'. This is all well and good, unless certain
> models have similar names.
>
> Consider for example an app 'polls' with two types of Poll:
> DemocratPoll and RepublicanPoll. Both models have a 'can_vote'
> permission defined in Meta.permissions. Both of these permissions will
> be confounded if you ask "user.has_perm('polls.can_vote')".
>
> Sometimes, this is what users want. Perhaps, in this example,
> 'can_vote' is meant to be a flag stating that the user is over 18 and
> is a registered voter.
>
> At other times, though, this is not what users want. Maybe 'can_vote'
> is supposed to be a flag stating that the user is registered as a
> member of the correct political party, and registered Democrats can
> only vote in the DemocratPolls while registered Republicans are
> confined to RepublicanPolls. In order to do this in django currently,
> you need to change the permissions to can_vote_democrat and
> can_vote_republican. (Note: you can still pass has_perm the actual
> Permission object in python code, but this is more difficult in a
> template.)
>
> This slightly violates the DRY principle and is a potential source of
> errors, especially as apps get larger (and people fail to realize that
> their permission names overlap).
>
> We propose, as such, that we depreciate the
> "app_label.permission_name" style of referring to permissions and
> switch to "app_label.Model.permission_name". This allows similarly
> named permissions between models to be easily differentiated.
>
> However, sometimes users want permissions that transcend models. In
> this example, people may want a permission that indicates a registered
> voter, regardless of political party. For this, we propose module-
> level permissions.
>
> We propose that each application has a PermissionManager attached to
> it. I'm not sure what the best way to do this would be, although the
> obvious way would be to use the first top-level PermissionManager
> instance found in the app as the module permission manager, similar to
> how we use the first queryset Manager instance found in a model as the
> default queryset manager. If none is found, the module will get a
> default PermissionManager that defines no module-level permissions.
>
> We would recommend that the module permission manager be named
> 'permissions' and be placed either in the application __inti__.py or
> models.py file. This may not be the cleanest of solutions, and I am
> open to other suggestions.
>
> This module-level PermissionManager would represent permissions
> attached to the module, but not to any specific model. We'd alter
> auth.Permission to allow a null Model, and those would be module-level
> permissions, accessed in the "app_label.permission_name" fashion.
>
> ## Superusers ##
>
> Any place that users override something like allows_PERM or PERM_list,
> they would have to consider whether or not the user is a superuser.
> This is a bit of boilerplate that is likely unnecessary. As such, let
> me describe how the allows_PERM and PERM_list functions work:
>
> The default allows_PERM(user) and PERM_list(user) methods simply call
> allows(permission, user) or list(permission, user) methods, where
> 'permission' is a string detailing the permission. These centralized
> methods always return true if the user is a superuser, and otherwise
> they look up the correct permission in auth.Permission and return the
> correct result. Thus, any override of allows_PERM or PERM_list need
> not check the user status so long as they use a super() call.
>
> This has the added benefit that a user can easily change the core
> workings of a PermissionManager by simply overriding the
> allows(permission, user) and edit(permission, user) functions.
>
> ## Backwards Compatibility ##
>
> As stated above, we suggest that referencing a model permission by
> string be changed to include the model name. "app.Model.perm" should
> be used to describe model permissions, while "app.perm" should be used
> for module permissions. That being said, in the interest of backwards
> compatibility, if "app.perm" fails, it should check "perm" on all
> modules. Whether or not such behavior should raise a
> PendingDepreciationWarning is up for discussion.
>
> Note that through the PermissionManager, ModelBackend will continue to
> be used without object permissions. "supports_object_permissions" will
> remain False. Rather, the default PermissionManager will check the
> permission with the ModelBackend, and then will filter the results
> accordingly through PERM_list. We feel that this is a better option
> than iterating over all available objects, asking "user.has_perm(perm,
> object)" for each object, and generating a list of all permitted
> objects.
>
> Using a PermissionManager and overriding PERM_list is both easier and
> more efficient than overriding the backend and checking with it for
> every object. That being said, users would still be welcome to make an
> object-managing backend and a specialized PermisisonManager that works
> with the new backend to make it's PERM_list methods.
>
> We would re-route User.has_perm to check with the PermissionManager
> instead of the backend, but this is trivial and would continue to work
> the same from all appearances.
>
> As far as the API goes, the old API would remain untouched (with
> pressure to move from app.perm to app.Model.perm), but the user would
> also be able to access permissions from the model side. In other
> words, the following will be equivalent:
>
> book.permissions.allows_edit(user)
> user.has_perm('writing.Book.edit', book)
>
> Author.permissions.allows_add(user)
> user.has_perm('writing.Author.add')
>
> Both styles have their place, I believe.
>
> ## Summary ##
>
> We would like to add a PermissionManager. It will be like a queryset
> Manager, and it will handle checking with the authentication Backend
> about the permission. It distinguishes between object-level and model-
> level permissions, providing "allows_PERM" (for model-level) and
> "PERM_list" (for object-level) methods for each permission at a class
> level, the former returning a bool and the latter returning a
> queryset. The common case will have users defining PermissionManagers
> with extra permissions, and overriding PERM_list to filter objects
> based upon user.
>
> We'll get started on writing a patch and some docs as soon as we get a
> green light. As you can tell, though, the idea is still not entirely
> fleshed out, and we'd like to bounce it around and clean it up a bit
> before we implement it. So, before we start logging hours, we'd like
> to know: is this sort of overhaul of the permission system something
> that the django-devs are interested in, and is this a sane direction
> to be taking?
>
> --
> You received this message because you are subscribed to the Google Groups 
> "Django developers" group.
> To post to this group, send email to django-develop...@googlegroups.com.
> To unsubscribe from this group, send email to 
> django-developers+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/django-developers?hl=en.
>
>



-- 
=======================
Andrew D. Ball
勃安
"Ὁ θεὸς ἀγάπη ἐστίν ..." (1 Jn 4:16)

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-develop...@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to