python access management permission management

Python: Implementing a taxonomy based access control — second part

On the first part we discovered how to check which ressources are allowed for a given rule.

On the first part we discovered how to check which ressources are allowed for a given rule.

Now it is time to ensure an action is allowed for the current user!

Protecting an endpoint

Like for the first part, we will start by showing what is done before explaining how it is done.

Illustration by the author, an endpoint protected by access control

Illustration by the author, an endpoint protected by access control

The @register_route decorator is decorator that allow to register an endpoint at run time, you can have more intel about it on my previous article.

The @authentification decorator has a double usage, it first ensure the user is authenticated, it also inject the composite context object that is used both to access databases and to have intel about who is authenticated.

The last decorator, @action_required is what matters for us now. We wan’t the current user to have the create action enabled for the data/mongodb/ path where collection is taken from the url route.

Details about how the action_required decorator works.

For the current user we list all associated “principals” each user has at least one principal, based on its user name, it has also at least one other principal for each groups it belongs to (multiple principals if the group is a nested one).

So we start by:

  • listing the user principals
  • retrieving policies associated with those principals,
  • then filtering policies to only those that grant the expected “action”,
  • and at the end filtering remaining policies to verify if they match the current resource path (here the url route)

If at the end there is at least one policy remaining, it’s that the user is granted the right to access this particular resource!

Screenshot by the author (yes there is still room to improvement)

Screenshot by the author (yes there is still room to improvement)

A bit of source code for inspiration

    def fetch_policies(self) -> List[Policy]:
        from applications.pinceau6.models.principal_policies import PrincipalPolicies

        principal_list = [self.principal, *self.groups]

        global_context = GlobalContext.get_instance()
        mongodb_handler = MongoDBHandler.from_default(global_context)

        principal_policies = mongodb_handler.load_multiples(
            PrincipalPolicies.MODEL, {"principal": {"$in": principal_list}}
        )

        all_policies = []
        for principal_policy in principal_policies:
            all_policies += principal_policy.policies

        return all_policies

def action_required(action: str, resource: str, mode=ActionRequiredMode):
    def my_decorator(func):
        @wraps(func)
        def decorated_view(*args, **kwargs):
            from core.context.composite_context import CompositeContext
            from core.context.user_context import UserContext

            context = cast(CompositeContext, kwargs.get("context"))
            user_context = context.cast_as(UserContext)

            resource_parts = []
            for rsc_part in resource.split("/"):
                if rsc_part.startswith("<") and rsc_part.endswith(">"):
                    resource_parts.append(kwargs[rsc_part[1:-1]])
                else:
                    resource_parts.append(rsc_part)

            if not user_context.is_allowed(action, "/".join(resource_parts)):
                abort(404)

            return func(*args, **kwargs)

        return decorated_view

    return my_decorator

Sorry guys I don’t share all my source code with you, but I still need to preserve some secrets ;) (and subjects to make articles about).

That’s all folks