Skip to content

Commit

Permalink
Fix inconsistent line endings
Browse files Browse the repository at this point in the history
  • Loading branch information
davnov015 committed Jun 10, 2024
1 parent 03b299b commit 2aa0370
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 418 deletions.
2 changes: 1 addition & 1 deletion .idea/django_permagate.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions MANIFEST.in
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
214 changes: 107 additions & 107 deletions README.md
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.
136 changes: 68 additions & 68 deletions permagate/core.py
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)
Loading

0 comments on commit 2aa0370

Please sign in to comment.