Simple access-scopes utility package.
Project description
Access scopes
Utility package for access scopes definition and checking.
Installation
pip install px-access-scopes
Usage
Defining access scopes:
access_scopes.py
from px_access_scopes import ScopeRegistry, ScopeDomain, auto, raw
# Creating root access scopes registry.
root = ScopeRegistry.create_root(ScopeDomain('TOKENS'))
# Tokens can be any Enum or even a simple object.
@root.nest('TOKENS')
class Tokens(Enum):
AUTO1 = auto
AUTO2 = auto
RAW = raw('RAW')
FIXED = 'SOME'
@Tokens.nest('NESTED')
class Nested:
AUTO = auto
SOME = 'OTHER'
Defining scopes aggregates(roles):
access_aggregates.py
from px_access_scopes import Aggregates
from .access_scopes import Tokens, Nested
class Roles(Aggregates):
Simple = Aggregate('Simple')
First = Aggregate('First')
Roles.Simple.add(Tokens.RAW)
Roles.First.add(Tokens.RAW)
Roles.First.add(Nested.SOME)
Run checkers whenever you need:
from px_access_scopes import (
ScopesCheckRunner, scopes_checker, aggregates_checker, HierarchyChecker,
domain_path_hierarchy_lookup,
)
from .access_scopes import Tokens, Nested
from .access_aggregates import Roles
# Defining a checker. The result is just a simple callable.
# ScopesCheckRunner receives special checker runners list and evaluates them,
# until it founds a match.
checker = ScopesCheckRunner((
HierarchyChecker((
scopes_checker,
aggregates_checker,
), hierarchy_lookup=domain_path_hierarchy_lookup),
))
USER1 = {'scopes': [Nested.AUTO]}
USER2 = {'scopes': [Nested.AUTO], 'aggregates': [Roles.Simple]}
USER3 = {'scopes': [Nested.AUTO], 'aggregates': [Roles.First]}
# It receives scopes list and kwargs, that internal checkers need to make decision.
checker((Nested.AUTO,), **USER1) # > True
checker((Nested.SOME,), **USER1) # > False
checker((Tokens.RAW,), **USER2) # > True
checker((Tokens.RAW,), **USER3) # > True
checker((Nested.AUTO,), **USER3) # > True
checker((Nested.SOME,), **USER3) # > True
Django
Registering scopes registries and access tokens aggregates.
On every manage.py migrate
auth Permissions and Groups will be autogenerated based on registered definitions.
settings.py
PX_ACCESS_TOKENS_REGISTRIES = [
'access_scopes.staff_root',
]
PX_ACCESS_TOKENS_AGGREGATES = [
'access_scopes.Roles',
]
access_scopes.py
from django.utils.translation import pgettext_lazy
from django.db.models.enums import TextChoices
# Has it's own, a little bit improved for django implementations:
from px_access_scopes.contrib.django import Aggregate, Aggregates, ScopeRegistry
class Roles(Aggregates):
# First parameter for any django aggregate is group identifier.
# Second is a key name, and the third one is an aggregate's verbose_name.
Admin = Aggregate(5000, 'Admin', pgettext_lazy('staff', 'Admin'))
Owner = Aggregate(4900, 'Owner', pgettext_lazy('staff', 'Owner'))
Reader = Aggregate(4800, 'Reader', pgettext_lazy('staff', 'Reader'))
staff_root = ScopeRegistry.create_root('STAFF', pgettext_lazy('staff', 'Staff'))
# TextChoices is also a Enum, but with labels so better use a django-specific registry.
@staff_root.nest('USERS', pgettext_lazy('staff', 'Users'))
class Users(TextChoices):
VIEW = 'VIEW', pgettext_lazy('staff', 'View')
CHANGE = 'CHANGE', pgettext_lazy('staff', 'Change')
CHANGE_OWN = 'CHANGE_OWN', pgettext_lazy('staff', 'Change own')
DISABLE = 'DISABLE', pgettext_lazy('staff', 'Disable')
SHARED = {Users.CHANGE_OWN}
Roles.Admin.update(
SHARED
| {Users.CHANGE, Users.VIEW, Users.DISABLE}
)
Roles.Owner.update(
SHARED
| {Users.CHANGE_OWN, Users.VIEW}
)
Roles.Reader.update(
SHARED
)
And so now you may run a checker for any user. Internally django checker will use user's .has_perm
. So this way administrators cay manage access for any user.
from px_access_scopes import (
ScopesChecker, HierarchyChecker, ScopesCheckRunner,
MultiregistryHierarchyLookup
)
from px_access_scopes.contrib.django import ScopeDomain, user_checker
# All registries, that you've registered in config
from px_access_scopes.contrib.django.globals import registries
from .access_scopes import Users
# Simple checker here.
checker: ScopesChecker = ScopesCheckRunner((
HierarchyChecker(
# Django-specific checker that calls `user.has_perm`.
(user_checker,),
hierarchy_lookup=MultiregistryHierarchyLookup(
# This might be any registries, not default ones.
registries=registries
)
),
))
def my_view(request):
can = checker(
# `.permission` - is the django's permission string.
(User.CHANGE.permission,),
# User kwarg is required for `user_checker`.
user=request.user,
)
if not can:
raise ...
...
For an easier usage scope on frontend there is an export mechanics:
from px_access_scopes.contrib.django.export import export_scopes
Function export_scopes
exports all scopes from all registered registries. It has two modes: shorter one export_scopes(as_leaves=True)
and more verbose and full export_scopes(as_leaves=False)
. It's for you to decide which one is preferable.
DRF
For django rest framework there are ready-to use permission classes.
They use the same checker mechanics as described above in django checking section.
some_views.py
from rest_framework.permissions import IsAuthenticated
from .access_scopes import Users
class UserUpdateDestroyAPIView:
permission_classes = (
IsAuthenticated
&
(
# You may pass multiple scopes here.
# Checker will be evaluated only for passed methods.
# `methods` keyword is optional as by default it will check permission
# for every possible method.
ScopePermission.from_scopes(Users.VIEW, methods=('GET',))
|
ScopePermission.from_scopes(Users.CHANGE, methods=('PUT', 'PATCH))
|
ScopePermission.from_scopes(Users.CHANGE_OWN, methods=('PUT', 'PATCH))
|
ScopePermission.from_scopes(Users.DISABLE, methods=('DELETE',))
),
)
serializer_class = UserSerializer
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.