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.