Skip to content

Commit 153d1cb

Browse files
author
m-bigaignon
committed
wip
1 parent f8d750a commit 153d1cb

File tree

6 files changed

+74
-171
lines changed

6 files changed

+74
-171
lines changed

entitled/client.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
from typing import Any, Literal
22

33
from entitled.exceptions import AuthorizationException
4+
from entitled.policies import Policy
45
from entitled.response import Err, Response
56
from entitled.rules import Actor, Rule, RuleProto
67

78

89
class Client:
910
def __init__(self):
11+
self._policy_registry: dict[type, type[Policy[Any]]] = {}
1012
self._rule_registry: dict[str, Rule[Any]] = {}
1113

1214
def define_rule(self, name: str, callable: RuleProto[Actor]) -> Rule[Actor]:
1315
rule = Rule(name, callable)
1416
self._rule_registry[rule.name] = rule
1517
return rule
1618

19+
def _register_policy(self, policy: type[Policy[Any]]):
20+
resource_type = getattr(policy, "__orig_bases__")[0].__args__[0]
21+
self._policy_registry[resource_type] = policy
22+
23+
def _resolve_rule(self, name: str, resource: Any) -> Rule[Any] | None:
24+
lookup_key = resource if isinstance(resource, type) else type(resource)
25+
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)
31+
1732
async def inspect(
1833
self,
1934
name: str,
2035
actor: Actor,
36+
resource: Any,
2137
*args: Any,
2238
**kwargs: Any,
2339
) -> Response:
24-
if name in self._rule_registry:
25-
return await self._rule_registry[name].inspect(actor, *args, **kwargs)
40+
rule = self._resolve_rule(name, resource)
41+
if rule is not None:
42+
return await rule.inspect(actor, resource, *args, **kwargs)
2643
return Err(f"No rule found with name '{name}'")
2744

2845
async def allows(

entitled/policies.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
from typing import Any
2-
from entitled.rules import RuleProto
1+
import functools
2+
from typing import Any, Callable
33

44

5-
class Policy[T]:
6-
def rule(self):
7-
def wrapped(func: RuleProto[Any]):
8-
func._is_rule = True
9-
return func
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)
9+
10+
setattr(wrapper, "_is_rule", True)
11+
return wrapper
1012

11-
return wrapped
13+
14+
class Policy[T]:
15+
pass

tests/test_policies.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from entitled.client import Client
4+
from entitled.policies import Policy, rule
5+
from entitled.response import Err, Ok, Response
6+
from tests.data.models import Tenant, User
7+
8+
pytestmark = pytest.mark.anyio
9+
10+
client = Client()
11+
12+
13+
class TenantPolicy(Policy[Tenant]):
14+
@rule
15+
async def is_member(
16+
self,
17+
actor: User,
18+
resource: Tenant,
19+
) -> bool:
20+
return actor.tenant == resource
21+
22+
@rule
23+
async def is_owner(
24+
self,
25+
actor: User,
26+
resource: Tenant,
27+
) -> Response:
28+
return Ok() if resource.owner == actor else Err("Not owner on the tenant")
29+
30+
31+
async def test_register():
32+
client._register_policy(TenantPolicy)
33+
assert Tenant in client._policy_registry
34+
35+
36+
async def test_inspect():
37+
tenant1 = Tenant("tenant1")
38+
tenant2 = Tenant("tenant2")
39+
user1 = User("user1", tenant1, set())
40+
user2 = User("user2", None, set())
41+
tenant2.owner = user2
42+
43+
assert await client.inspect("is_member", user1, tenant1)
File renamed without changes.

tests/vtest_client.py

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

tests/vtest_policies.py

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

0 commit comments

Comments
 (0)