Package: px-access-scopes
Access scopesUtility package for access scopes definition and checking.Installationpipinstallpx-access-scopesUsageDefining access scopes:access_scopes.pyfrompx_access_scopesimportScopeRegistry,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')classTokens(Enum):AUTO1=autoAUTO2=autoRAW=raw('RAW')FIXED='SOME'@Tokens.nest('NESTED')classNested:AUTO=autoSOME='OTHER'Defining scopes aggregates(roles):access_aggregates.pyfrompx_access_scopesimportAggregatesfrom.access_scopesimportTokens,NestedclassRoles(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:frompx_access_scopesimport(ScopesCheckRunner,scopes_checker,aggregates_checker,HierarchyChecker,domain_path_hierarchy_lookup,)from.access_scopesimportTokens,Nestedfrom.access_aggregatesimportRoles# 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)# > Truechecker((Nested.SOME,),**USER1)# > Falsechecker((Tokens.RAW,),**USER2)# > Truechecker((Tokens.RAW,),**USER3)# > Truechecker((Nested.AUTO,),**USER3)# > Truechecker((Nested.SOME,),**USER3)# > TrueDjangoRegistering scopes registries and access tokens aggregates.On everymanage.py migrateauth Permissions and Groups will be autogenerated based on registered definitions.settings.pyPX_ACCESS_TOKENS_REGISTRIES=['access_scopes.staff_root',]PX_ACCESS_TOKENS_AGGREGATES=['access_scopes.Roles',]access_scopes.pyfromdjango.utils.translationimportpgettext_lazyfromdjango.db.models.enumsimportTextChoices# Has it's own, a little bit improved for django implementations:frompx_access_scopes.contrib.djangoimportAggregate,Aggregates,ScopeRegistryclassRoles(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'))classUsers(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.frompx_access_scopesimport(ScopesChecker,HierarchyChecker,ScopesCheckRunner,MultiregistryHierarchyLookup)frompx_access_scopes.contrib.djangoimportScopeDomain,user_checker# All registries, that you've registered in configfrompx_access_scopes.contrib.django.globalsimportregistriesfrom.access_scopesimportUsers# 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)),))defmy_view(request):can=checker(# `.permission` - is the django's permission string.(User.CHANGE.permission,),# User kwarg is required for `user_checker`.user=request.user,)ifnotcan:raise......For an easier usage scope on frontend there is an export mechanics:frompx_access_scopes.contrib.django.exportimportexport_scopesFunctionexport_scopesexports all scopes from all registered registries. It has two modes: shorter oneexport_scopes(as_leaves=True)and more verbose and fullexport_scopes(as_leaves=False). It's for you to decide which one is preferable.DRFFor 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.pyfromrest_framework.permissionsimportIsAuthenticatedfrom.access_scopesimportUsersclassUserUpdateDestroyAPIView: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=UserSerializerChangelogAll notable changes to this project will be documented in this file.The format is based onKeep a Changelog,
and this project adheres toSemantic Versioning.[Unreleased][0.1.7]AddedDjango 4.x support.[0.1.0]Initial version.