policy-pattern 0.3.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/PKG-INFO +8 -11
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/README.md +7 -10
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/evaluator.py +37 -9
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/pyproject.toml +2 -2
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/pyproject.toml.orig +2 -2
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/LICENSE.md +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/__init__.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/__init__.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/_db_protocols.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/_tuple_mapping.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/memory.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/postgres.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/__init__.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/base_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/evaluation_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/identifier_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/index_entry_too_large_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/model_validation_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/mutation_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/relation_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/unknown_name_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/unknown_type_error.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/__init__.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/_schema_validation.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/compiled_model.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/direct_subject.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/identifier.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/model.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/mutation.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/object_reference.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/operators.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/reference.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/relation.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/subject.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/subject_set.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/tuple.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/tuple_to_userset.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/type.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/ports/__init__.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/ports/tuple_store.py +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/py.typed +0 -0
- {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/sql/postgres/0001_create_policy_tuples.sql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: policy-pattern
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar.
|
|
5
5
|
Keywords: python,authorization,access-control,rebac,zanzibar,security
|
|
6
6
|
Author: Guillem Pozo Sebastián
|
|
@@ -45,8 +45,7 @@ Description-Content-Type: text/markdown
|
|
|
45
45
|
</a>
|
|
46
46
|
</p>
|
|
47
47
|
|
|
48
|
-
`policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models
|
|
49
|
-
authorization as relationships between subjects and objects and evaluates those relationships through composable
|
|
48
|
+
`policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models authorization as relationships between subjects and objects and evaluates those relationships through composable
|
|
50
49
|
authorization rules.
|
|
51
50
|
|
|
52
51
|
- **No authorization server**
|
|
@@ -131,18 +130,15 @@ evaluator = Evaluator(model=compiled, store=store)
|
|
|
131
130
|
evaluator.check(alice, "viewer", document_42) # True — alice owns the parent folder
|
|
132
131
|
```
|
|
133
132
|
|
|
134
|
-
`viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under
|
|
135
|
-
`folder:engineering` inherits access from that folder without copying a permission onto every document.
|
|
133
|
+
`viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under `folder:engineering` inherits access from that folder without copying a permission onto every document.
|
|
136
134
|
|
|
137
|
-
See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested
|
|
138
|
-
groups, `EvaluationBudget`, and error handling.
|
|
135
|
+
See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested groups, `EvaluationBudget`, and error handling.
|
|
139
136
|
|
|
140
137
|
---
|
|
141
138
|
|
|
142
139
|
## Documentation
|
|
143
140
|
|
|
144
|
-
The repository uses specifications and architecture documents as part of the implementation contract — they
|
|
145
|
-
describe **what must be true**, independent of any one adapter's implementation.
|
|
141
|
+
The repository uses specifications and architecture documents as part of the implementation contract — they describe **what must be true**, independent of any one adapter's implementation.
|
|
146
142
|
|
|
147
143
|
```text
|
|
148
144
|
docs/
|
|
@@ -153,8 +149,9 @@ docs/
|
|
|
153
149
|
└── errors.md — the PPxxx error code ranges and what each one means
|
|
154
150
|
```
|
|
155
151
|
|
|
156
|
-
Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then
|
|
157
|
-
|
|
152
|
+
Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
|
|
153
|
+
|
|
154
|
+
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
|
|
158
155
|
|
|
159
156
|
---
|
|
160
157
|
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
</a>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
-
`policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models
|
|
24
|
-
authorization as relationships between subjects and objects and evaluates those relationships through composable
|
|
23
|
+
`policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models authorization as relationships between subjects and objects and evaluates those relationships through composable
|
|
25
24
|
authorization rules.
|
|
26
25
|
|
|
27
26
|
- **No authorization server**
|
|
@@ -106,18 +105,15 @@ evaluator = Evaluator(model=compiled, store=store)
|
|
|
106
105
|
evaluator.check(alice, "viewer", document_42) # True — alice owns the parent folder
|
|
107
106
|
```
|
|
108
107
|
|
|
109
|
-
`viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under
|
|
110
|
-
`folder:engineering` inherits access from that folder without copying a permission onto every document.
|
|
108
|
+
`viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under `folder:engineering` inherits access from that folder without copying a permission onto every document.
|
|
111
109
|
|
|
112
|
-
See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested
|
|
113
|
-
groups, `EvaluationBudget`, and error handling.
|
|
110
|
+
See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested groups, `EvaluationBudget`, and error handling.
|
|
114
111
|
|
|
115
112
|
---
|
|
116
113
|
|
|
117
114
|
## Documentation
|
|
118
115
|
|
|
119
|
-
The repository uses specifications and architecture documents as part of the implementation contract — they
|
|
120
|
-
describe **what must be true**, independent of any one adapter's implementation.
|
|
116
|
+
The repository uses specifications and architecture documents as part of the implementation contract — they describe **what must be true**, independent of any one adapter's implementation.
|
|
121
117
|
|
|
122
118
|
```text
|
|
123
119
|
docs/
|
|
@@ -128,8 +124,9 @@ docs/
|
|
|
128
124
|
└── errors.md — the PPxxx error code ranges and what each one means
|
|
129
125
|
```
|
|
130
126
|
|
|
131
|
-
Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then
|
|
132
|
-
|
|
127
|
+
Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
|
|
128
|
+
|
|
129
|
+
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
|
|
133
130
|
|
|
134
131
|
---
|
|
135
132
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Evaluator: resolves check() over a CompiledModel and a TupleStore.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from collections.abc import Sequence
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
|
|
7
8
|
from .errors import EvaluationError, UnknownNameError, UnknownTypeError
|
|
@@ -24,7 +25,11 @@ from .ports import TupleStore, View
|
|
|
24
25
|
@dataclass(frozen=True, slots=True)
|
|
25
26
|
class EvaluationBudget:
|
|
26
27
|
"""
|
|
27
|
-
|
|
28
|
+
Resource limits for one `check()` or `check_many()` call.
|
|
29
|
+
|
|
30
|
+
`max_nodes` and `max_store_reads` accumulate across a `check_many()` call.
|
|
31
|
+
`max_depth` bounds the live `context.visited` frontier and resets as each
|
|
32
|
+
request finishes, so it does not accumulate across the batch.
|
|
28
33
|
|
|
29
34
|
No `__post_init__` validation — a degenerate value (e.g. `max_depth=0`)
|
|
30
35
|
is well-defined without a guard: it simply fails on the first node
|
|
@@ -35,16 +40,16 @@ class EvaluationBudget:
|
|
|
35
40
|
EvaluationBudget(max_depth=10, max_nodes=500, max_store_reads=100)
|
|
36
41
|
"""
|
|
37
42
|
|
|
38
|
-
# Maximum recursion depth for
|
|
43
|
+
# Maximum recursion depth for one call. Each recursive call
|
|
39
44
|
# to `_check_name()` adds one to the depth; exceeding this limit raises an
|
|
40
45
|
# `EvaluationError`.
|
|
41
46
|
max_depth: int = 25
|
|
42
47
|
|
|
43
|
-
# Maximum number of nodes visited during
|
|
48
|
+
# Maximum number of nodes visited during one call. Each
|
|
44
49
|
# call to `_check_name()` increments this count; exceeding this limit raises an `EvaluationError`.
|
|
45
50
|
max_nodes: int = 10_000
|
|
46
51
|
|
|
47
|
-
# Maximum number of store reads during
|
|
52
|
+
# Maximum number of store reads during one call. Each call
|
|
48
53
|
# to `_charge_read()` increments this count; exceeding this limit raises an `EvaluationError`.
|
|
49
54
|
max_store_reads: int = 1_000
|
|
50
55
|
|
|
@@ -52,7 +57,7 @@ class EvaluationBudget:
|
|
|
52
57
|
@dataclass(slots=True)
|
|
53
58
|
class EvaluationContext:
|
|
54
59
|
"""
|
|
55
|
-
Live bookkeeping for exactly one `check()` call.
|
|
60
|
+
Live bookkeeping for exactly one `check()` or `check_many()` call.
|
|
56
61
|
|
|
57
62
|
Deliberately mutable, unlike almost everything else in this package —
|
|
58
63
|
this is not a domain value, it is the in-flight state of one
|
|
@@ -63,13 +68,19 @@ class EvaluationContext:
|
|
|
63
68
|
(added before recursing, discarded after returning) — anything
|
|
64
69
|
permanently resolved lives in `memo` instead, so the two never overlap
|
|
65
70
|
in meaning.
|
|
71
|
+
|
|
72
|
+
The key carries the subject because one `check()` has a constant subject,
|
|
73
|
+
while `check_many()` can mix subjects; a narrower key could reuse one
|
|
74
|
+
subject's cached decision for another and cause a false allow.
|
|
66
75
|
"""
|
|
67
76
|
|
|
68
77
|
view: View
|
|
69
78
|
budget: EvaluationBudget
|
|
70
|
-
visited: set[tuple[ObjectReference, str]] = field(
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
visited: set[tuple[Subject, ObjectReference, str]] = field(
|
|
80
|
+
default_factory=lambda: set[tuple[Subject, ObjectReference, str]]()
|
|
81
|
+
)
|
|
82
|
+
memo: dict[tuple[Subject, ObjectReference, str], bool] = field(
|
|
83
|
+
default_factory=lambda: dict[tuple[Subject, ObjectReference, str], bool]()
|
|
73
84
|
)
|
|
74
85
|
node_count: int = 0
|
|
75
86
|
store_read_count: int = 0
|
|
@@ -120,11 +131,28 @@ class Evaluator:
|
|
|
120
131
|
context = EvaluationContext(view=view, budget=budget)
|
|
121
132
|
return self._check_name(subject, name, object, context)
|
|
122
133
|
|
|
134
|
+
def check_many(
|
|
135
|
+
self,
|
|
136
|
+
requests: Sequence[tuple[Subject, str, ObjectReference]],
|
|
137
|
+
budget: EvaluationBudget | None = None,
|
|
138
|
+
) -> list[bool]:
|
|
139
|
+
"""
|
|
140
|
+
Returns one decision per request, preserving input order and duplicates.
|
|
141
|
+
|
|
142
|
+
The whole batch shares one snapshot, context, and budget. An error on
|
|
143
|
+
any request aborts the call and propagates without partial results.
|
|
144
|
+
"""
|
|
145
|
+
if budget is None:
|
|
146
|
+
budget = EvaluationBudget()
|
|
147
|
+
with self._store.snapshot() as view:
|
|
148
|
+
context = EvaluationContext(view=view, budget=budget)
|
|
149
|
+
return [self._check_name(subject, name, object, context) for subject, name, object in requests]
|
|
150
|
+
|
|
123
151
|
def _check_name(self, subject: Subject, name: str, object: ObjectReference, context: EvaluationContext) -> bool:
|
|
124
152
|
"""
|
|
125
153
|
Dispatches (object, name) to a relation or permission check, with cycle/memo/budget bookkeeping.
|
|
126
154
|
"""
|
|
127
|
-
key = (object, name)
|
|
155
|
+
key = (subject, object, name)
|
|
128
156
|
if key in context.memo:
|
|
129
157
|
return context.memo[key]
|
|
130
158
|
if key in context.visited:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "policy-pattern"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -38,7 +38,7 @@ Repository = "https://github.com/guillem-ps/policy-pattern"
|
|
|
38
38
|
Issues = "https://github.com/guillem-ps/policy-pattern/issues"
|
|
39
39
|
|
|
40
40
|
[build-system]
|
|
41
|
-
requires = ["uv_build>=0.11.16,<0.
|
|
41
|
+
requires = ["uv_build>=0.11.16,<0.14.0"]
|
|
42
42
|
build-backend = "uv_build"
|
|
43
43
|
|
|
44
44
|
[tool.uv.build-backend]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "policy-pattern"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -37,7 +37,7 @@ Repository = "https://github.com/guillem-ps/policy-pattern"
|
|
|
37
37
|
Issues = "https://github.com/guillem-ps/policy-pattern/issues"
|
|
38
38
|
|
|
39
39
|
[build-system]
|
|
40
|
-
requires = ["uv_build>=0.11.16,<0.
|
|
40
|
+
requires = ["uv_build>=0.11.16,<0.14.0"]
|
|
41
41
|
build-backend = "uv_build"
|
|
42
42
|
|
|
43
43
|
[tool.uv.build-backend]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/index_entry_too_large_error.py
RENAMED
|
File without changes
|
{policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/model_validation_error.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|