-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
David Bozhanov
committed
Jun 10, 2024
1 parent
2283983
commit 5e946fb
Showing
11 changed files
with
421 additions
and
418 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
include LICENSE | ||
include README.md | ||
exclude django_permagate/* | ||
exclude manage.py | ||
include LICENSE | ||
include README.md | ||
exclude django_permagate/* | ||
exclude manage.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,107 +1,107 @@ | ||
# PermaGate | ||
|
||
PermaGate is a Django permissions system which offers hierarchical permissions that can be | ||
directly to users and groups. | ||
|
||
# Installation | ||
|
||
1. ```pip install django-permagate``` to install the package | ||
2. ```python | ||
# Add it to the list of installed apps in Django's settings.py: | ||
INSTALLED_APPS = [ | ||
..., | ||
"permagate", | ||
] | ||
``` | ||
3. ```python manage.py migrate``` to create the permission models | ||
4. Set the ```PERMAGATE_PERMISSIONS``` settings variable to the module defining the root permission (see below) | ||
|
||
# Usage | ||
|
||
## Defining Permissions | ||
|
||
The list of permissions that may be assigned to users and groups is defined using the | ||
Permission class. A root permission object is used as the starting point of the permission | ||
tree and is created by calling | ||
```Permission()``` with the default constructor parameters. Child permission nodes can be | ||
added to the permission tree by calling the | ||
```register()``` function on a permission and passing the list of child permissions: | ||
|
||
```python | ||
from permagate.permission import Permission | ||
root = Permission().register([ | ||
Permission("test", "Optional Name", "Optional Description").register([ | ||
Permission("sub1").register([ | ||
Permission("sub-sub1") | ||
]), | ||
Permission("sub2"), | ||
]), | ||
Permission("test1"), | ||
]) | ||
|
||
# In this case, root.permission_list will return: | ||
# ['*', | ||
# 'test', | ||
# 'test>', | ||
# 'test.sub1', | ||
# 'test.sub1>', | ||
# 'test.sub1.sub-sub1', | ||
# 'test.sub2', | ||
# 'test1'] | ||
# | ||
# This is the list of permission strings that may be assigned to users and groups. | ||
|
||
``` | ||
|
||
The settings variable ```PERMAGATE_PERMISSIONS``` should be set to the path of the | ||
module defining the root permission variable: | ||
```python | ||
# Assuming there's a permissions.py file in mymodule containing a variable named 'root' | ||
# that contains the root permission: | ||
PERMAGATE_PERMISSIONS = "mymodule.permissions" | ||
``` | ||
|
||
It is possible to store the permission root in a variable named something other than | ||
```root``` by suffixing the path with ```:new_root_name```: | ||
```python | ||
PERMAGATE_PERMISSIONS = "mymodule.permissions:permission_root" | ||
``` | ||
|
||
## Using Permissions | ||
|
||
Permissions in the user-defined hierarchy may be referenced using dot-separated strings, | ||
where each string segment is a permission key. The '*' and '>' are special characters | ||
where: | ||
1. '>' is referred as the inclusive wildcard since it references the current permission and its children | ||
2. '*' references the root permission and is considered as its key | ||
|
||
To assign user and group permissions and check user access: | ||
|
||
```python | ||
from permagate.models import UserPermission, GroupPermission | ||
from permagate.core import has_permission | ||
from django.contrib.auth import get_user_model | ||
from django.contrib.auth.models import Group | ||
|
||
User = get_user_model() | ||
|
||
# Assign a permission to a user | ||
test_user = User.objects.create(username="test") | ||
UserPermission.objects.create(user=test_user, permission="test>") | ||
|
||
# Assign a permission to a group | ||
test_group = Group.objects.create(group="test") | ||
GroupPermission.objects.create(group=test_group, permission="test.sub1>") | ||
|
||
test_user_two = User.objects.create(username="test2") | ||
test_group.user_set.add(test_user_two) | ||
|
||
if has_permission(test_user, "test.sub1"): | ||
print(f"User {test_user.username} has permission test.sub1 due to directly assignment") | ||
|
||
if has_permission(test_user_two, "test.sub1"): | ||
print(f"fUser {test_user_two.username} has permission test.sub1 via group assignment") | ||
``` | ||
|
||
Note that the permission strings assigned to users may include the inclusive wildcard | ||
character while absolute permissions strings must be used when checking user permissions. | ||
# PermaGate | ||
|
||
PermaGate is a Django permissions system which offers hierarchical permissions that can be | ||
directly to users and groups. | ||
|
||
# Installation | ||
|
||
1. ```pip install django-permagate``` to install the package | ||
2. ```python | ||
# Add it to the list of installed apps in Django's settings.py: | ||
INSTALLED_APPS = [ | ||
..., | ||
"permagate", | ||
] | ||
``` | ||
3. ```python manage.py migrate``` to create the permission models | ||
4. Set the ```PERMAGATE_PERMISSIONS``` settings variable to the module defining the root permission (see below) | ||
|
||
# Usage | ||
|
||
## Defining Permissions | ||
|
||
The list of permissions that may be assigned to users and groups is defined using the | ||
Permission class. A root permission object is used as the starting point of the permission | ||
tree and is created by calling | ||
```Permission()``` with the default constructor parameters. Child permission nodes can be | ||
added to the permission tree by calling the | ||
```register()``` function on a permission and passing the list of child permissions: | ||
|
||
```python | ||
from permagate.permission import Permission | ||
root = Permission().register([ | ||
Permission("test", "Optional Name", "Optional Description").register([ | ||
Permission("sub1").register([ | ||
Permission("sub-sub1") | ||
]), | ||
Permission("sub2"), | ||
]), | ||
Permission("test1"), | ||
]) | ||
|
||
# In this case, root.permission_list will return: | ||
# ['*', | ||
# 'test', | ||
# 'test>', | ||
# 'test.sub1', | ||
# 'test.sub1>', | ||
# 'test.sub1.sub-sub1', | ||
# 'test.sub2', | ||
# 'test1'] | ||
# | ||
# This is the list of permission strings that may be assigned to users and groups. | ||
|
||
``` | ||
|
||
The settings variable ```PERMAGATE_PERMISSIONS``` should be set to the path of the | ||
module defining the root permission variable: | ||
```python | ||
# Assuming there's a permissions.py file in mymodule containing a variable named 'root' | ||
# that contains the root permission: | ||
PERMAGATE_PERMISSIONS = "mymodule.permissions" | ||
``` | ||
|
||
It is possible to store the permission root in a variable named something other than | ||
```root``` by suffixing the path with ```:new_root_name```: | ||
```python | ||
PERMAGATE_PERMISSIONS = "mymodule.permissions:permission_root" | ||
``` | ||
|
||
## Using Permissions | ||
|
||
Permissions in the user-defined hierarchy may be referenced using dot-separated strings, | ||
where each string segment is a permission key. The '*' and '>' are special characters | ||
where: | ||
1. '>' is referred as the inclusive wildcard since it references the current permission and its children | ||
2. '*' references the root permission and is considered as its key | ||
|
||
To assign user and group permissions and check user access: | ||
|
||
```python | ||
from permagate.models import UserPermission, GroupPermission | ||
from permagate.core import has_permission | ||
from django.contrib.auth import get_user_model | ||
from django.contrib.auth.models import Group | ||
|
||
User = get_user_model() | ||
|
||
# Assign a permission to a user | ||
test_user = User.objects.create(username="test") | ||
UserPermission.objects.create(user=test_user, permission="test>") | ||
|
||
# Assign a permission to a group | ||
test_group = Group.objects.create(group="test") | ||
GroupPermission.objects.create(group=test_group, permission="test.sub1>") | ||
|
||
test_user_two = User.objects.create(username="test2") | ||
test_group.user_set.add(test_user_two) | ||
|
||
if has_permission(test_user, "test.sub1"): | ||
print(f"User {test_user.username} has permission test.sub1 due to directly assignment") | ||
|
||
if has_permission(test_user_two, "test.sub1"): | ||
print(f"fUser {test_user_two.username} has permission test.sub1 via group assignment") | ||
``` | ||
|
||
Note that the permission strings assigned to users may include the inclusive wildcard | ||
character while absolute permissions strings must be used when checking user permissions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,68 @@ | ||
from django.db.models import QuerySet | ||
from .models import User | ||
from .permission import Permission | ||
import logging | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def has_permission(user: User, permission: str) -> bool: | ||
""" | ||
Verifies that a user has a specific permission, or is a member of a group that has the permission. Note that the | ||
permission string "a.b>" implies that a user has the a.b permission as well as all child permissions. | ||
:param user: A User model instance | ||
:param permission: An absolute permission string (does not contain the inclusive wildcard '>') | ||
:return: True if the user has the specified permission | ||
""" | ||
root: Permission = Permission.get_root() | ||
if root.exists(permission): | ||
user_permission = _has_permission(user.permagate_permissions.all(), permission) | ||
if not user_permission: | ||
for group in user.groups.all(): | ||
group_permissions = group.permagate_permissions.all() | ||
if _has_permission(group_permissions, permission): | ||
return True | ||
else: | ||
return user_permission | ||
else: | ||
logger.warning(f"Checking for permission {permission} that does not exist") | ||
return False | ||
|
||
|
||
def _has_permission( | ||
queryset: QuerySet, | ||
permission: str, | ||
wildcard_only: bool = False, | ||
) -> bool: | ||
""" | ||
Determines if a given permission is contained in the specified queryset while considering the inclusive wildcard | ||
operator '>'. The root permission ('*') is also considered. | ||
:param queryset: The queryset we're operating on | ||
:param permission: The permission we're looking up | ||
:param wildcard_only: Only check if the permission string w/ an appended wildcard character is found in the queryset | ||
:return: True if the queryset contains the permission | ||
""" | ||
assert ( | ||
">" not in permission | ||
), "A required permission cannot contain an inclusive wildcard '>'" | ||
assert len(permission) > 0, "The permission string cannot be empty" | ||
found = ( | ||
not wildcard_only | ||
and ( | ||
queryset.filter(permission=permission).exists() | ||
or queryset.filter(permission="*") | ||
) | ||
) or queryset.filter(permission=f"{permission}>").exists() | ||
if found: | ||
return True | ||
path_segments = permission.split(".") | ||
path_segments.pop() | ||
# If there are no segments left in the permission string, the permission was not found | ||
if not path_segments: | ||
return False | ||
permission = ".".join(path_segments) | ||
# If not set, this is our first run, narrow down the queryset (performance gain?) | ||
if not wildcard_only: | ||
queryset = queryset.filter(permission__iendswith=">") | ||
return _has_permission(queryset, permission, True) | ||
from django.db.models import QuerySet | ||
from .models import User | ||
from .permission import Permission | ||
import logging | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def has_permission(user: User, permission: str) -> bool: | ||
""" | ||
Verifies that a user has a specific permission, or is a member of a group that has the permission. Note that the | ||
permission string "a.b>" implies that a user has the a.b permission as well as all child permissions. | ||
:param user: A User model instance | ||
:param permission: An absolute permission string (does not contain the inclusive wildcard '>') | ||
:return: True if the user has the specified permission | ||
""" | ||
root: Permission = Permission.get_root() | ||
if root.exists(permission): | ||
user_permission = _has_permission(user.permagate_permissions.all(), permission) | ||
if not user_permission: | ||
for group in user.groups.all(): | ||
group_permissions = group.permagate_permissions.all() | ||
if _has_permission(group_permissions, permission): | ||
return True | ||
else: | ||
return user_permission | ||
else: | ||
logger.warning(f"Checking for permission {permission} that does not exist") | ||
return False | ||
|
||
|
||
def _has_permission( | ||
queryset: QuerySet, | ||
permission: str, | ||
wildcard_only: bool = False, | ||
) -> bool: | ||
""" | ||
Determines if a given permission is contained in the specified queryset while considering the inclusive wildcard | ||
operator '>'. The root permission ('*') is also considered. | ||
:param queryset: The queryset we're operating on | ||
:param permission: The permission we're looking up | ||
:param wildcard_only: Only check if the permission string w/ an appended wildcard character is found in the queryset | ||
:return: True if the queryset contains the permission | ||
""" | ||
assert ( | ||
">" not in permission | ||
), "A required permission cannot contain an inclusive wildcard '>'" | ||
assert len(permission) > 0, "The permission string cannot be empty" | ||
found = ( | ||
not wildcard_only | ||
and ( | ||
queryset.filter(permission=permission).exists() | ||
or queryset.filter(permission="*") | ||
) | ||
) or queryset.filter(permission=f"{permission}>").exists() | ||
if found: | ||
return True | ||
path_segments = permission.split(".") | ||
path_segments.pop() | ||
# If there are no segments left in the permission string, the permission was not found | ||
if not path_segments: | ||
return False | ||
permission = ".".join(path_segments) | ||
# If not set, this is our first run, narrow down the queryset (performance gain?) | ||
if not wildcard_only: | ||
queryset = queryset.filter(permission__iendswith=">") | ||
return _has_permission(queryset, permission, True) |
Oops, something went wrong.