Source code for anyblok.authorization.binding

# This file is a part of the AnyBlok project
#
#    Copyright (C) 2015 Georges Racinet <gracinet@anybox.fr>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
"""Authorization subframework.

The founding principle of authorization handling within Anyblok is to check
authorization explicitely at the edge of the system (for instance,
for applications
exposed over HTTP, that would be in the controllers), because that is where the
idea of user, or slightly more generally, has functional semantics that can
be interpreted in the context of a given action.

In that spirit, we don't pass the user to the core framework and business
layers.
Instead, these provide *policies* to check permissions on records or query
records according to the.

The declarations at the edge will *associate* the policies with the
models. The edge user-aware methods will call the check and query facilities
provided by the core that themselves apply the relevant policies.
"""
from copy import deepcopy

from ..declarations import Declarations
from ..environment import EnvironmentManager
from ..registry import RegistryManager


@Declarations.add_declaration_type(isAnEntry=True, assemble="assemble_callback")
class AuthorizationBinding:
    """Encodes which policy to use per model or (model, permission).

    In the assembly phase, copies of the policy are issued, and the registry
    is set as an attribute on them. This is a bit memory inefficient, but
    otherwise, passing the registry would have to be in all AuthorizationRule
    API calls.
    """

    def __new__(cls, model_declaration, policy, permission=None):
        """Declare for given model that policy should be used.

        :param permission: if provided, the policy will apply for this
                           permission only, otherwise, it will act as the
                           default policy for this model.
        """
        cb = EnvironmentManager.get("current_blok")
        model = model_declaration.__registry_name__
        key = (model, permission) if permission is not None else model
        blok_declarations = RegistryManager.loaded_bloks[cb][cls.__name__]
        blok_declarations[key] = policy

    @classmethod
    def assemble_callback(cls, registry):
        policies = {}
        for blok in registry.ordered_loaded_bloks:
            policies.update(RegistryManager.loaded_bloks[blok][cls.__name__])

        # for this registry entry, the list of names is irrelevant pollution:
        del policies["registry_names"]
        registry._authz_policies = deepcopy(policies)
        for policy in registry._authz_policies.values():
            policy.registry = registry