Skip to content

Proposal: Combined permissions #745

@iamcrookedman

Description

@iamcrookedman

Feature Request Type

  • Core functionality
  • Alteration (enhancement/optimization) of existing feature(s)
  • New behavior

Description

In Django it is possible to combine different permissions to manage access to objects more flexibly. In one of my projects I implemented a similar mechanism, which looks like this:

from django.conf import settings

User = settings.AUTH_USER_MODEL


class AND:
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def has_permission(self, user: User) -> bool:
        return self.op1.has_permission(user=user) and self.op2.has_permission(user=user)


class OR:
    def __init__(self, op1, op2):
        self.op1 = op1
        self.op2 = op2

    def has_permission(self, user: User) -> bool:
        return self.op1.has_permission(user=user) or self.op2.has_permission(user=user)


class OperationHolderMixin:
    def __and__(self, other):
        return OperandHolder(AND, self, other)

    def __or__(self, other):
        return OperandHolder(OR, self, other)


class OperandHolder(OperationHolderMixin):
    def __init__(self, operator_class, op1_class, op2_class):
        self.operator_class = operator_class
        self.op1_class = op1_class
        self.op2_class = op2_class

    def __call__(self, *args, **kwargs):
        op1 = self.op1_class(*args, **kwargs)
        op2 = self.op2_class(*args, **kwargs)
        return self.operator_class(op1, op2)

    def __eq__(self, other):
        return (
            isinstance(other, OperandHolder)
            and self.operator_class == other.operator_class
            and self.op1_class == other.op1_class
            and self.op2_class == other.op2_class
        )

    def __hash__(self):
        return hash((self.operator_class, self.op1_class, self.op2_class))


class BasePermissionMetaclass(OperationHolderMixin, type):
    pass


class BasePermission(metaclass=BasePermissionMetaclass):
    def has_permission(self, user: User) -> bool:
        return True
class HasPermissions(DjangoPermissionExtension):
    def __init__(self, *args, permission_classes: Iterable, **kwargs):
        super().__init__(*args, **kwargs)
        self.permission_classes = permission_classes

    def resolve_for_user(  # pragma: no cover
        self,
        resolver: Callable,
        user: UserType | None,
        *,
        info: Info,
        source: Any,
    ) -> AwaitableOrValue[Any]:
        if not user.is_authenticated or not user.is_active:
            raise DjangoNoPermission

        permissions = [permission() for permission in self.permission_classes]
        for permission in permissions:
            if not permission.has_permission(user=user):
                raise DjangoNoPermission
        return resolver()
class HasPerm1(BasePermission):
    def has_permission(self, user: User):
        return user.has_perm_1

class HasPerm2(BasePermission):
    def has_permission(self, user: User):
        return user.has_perm_2

Usage example:

@strawberry_django.mutation(
    extensions=[HasPermissions(permission_classes=(HasPerm1 | HasPerm2,))]
)
...

What do you think about this?
If you are interested in this idea, let me know and I can try to contribute myself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions