Skip to content

Commit 4b5c622

Browse files
author
Mathias BIGAIGNON
committed
Finishing up v1
1 parent 153d1cb commit 4b5c622

File tree

10 files changed

+304
-276
lines changed

10 files changed

+304
-276
lines changed

entitled/client.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from typing import Any, Literal
23

34
from entitled.exceptions import AuthorizationException
@@ -8,67 +9,77 @@
89

910
class Client:
1011
def __init__(self):
11-
self._policy_registry: dict[type, type[Policy[Any]]] = {}
12+
self._policy_registry: dict[type, Policy[Any]] = {}
1213
self._rule_registry: dict[str, Rule[Any]] = {}
1314

1415
def define_rule(self, name: str, callable: RuleProto[Actor]) -> Rule[Actor]:
1516
rule = Rule(name, callable)
1617
self._rule_registry[rule.name] = rule
1718
return rule
1819

19-
def _register_policy(self, policy: type[Policy[Any]]):
20-
resource_type = getattr(policy, "__orig_bases__")[0].__args__[0]
20+
def register_policy(self, policy: Policy[Any]):
21+
resource_type = getattr(policy, "__orig_class__").__args__[0]
2122
self._policy_registry[resource_type] = policy
2223

23-
def _resolve_rule(self, name: str, resource: Any) -> Rule[Any] | None:
24+
def _resolve_policy(self, resource: Any) -> Policy[Any] | None:
2425
lookup_key = resource if isinstance(resource, type) else type(resource)
2526
policy = self._policy_registry.get(lookup_key, None)
26-
if policy is not None:
27-
rule = getattr(policy, name, None)
28-
if rule is not None and hasattr(rule, "_is_rule"):
29-
return Rule(name, rule)
30-
return self._rule_registry.get(name, None)
27+
return policy
3128

3229
async def inspect(
3330
self,
3431
name: str,
35-
actor: Actor,
32+
actor: Any,
3633
resource: Any,
3734
*args: Any,
3835
**kwargs: Any,
3936
) -> Response:
40-
rule = self._resolve_rule(name, resource)
41-
if rule is not None:
42-
return await rule.inspect(actor, resource, *args, **kwargs)
43-
return Err(f"No rule found with name '{name}'")
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}'")
4441

4542
async def allows(
4643
self,
4744
name: str,
48-
actor: Actor,
45+
actor: Any,
46+
resource: Any,
4947
*args: Any,
5048
**kwargs: Any,
5149
) -> bool:
52-
if name in self._rule_registry:
53-
return await self._rule_registry[name].allows(actor, *args, **kwargs)
54-
return False
50+
return (await self.inspect(name, actor, resource, *args, **kwargs)).allowed()
5551

5652
async def denies(
5753
self,
5854
name: str,
59-
actor: Actor,
55+
actor: Any,
56+
resource: Any,
6057
*args: Any,
6158
**kwargs: Any,
6259
) -> bool:
63-
return not self.allows(name, actor, *args, **kwargs)
60+
return not await self.allows(name, actor, resource, *args, **kwargs)
6461

6562
async def authorize(
6663
self,
6764
name: str,
68-
actor: Actor,
65+
actor: Any,
66+
resource: Any,
6967
*args: Any,
7068
**kwargs: Any,
7169
) -> Literal[True]:
72-
if name in self._rule_registry:
73-
return await self._rule_registry[name].authorize(actor, *args, **kwargs)
74-
raise AuthorizationException(f"No rule found with name '{name}'")
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(
76+
self,
77+
actor: Any,
78+
resource: Any,
79+
*args: Any,
80+
**kwargs: Any,
81+
) -> dict[str, bool]:
82+
policy = self._resolve_policy(resource)
83+
if policy is None:
84+
return {}
85+
return await policy.grants(actor, resource, *args, **kwargs)

entitled/old_client.py

Lines changed: 0 additions & 97 deletions
This file was deleted.

entitled/old_policies.py

Lines changed: 0 additions & 89 deletions
This file was deleted.

entitled/policies.py

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,93 @@
1-
import functools
2-
from typing import Any, Callable
1+
"""Grouping of authorization rules around a particular resource type"""
32

3+
from typing import Any, TypeVar
44

5-
def rule(func: Callable[..., Any]):
6-
@functools.wraps(func)
7-
async def wrapper(*args: Any, **kwargs: Any) -> Any:
8-
return await func(*args, **kwargs)
5+
from entitled import exceptions
6+
from entitled.response import Err, Ok, Response
7+
from entitled.rules import Rule, RuleProto
98

10-
setattr(wrapper, "_is_rule", True)
11-
return wrapper
9+
T = TypeVar("T")
1210

1311

1412
class Policy[T]:
15-
pass
13+
"""A grouping of rules refering the given resource type."""
14+
15+
_registry: dict[str, Rule[Any]]
16+
17+
def __init__(
18+
self,
19+
rules: dict[str, Rule[Any]] | None = None,
20+
):
21+
self._registry = {}
22+
23+
if not rules:
24+
rules = {}
25+
26+
for action, rule in rules.items():
27+
self.register(action, *rule)
28+
29+
def rule(self, func: RuleProto[Any]):
30+
rule_name = f"{func.__name__}"
31+
new_rule = Rule[T](rule_name, func)
32+
self.register(rule_name, new_rule)
33+
return func
34+
35+
def register(self, action: str, rule: Rule[T]):
36+
self._registry[action] = rule
37+
38+
async def inspect(
39+
self,
40+
action: str,
41+
actor: Any,
42+
*args: Any,
43+
**kwargs: Any,
44+
) -> Response:
45+
if action not in self._registry:
46+
return Err(f"Action <{action}> undefined for this policy")
47+
return await self._registry[action].inspect(actor, *args, **kwargs)
48+
49+
async def allows(
50+
self,
51+
action: str,
52+
actor: Any,
53+
*args: Any,
54+
**kwargs: Any,
55+
) -> bool:
56+
return (await self.inspect(action, actor, *args, **kwargs)).allowed()
57+
58+
async def denies(
59+
self,
60+
action: str,
61+
actor: Any,
62+
*args: Any,
63+
**kwargs: Any,
64+
) -> bool:
65+
return not (await self.inspect(action, actor, *args, **kwargs)).allowed()
66+
67+
async def authorize(
68+
self,
69+
action: str,
70+
actor: Any,
71+
*args: Any,
72+
**kwargs: Any,
73+
) -> bool:
74+
res = await self.inspect(action, actor, *args, **kwargs)
75+
if not res.allowed():
76+
raise exceptions.AuthorizationException(res.message())
77+
return True
78+
79+
async def grants(
80+
self,
81+
actor: Any,
82+
*args: Any,
83+
**kwargs: Any,
84+
) -> dict[str, bool]:
85+
return {
86+
action: await self.allows(
87+
action,
88+
actor,
89+
*args,
90+
**kwargs,
91+
)
92+
for action in self._registry
93+
}

0 commit comments

Comments
 (0)