|
1 | | -"""Centralization of all decision-making processes on a single decision point""" |
| 1 | +from typing import Any, Literal |
2 | 2 |
|
3 | | -import importlib.util |
4 | | -import pathlib |
5 | | -import types |
6 | | -from typing import Any |
7 | | - |
8 | | -from entitled import policies |
| 3 | +from entitled.exceptions import AuthorizationException |
| 4 | +from entitled.response import Err, Response |
| 5 | +from entitled.rules import Actor, Rule, RuleProto |
9 | 6 |
|
10 | 7 |
|
11 | 8 | class Client: |
12 | | - "The Client class for decision-making centralization." |
| 9 | + def __init__(self): |
| 10 | + self._rule_registry: dict[str, Rule[Any]] = {} |
13 | 11 |
|
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) |
| 12 | + def define_rule(self, name: str, callable: RuleProto[Actor]) -> Rule[Actor]: |
| 13 | + rule = Rule(name, callable) |
| 14 | + self._rule_registry[rule.name] = rule |
| 15 | + return rule |
20 | 16 |
|
21 | | - async def authorize( |
| 17 | + async def inspect( |
22 | 18 | self, |
23 | | - action: str, |
24 | | - actor: Any, |
25 | | - resource: Any, |
26 | | - context: dict[str, Any] | None = None, |
27 | | - ) -> bool: |
28 | | - policy = self._policy_lookup(resource) |
29 | | - return await policy.authorize(action, actor, resource, context) |
| 19 | + name: str, |
| 20 | + actor: Actor, |
| 21 | + *args: Any, |
| 22 | + **kwargs: Any, |
| 23 | + ) -> Response: |
| 24 | + if name in self._rule_registry: |
| 25 | + return await self._rule_registry[name].inspect(actor, *args, **kwargs) |
| 26 | + return Err(f"No rule found with name '{name}'") |
30 | 27 |
|
31 | 28 | async def allows( |
32 | 29 | self, |
33 | | - action: str, |
34 | | - actor: Any, |
35 | | - resource: Any, |
36 | | - context: dict[str, Any] | None = None, |
| 30 | + name: str, |
| 31 | + actor: Actor, |
| 32 | + *args: Any, |
| 33 | + **kwargs: Any, |
37 | 34 | ) -> bool: |
38 | | - policy = self._policy_lookup(resource) |
39 | | - return await policy.allows(action, actor, resource, context) |
| 35 | + if name in self._rule_registry: |
| 36 | + return await self._rule_registry[name].allows(actor, *args, **kwargs) |
| 37 | + return False |
40 | 38 |
|
41 | | - async def grants( |
| 39 | + async def denies( |
42 | 40 | self, |
43 | | - actor: Any, |
44 | | - resource: Any, |
45 | | - context: dict[str, Any] | None = None, |
46 | | - ) -> dict[str, bool]: |
47 | | - policy = self._policy_lookup(resource) |
48 | | - return await 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") |
| 41 | + name: str, |
| 42 | + actor: Actor, |
| 43 | + *args: Any, |
| 44 | + **kwargs: Any, |
| 45 | + ) -> bool: |
| 46 | + return not self.allows(name, actor, *args, **kwargs) |
96 | 47 |
|
97 | | - return self._policy_registrar[lookup_key] |
| 48 | + async def authorize( |
| 49 | + self, |
| 50 | + name: str, |
| 51 | + actor: Actor, |
| 52 | + *args: Any, |
| 53 | + **kwargs: Any, |
| 54 | + ) -> Literal[True]: |
| 55 | + if name in self._rule_registry: |
| 56 | + return await self._rule_registry[name].authorize(actor, *args, **kwargs) |
| 57 | + raise AuthorizationException(f"No rule found with name '{name}'") |
0 commit comments