django-namespace-perms
Apart from security and maintenance fixes, this project is being retired, please check out https://github.com/20c/django-grainy
Purpose
Provide granular permissions to django that go down to the level of individual model fields. For example we want to be able to grant a user permission to read the field "name" of a certain object instance
app_name.model_name.instance_id.field_name
Installation
Django
Versions Supported
django-namespace-perms==0.6.0
- 2.0
- 2.1
- 2.2
- 3.0
django-namespace-perms==0.5.0
- 1.8
- 1.9
- 1.10
- 1.11
Config
Edit settings.py INSTALLED_APPS
INSTALLED_APPS += (
"django_namespace_perms",
)
Run
python manage.py migrate
Add Inline Permission editing to the user admin forms
Edit your app admin.py and add these:
from django_namespace_perms.admin import UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Note that this will remove editing of django out-of-the-box permissions from the admin UI and replace it with the nsp permissions forms. So make sure you enable NSP as a django permissions backend (next step)
If you wish to simply append django namespace permissions forms the the user and group admin editors you can do so by adding UserGroupInline and UserPermissionInline to the existing UserAdmin admin model
from django_namespace_perms.admin import (
UserGroupInline,
UserGroupInlineAdd,
UserPermissionInline,
UserPermissionInlineAdd
)
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class UserAdmin(UserAdmin):
...
inlines = (UserPermissionInline, UserPermissionInlineAdd)
...
admin.site.register(User, UserAdmin)
Set as django permission backend
Edit your settings.py and add
AUTHENTICATION_BACKENDS = ("django_namespace_perms.auth.backends.NSPBackend",)
Supports Django REST Framework
Edit your settings.py and add
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES' : (
'rest_framework.permissions.IsAuthenticated',
'django_namespace_perms.rest.BasePermission',
)
}
Also you might need to add the same classes to permissions_classes property of the rest viewset class you defined in view.py
Usage
Permission namespace structure examples
Permissions for applications and models
give read access to all model instances that are part of application matching app-name
app_name : PERM_READ
give read access to all fields on all models matching app-name.model-name
app_name.model_name : PERM_READ
give write access to all fields on instance(id=1) of app-name.model-name
app_name.model_name.1 : PERM_WRITE
give write access to field field-name of instance(id=1) of app-name.model-name
app_name.model_name.1.field_name : PERM_WRITE
deny access to field field-name on all model instances of app-name.model-name
app_name.model_name.*.field_name : PERM_DENY
Permissions dont need to target application and model names, they can be completely arbitrary, like
a.b : PERM_READ
a.b.c : PERM_WRITE
Checking permissions
import django_namespace_perms.util as nsp
from django_namespace_perms.constants import PERM_READ, PERM_WRITE
from django.contrib.auth import User
user = User.objects.get(id=1)
#check if user has read perms to a model (User as example)
nsp.has_perms(user, User, PERM_READ)
#check if user has read perms to a model field (User.username as example)
nsp.has_perms(user, [User, "username"], PERM_READ)
#check if user has read perms to a model instance
nsp.has_perms(user, user, PERM_READ)
#check if user has read perms to arbitrary namespace
nsp.has_perms(user, "a.b.c", PERM_READ)
# When checking multiple perms for the same user, make sure to cache the perms
# in order to speed up the process
perms = nsp.load_perms(user)
nsp.has_perms(perms, User, PERM_WRITE)
nsp.has_perms(perms, SomeModel, PERM_READ)
Building namespaces
By default the namespace for a model will be returned as
app_name.model_name
and the namespace for a model instance will be returned as
app_name.model_name.<id>
Which is all you need in most cases, but sometimes it makes sense to customize your namespaces. For example when you wish to nest permissions
class Parent(object):
# we override the model instance's namespace
# so it returns 'parent.<id>'
@property
def nsp_namespace(self):
return "parent.%s" % self.id
class Child(object):
parent = models.ForeignKey(Parent)
# we want child perms to be nested under it's parent
# so again we override the namespace and prepend
# the parent's namespace to it
#
# it returns 'parent.<parent_id>.child.<child_id>
@property
def nsp_namespace(self):
return "%s.child.%s" % (self.parent.nsp_namespace, self.id)
Doing this can be really usefully if you want to quickly permission out sets of objects. So a user with permissions to parent.1 would have also permissions to all child objects under that parent.
Requiring explicit permissions
It's nice to be able to grant a user permissions to "parent" and automatically cascade those permissions out to all the children under it, however sometimes this is too loose and you may want to restrict permissions to certain children.
In order to do this you need to require explicit permissions for a model (continuing from example above)
class Child(object):
...
# we require the user to have explicit perms to an
# instance of the model for him to be allowed to write
# to it
@property
def nsp_require_explicit_write(self):
return True
This means that in order for the user to be able to write to an instance of Child he needs to have a permission rule explicitly targeting either
parent.<parent_id>.child.<child_id> -> PERM_WRITE
or
parent.*.child.* -> PERM_WRITE
Apply permissions to dict data
It is possible to apply a users permissions to a data dict, removing any keys the user does not have permission to see.
Let's assume the user has permissions set as follows
a.b : READ
a.b.c : READ | WRITE
a.b.d : DENY
b : READ
We can apply these permissions to any dict holding data with the proper keys
data = {
"a" : {
"b": {
"c" : "This should be here",
"d" : "This should be gone"
}
},
"b" : "This should be here",
"c" : "This should be gone"
}
from django_namespace_perms.util import perms_structure, permissions_apply
data = permissions_apply(data, perms_structure(user))
After permissions apply the contents of data will be
{
"a" : {
"b" : {
"c" : "This should be here"
}
},
"b" : "This should be here"
}
Applying permissions to lists
If you have a dataset that looks like this
data = {
"a" : [
{ "id" : 1, "name" : "should be here" },
{ "id" : 2, "name" : "should be gone" }
]
}
you can still apply permissions to it, but it's a bit trickier. In order to do so you will need to define a list-handler
# we want the list handler function to return the id of the row
# since this is what we want to append to the namspace
#
# so each row in the list will be checked against the namespace
# a.<row.id>
def handler(**kwargs):
return kwargs.get("id")
ruleset = {
"list-handlers" : {
# namespace a
"a" : {
namespace : handler
}
}
}
data = permissions_apply(data, perms_structure(user), ruleset=ruleset)
Setting permissions via API
from django_namespace_perms.constants import PERM_READ, PERM_WRITE
from django_namespace_perms.models import GroupPermission, UserPermission
from django_namespace_perms.util import obj_to_namespace
from django.contrib.auth.models import User, Group
# adding a new group permission to group with id=1
group = Group.objects.get(id=1)
perm = GroupPermission(group=group, namespace="a.b.c", permissions=PERM_READ)
perm.save()
# adding a new user permission to user with id=1]
user = User.objects.get(id=1)
perm = UserPermission(user=user, namespace="a.b.c", permissions=PERM_WRITE)
perm.save()
# use obj_to_namespace to quickly permission out models or instances
perms = UserPermission(user=user, namespace=obj_to_namespace(SomeModel), permission=PERM_READ)
perms = UserPermission(user=user, namespace=obj_to_namespace(SomeModel.objects.get(id=1)), permission=PERM_WRITE)
discover permission namespaces (which then can be granted/revoked in the admin ui)
from django_namespace_perms.util import autodiscover_namespaces
autodiscover_namespaces(SomeModel)
Known Issues
Autocomplete in admin interface for auto-discovered namespaces does currently not work if grappeli is installed.