|
1 | | -"""Centralization of all decision-making processes on a single decision point""" |
| 1 | +import re |
| 2 | +from typing import Any, Literal |
2 | 3 |
|
3 | | -import importlib.util |
4 | | -import pathlib |
5 | | -import types |
6 | | -from typing import Any |
7 | | - |
8 | | -from entitled import policies |
| 4 | +from entitled.exceptions import AuthorizationException |
| 5 | +from entitled.policies import Policy |
| 6 | +from entitled.response import Err, Response |
| 7 | +from entitled.rules import Actor, Rule, RuleProto |
9 | 8 |
|
10 | 9 |
|
11 | 10 | class Client: |
12 | | - "The Client class for decision-making centralization." |
| 11 | + def __init__(self): |
| 12 | + self._policy_registry: dict[type, Policy[Any]] = {} |
| 13 | + self._rule_registry: dict[str, Rule[Any]] = {} |
| 14 | + |
| 15 | + def define_rule(self, name: str, callable: RuleProto[Actor]) -> Rule[Actor]: |
| 16 | + rule = Rule(name, callable) |
| 17 | + self._rule_registry[rule.name] = rule |
| 18 | + return rule |
13 | 19 |
|
14 | | - def __init__(self, base_path: str | None = None): |
15 | | - self._policy_registrar: dict[type, policies.Policy[Any]] = {} |
16 | | - self._load_path = None |
17 | | - if base_path: |
18 | | - self._load_path = pathlib.Path(base_path) |
19 | | - self.load_policies_from_path(self._load_path) |
| 20 | + def register_policy(self, policy: Policy[Any]): |
| 21 | + resource_type = getattr(policy, "__orig_class__").__args__[0] |
| 22 | + self._policy_registry[resource_type] = policy |
20 | 23 |
|
21 | | - def authorize( |
| 24 | + def _resolve_policy(self, resource: Any) -> Policy[Any] | None: |
| 25 | + lookup_key = resource if isinstance(resource, type) else type(resource) |
| 26 | + policy = self._policy_registry.get(lookup_key, None) |
| 27 | + return policy |
| 28 | + |
| 29 | + async def inspect( |
| 30 | + self, |
| 31 | + name: str, |
| 32 | + actor: Any, |
| 33 | + resource: Any, |
| 34 | + *args: Any, |
| 35 | + **kwargs: Any, |
| 36 | + ) -> Response: |
| 37 | + policy = self._resolve_policy(resource) |
| 38 | + if policy is not None: |
| 39 | + return await policy.inspect(name, actor, resource, *args, **kwargs) |
| 40 | + return Err(f"No policy found with name '{name}'") |
| 41 | + |
| 42 | + async def allows( |
22 | 43 | self, |
23 | | - action: str, |
| 44 | + name: str, |
24 | 45 | actor: Any, |
25 | 46 | resource: Any, |
26 | | - context: dict[str, Any] | None = None, |
| 47 | + *args: Any, |
| 48 | + **kwargs: Any, |
27 | 49 | ) -> bool: |
28 | | - policy = self._policy_lookup(resource) |
29 | | - return policy.authorize(action, actor, resource, context) |
| 50 | + return (await self.inspect(name, actor, resource, *args, **kwargs)).allowed() |
30 | 51 |
|
31 | | - def allows( |
| 52 | + async def denies( |
32 | 53 | self, |
33 | | - action: str, |
| 54 | + name: str, |
34 | 55 | actor: Any, |
35 | 56 | resource: Any, |
36 | | - context: dict[str, Any] | None = None, |
| 57 | + *args: Any, |
| 58 | + **kwargs: Any, |
37 | 59 | ) -> bool: |
38 | | - policy = self._policy_lookup(resource) |
39 | | - return policy.allows(action, actor, resource, context) |
| 60 | + return not await self.allows(name, actor, resource, *args, **kwargs) |
40 | 61 |
|
41 | | - def grants( |
| 62 | + async def authorize( |
| 63 | + self, |
| 64 | + name: str, |
| 65 | + actor: Any, |
| 66 | + resource: Any, |
| 67 | + *args: Any, |
| 68 | + **kwargs: Any, |
| 69 | + ) -> Literal[True]: |
| 70 | + result = await self.inspect(name, actor, resource, *args, **kwargs) |
| 71 | + if not result.allowed(): |
| 72 | + raise AuthorizationException(result.message()) |
| 73 | + return True |
| 74 | + |
| 75 | + async def grants( |
42 | 76 | self, |
43 | 77 | actor: Any, |
44 | 78 | resource: Any, |
45 | | - context: dict[str, Any] | None = None, |
| 79 | + *args: Any, |
| 80 | + **kwargs: Any, |
46 | 81 | ) -> dict[str, bool]: |
47 | | - policy = self._policy_lookup(resource) |
48 | | - return policy.grants(actor, resource, context) |
49 | | - |
50 | | - def register(self, policy: policies.Policy[Any]): |
51 | | - if hasattr(policy, "__orig_class__"): |
52 | | - resource_type = getattr(policy, "__orig_class__").__args__[0] |
53 | | - if resource_type not in self._policy_registrar: |
54 | | - self._policy_registrar[resource_type] = policy |
55 | | - else: |
56 | | - raise ValueError( |
57 | | - "A policy is already registered for this resource type" |
58 | | - ) |
59 | | - else: |
60 | | - raise AttributeError(f"Policy {policy} is incorrectly defined") |
61 | | - |
62 | | - def reload_registrar(self): |
63 | | - if self._load_path is not None: |
64 | | - self.load_policies_from_path(self._load_path) |
65 | | - |
66 | | - def load_policies_from_path(self, path: pathlib.Path): |
67 | | - for file_path in path.glob("*.py"): |
68 | | - print(file_path) |
69 | | - mod_name = file_path.stem |
70 | | - full_module_name = ".".join(file_path.parts[:-1] + (mod_name,)) |
71 | | - spec = importlib.util.spec_from_file_location(full_module_name, file_path) |
72 | | - if spec: |
73 | | - module = importlib.util.module_from_spec(spec) |
74 | | - if spec.loader: |
75 | | - try: |
76 | | - spec.loader.exec_module(module) |
77 | | - except Exception as e: |
78 | | - raise e |
79 | | - |
80 | | - self._register_from_module(module) |
81 | | - |
82 | | - def _register_from_module(self, module: types.ModuleType): |
83 | | - for attribute_name in dir(module): |
84 | | - attr = getattr(module, attribute_name) |
85 | | - if isinstance(attr, policies.Policy): |
86 | | - try: |
87 | | - self.register(attr) |
88 | | - except (ValueError, AttributeError): |
89 | | - pass |
90 | | - |
91 | | - def _policy_lookup(self, resource: Any) -> policies.Policy[Any]: |
92 | | - lookup_key = resource if isinstance(resource, type) else type(resource) |
93 | | - |
94 | | - if lookup_key not in self._policy_registrar: |
95 | | - raise ValueError("No policy registered for this resource type") |
96 | | - |
97 | | - return self._policy_registrar[lookup_key] |
| 82 | + policy = self._resolve_policy(resource) |
| 83 | + if policy is None: |
| 84 | + return {} |
| 85 | + return await policy.grants(actor, resource, *args, **kwargs) |
0 commit comments