Loading repository data…
Loading repository data…
presslabs / repository
A package intended to aid developers in implementing authorization for Django apps.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A useful package for implementing authorization in your Django apps.
This project is developed at Presslabs.
pip install django-woah
Then, in your settings.py, add it to the INSTALLED_APPS:
INSTALLED_APPS = [
# [...]
"django_woah",
]
For what it can not do, check the shortcomings and limitations.
Let's say we're making an issue tracker kind of app. Then, our Issue model might look something like this:
# my_app/issue_tracker/models.py
class Issue(models.Model):
owner = models.ForeignKey(
Organization, on_delete=models.CASCADE, related_name="owned_issues"
)
author = models.ForeignKey(
User,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="authored_issues",
)
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="issues"
)
title = models.CharField(max_length=512)
content = models.TextField()
state = models.CharField(max_length=16, default=IssueState.OPEN)
Now, let's define an AuthorizationScheme for our Issue model, starting with some CRUD permissions, and a permission for closing/reopening issues. We'll also add an Issue Manager role:
# my_app/issue_tracker/authorization.py
# Here you can define authorization schemes for your models
from django_woah.authorization import ModelAuthorizationScheme, PermEnum
class IssueAuthorizationScheme(ModelAuthorizationScheme):
model = Issue
class Perms(PermEnum):
# The values here are up to preference. But we're going
# to use an "issue:" prefix, to avoid collisions with other
# model perms and roles, when storing to the database.
ISSUE_VIEW = "issue:issue_view"
ISSUE_CREATE = "issue:issue_create"
ISSUE_EDIT_CONTENT = "issue:issue_edit_content"
ISSUE_CHANGE_STATE = "issue:issue_change_state"
ISSUE_DELETE = "issue:issue_delete"
class Roles(PermEnum):
ISSUE_MANAGER = "issue:manager"
We want our Issue Manager role to receive all the permissions above, so we are going to add some indirect permissions logic to our IssueAuthorizationScheme:
# my_app/issue_tracker/authorization.py
from django_woah.authorization import (
# [...]
IndirectPerms, ConditionalPerms, HasSameResourcePerms,
)
class IssueAuthorizationScheme(ModelAuthorizationScheme):
# [...]
def get_indirect_perms(self, context: Context) -> list[IndirectPerms]:
return [
ConditionalPerms(
conditions=[
HasSameResourcePerms([self.Roles.ISSUE_MANAGER]),
],
receives_perms=self.Perms.values(),
),
]
Now we might want to ensure that only members/collaborators of the organization that owns the issue may receive authorization. For that we must establish the owner_relation (field) of the Issue model, and set up an implicit condition that will represent our Membership check.
# my_app/issue_tracker/authorization.py
class IssueAuthorizationScheme(ModelAuthorizationScheme):
# [...]
owner_relation = "owner" # this references the owner ForeignKey field in our Issue model
def get_implicit_conditions(
self, context: Context
) -> list[Condition]:
return [
HasRootMembership(actor=context.actor),
]
# [...]
Noticed the project field in the Issue model? It can be used to organize a bunch of issues. Let's then allow assigning permissions in a project-wide manner. To achieve this, we need to add another indirect way of obtaining permissions for Issues:
# my_app/issue_tracker/authorization.py
from django_woah.authorization import TransitiveFromRelationPerms
class IssueAuthorizationScheme(ModelAuthorizationScheme):
# [...]
def get_indirect_perms(self, context: Context) -> list[IndirectPerms]:
return [
# [...],
# You'd need to have a ProjectAuthorizationScheme in place for
# this to properly work, but it's out of scope for this example.
TransitiveFromRelationPerms(
relation="project",
),
]
What if we want to allow authors to manage their own issues a bit?
from django_woah.authorization import QCondition
# [...]
def get_indirect_perms(self, context: Context) -> list[IndirectPerms]:
return [
# [...],
ConditionalPerms(
conditions=[
# The implicit membership condition we defined above,
# will still apply, so if the author were to lose
# membership, this condition will not apply anymore.
QCondition(Q(author=context.actor)),
],
receives_perms=[
self.Perms.ISSUE_VIEW,
self.Perms.ISSUE_EDIT_CONTENT,
self.Perms.ISSUE_CHANGE_STATE,
],
),
]
To see more code in action you can check the examples, or read about how it all works.
UserGroups represents, as the name implies, a group of users; for convenience these can be used to represent a single user, a Team, or the entirety of members/collaborators of an organization/account.Memberships are used to represent a Django User's membership into the aforementioned UserGroups.AssignedPerms represent the direct relation between a UserGroup (actors), a Perm/Role and a resource (usually a Django Model instance or class).AuthorizationSchemes define what actors are allowed to do, and under what Conditions.
Permissions definitions; Roles can be defined separately, but they are permissions too.AuthorizationSolver glues together the user-defined AuthorizationSchemes. It's responsible for enforcing authorization.Context consists mostly of an actor, permissions and a resource, and an optional extra field. It acts both as a query and state, and it's passed to the AuthorizationSolver.Conditions are what authorization is granted (or not) on.
get_resources_q method, which returns a Django Q that filters the resources that match the condition, and is the one used when fetching resources from the DB;verify_authorization method (name is subject to change), which returns a bool, and is used to verify the condition for prefetched or about to be created resources.get_assigned_perms_q method, which is used to prefetch any AssignedPerms from the database, that might be of use in the previously mentioned important methods;get_memberships_q method, which is used to prefetch any Memberships from the database; although it may be used in get_resources_q, it's more commonly used in the verify_authorization methods that belong to conditions related to memberships.AUTH_USER_MODEL. This implies that your Organizations must share the same model with your Users (which we believe simplifies things for most cases). Account could be your model name to represent both Users and Organizations. While it should be possible to work around this assumption, everything is set up to work this way out of the box.If these are dealbreakers for you or you are simply looking for something else, check the alternatives.
ping@presslabs.com.