Authorization library for Django, not depends on models.
- Won't depend on models
- Won't depend on datastores
- Won't depend on Django's permission system
- Won't depend on Django's User model
Supported versions:
- Python3.8
- Python 3.9
- Django 2.2
- Django 3.0
- Django 3.1
- Django 3.2
- Django 4.1
- Django 4.2
$ pip install django-keeper
And add to INSTALLED_APPS
INSTALLED_APPS = [
...
'keeper',
]
Declarative permission mapping for models.
from django.conf import settings
from keeper.security import Allow
from keeper.operators import Everyone, Authenticated, IsUser
class Issue(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
...
def __acl__(self):
return [
(Allow, Everyone, 'view'),
(Allow, Authenticated, 'add_comment'),
(Allow, IsUser(self.author), 'edit'),
]
Instances of model allow:
- Every requests to view
- Autheticated requests to add comments
- it's author to edit
Then, apply @keeper
for views.
from keeper.views import keeper
# Model Permissions
@keeper(
'view',
model=Issue,
mapper=lambda request, issue_id: {'id': issue_id},
)
def issue_detail(request, issue_id):
""" View requires 'view' permission of Issue model
* An issue object will be retrieved
* keeper will check whether the rquests has 'view' permission for the issue
The third argument function can return keyword argument to retrieve the issue object.
"""
request.k_context # Will be instance of the issue object
...
@keeper(
'add_comment',
model=Issue,
mapper=lambda request, issue_id: {'id': issue_id},
)
def add_comment(request, issue_id):
...
Not just for model permissions django-keeper
can handle global permissions.
First, write class having __acl__
method in models.py.
class Root:
def __acl__(self):
return [
(Allow, Authenticated, 'view_dashboard'),
(Allow, Authenticated, 'add_issue'),
]
It's not necessary to put it in models.py
,
but easy to understand.
And specify it in settings.
KEEPER_GLOBAL_CONTEXT = 'myapp.models.Root'
Then you can use global permission in views.
Simply just apply @keeper
and permission names.
@keeper('add_issue')
def issue_list(request):
""" View requires 'add_issue' permission of Root Context
"""
Operators is just Callable[[HttpRequest], bool]
.
By default django-keeper has these operators:
keeper.operators.Everyone
keeper.operators.Authenticated
keeper.operators.IsUser
keeper.operators.Staff
Also you can create your own operators easily.
from keeper.operators import Authenticated, Operator
class IsIP(Operator):
def __init__(self, ip):
self.ip = ip
def __call__(self, request):
return request.META.get('REMOTE_ADDR') == self.ip
class BelongsTeam(Authenticated):
def __init__(self, team, role):
self.team = team
def __call__(self, request):
if not super().__call__(request):
return False
return request.user.team == self.team
Use it in ACL
class Article(models.Model):
team = models.ForeignKey(Team)
def __acl__(self):
return [
(Allow, Everyone, 'view'),
(Allow, BelongsTeam(self.team), 'edit'),
(Allow, IsIP(settings.COMPANY_IP_ADDRESS), 'edit'),
]
You can use bitwise operators to combine multiple "Operators".
class Article(models.Model):
def __acl__(self):
return [
(Allow, Authenticated() & IsIP(settings.COMPANY_IP_ADDRESS), 'view'),
]
There operators can be used
a & b
a | b
a ^ b
~a
You can change actions when requests can't pass ACLs.
from keeper.views import keeper, login_required
@keeper(
'view_articles',
on_fail=login_required(),
)
def dashboard(request):
...
This view will behave just like @login_required
decorator of Django
when requests don't have 'view' permission.
Also you can use other actions.
keeper.views.login_required
keeper.views.permission_denied
keeper.views.not_found
keeper.views.redirect
Handling permissions in templates is also supported.
{% load keeper %}
{% has_permission issue 'edit' as can_edit %}
{% if can_edit %}
<a href="...">Edit</a>
{% endif %}
When checking global permission, use has_global_permission
.
{% load keeper %}
{% has_global_permission 'add_issue' as can_add_issue %}
{% if can_add_issue %}
<a href="...">New Issue</a>
{% endif %}
Add the authentication backend:
AUTHENTICATION_BACKENDS = (
'keeper.permissions.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend',
)
Now User.has_perm
method will consider permissions of django-keeper.
- django-guardian
- It depends on databases
- Not way to handle global permissions, not just for a model
- django-rules
- Basiaclly, rules is used with Django's permissions, but keeper isn't (basiacally).
- rules doesn't support view decorators or mixins for it's "rules" system (for Django's permission, yes).
- Can I filter models by using ACL?
- Not supported