policy-pattern 0.4.1__tar.gz → 0.6.0__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.4.1 → policy_pattern-0.6.0}/PKG-INFO +2 -2
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/README.md +1 -1
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/adapters/memory.py +25 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/adapters/postgres.py +51 -10
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/evaluator.py +152 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/ports/tuple_store.py +21 -0
- policy_pattern-0.6.0/policy_pattern/sql/postgres/0002_add_subject_index.sql +5 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/pyproject.toml +1 -1
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/pyproject.toml.orig +1 -1
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/LICENSE.md +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/__init__.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/adapters/__init__.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/adapters/_db_protocols.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/adapters/_tuple_mapping.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/__init__.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/base_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/evaluation_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/identifier_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/index_entry_too_large_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/model_validation_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/mutation_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/relation_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/unknown_name_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/unknown_type_error.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/__init__.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/_schema_validation.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/compiled_model.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/direct_subject.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/identifier.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/model.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/mutation.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/object_reference.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/operators.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/reference.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/relation.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/subject.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/subject_set.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/tuple.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/tuple_to_userset.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/models/type.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/ports/__init__.py +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/policy_pattern/py.typed +0 -0
- {policy_pattern-0.4.1 → policy_pattern-0.6.0}/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.6.0
|
|
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
|
|
@@ -151,7 +151,7 @@ docs/
|
|
|
151
151
|
|
|
152
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
153
|
|
|
154
|
-
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
|
|
154
|
+
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](.github/TESTING.md) for contributor conventions no tool enforces.
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
@@ -126,7 +126,7 @@ docs/
|
|
|
126
126
|
|
|
127
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
128
|
|
|
129
|
-
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
|
|
129
|
+
See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](.github/TESTING.md) for contributor conventions no tool enforces.
|
|
130
130
|
|
|
131
131
|
---
|
|
132
132
|
|
|
@@ -133,3 +133,28 @@ class _MemoryView(View):
|
|
|
133
133
|
return ReadResult(tuples=frozenset(candidates[:limit]), complete=False)
|
|
134
134
|
|
|
135
135
|
return ReadResult(tuples=frozenset(candidates), complete=True)
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
def read_tuples_for_subject(self, subject: Subject, limit: int) -> ReadResult:
|
|
139
|
+
"""Linearly scans the snapshot for tuples with this exact subject.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
subject (Subject): The exact subject to match.
|
|
143
|
+
limit (int): The maximum number of matching tuples to return.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
ReadResult: Matching tuples and whether the enumeration is complete.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
None: This method defines no exception of its own.
|
|
150
|
+
"""
|
|
151
|
+
candidates: list[Tuple] = nsmallest(
|
|
152
|
+
limit + 1,
|
|
153
|
+
(t for t in self.tuples if t.subject == subject),
|
|
154
|
+
key=repr,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if len(candidates) > limit:
|
|
158
|
+
return ReadResult(tuples=frozenset(candidates[:limit]), complete=False)
|
|
159
|
+
|
|
160
|
+
return ReadResult(tuples=frozenset(candidates), complete=True)
|
|
@@ -8,13 +8,16 @@ from importlib.resources import files
|
|
|
8
8
|
from typing import Any, override
|
|
9
9
|
|
|
10
10
|
from policy_pattern.errors import IndexEntryTooLargeError
|
|
11
|
-
from policy_pattern.models import Mutation, ObjectReference, Subject
|
|
11
|
+
from policy_pattern.models import Mutation, ObjectReference, Subject, Tuple
|
|
12
12
|
from policy_pattern.ports import ReadResult, TupleStore, View
|
|
13
13
|
|
|
14
14
|
from ._db_protocols import Connection, ConnectionPool, Cursor
|
|
15
15
|
from ._tuple_mapping import columns, compose, decompose
|
|
16
16
|
|
|
17
|
-
MIGRATION_RESOURCE: tuple[str, str, str] = (
|
|
17
|
+
MIGRATION_RESOURCE: tuple[tuple[str, str, str], ...] = (
|
|
18
|
+
("sql", "postgres", "0001_create_policy_tuples.sql"),
|
|
19
|
+
("sql", "postgres", "0002_add_subject_index.sql"),
|
|
20
|
+
)
|
|
18
21
|
|
|
19
22
|
# PostgreSQL's B-tree index entry limit for the default 8KB page size
|
|
20
23
|
# (BLCKSZ). Identifier bounds a value to 255 code points, any printable
|
|
@@ -68,6 +71,12 @@ _READ_RELATION_SQL: str = """
|
|
|
68
71
|
ORDER BY subject_type, subject_id, subject_relation LIMIT %s
|
|
69
72
|
"""
|
|
70
73
|
|
|
74
|
+
_READ_TUPLES_FOR_SUBJECT_SQL: str = """
|
|
75
|
+
SELECT object_type, object_id, relation FROM "authz_tuples"
|
|
76
|
+
WHERE subject_type=%s AND subject_id=%s AND subject_relation=%s
|
|
77
|
+
ORDER BY object_type, object_id, relation LIMIT %s
|
|
78
|
+
"""
|
|
79
|
+
|
|
71
80
|
|
|
72
81
|
class PostgresTupleStore(TupleStore):
|
|
73
82
|
"""
|
|
@@ -114,16 +123,17 @@ class PostgresTupleStore(TupleStore):
|
|
|
114
123
|
failed execute() would otherwise return an aborted transaction to
|
|
115
124
|
the pool for the next caller to trip over.
|
|
116
125
|
"""
|
|
117
|
-
migration_sql = files("policy_pattern").joinpath(*MIGRATION_RESOURCE).read_text(encoding="utf-8")
|
|
118
126
|
with self._pool.connection() as conn:
|
|
119
127
|
cursor = conn.cursor()
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
for resource in MIGRATION_RESOURCE:
|
|
129
|
+
migration_sql = files("policy_pattern").joinpath(*resource).read_text(encoding="utf-8")
|
|
130
|
+
try:
|
|
131
|
+
cursor.execute(migration_sql)
|
|
132
|
+
except Exception:
|
|
133
|
+
conn.rollback()
|
|
134
|
+
raise
|
|
135
|
+
else:
|
|
136
|
+
conn.commit()
|
|
127
137
|
|
|
128
138
|
@override
|
|
129
139
|
def write_tuples(self, mutation: Mutation) -> None:
|
|
@@ -255,3 +265,34 @@ class _PostgresView(View):
|
|
|
255
265
|
tuples = frozenset(compose(r, relation, object) for r in rows[:limit])
|
|
256
266
|
|
|
257
267
|
return ReadResult(tuples=tuples, complete=len(rows) <= limit)
|
|
268
|
+
|
|
269
|
+
@override
|
|
270
|
+
def read_tuples_for_subject(self, subject: Subject, limit: int) -> ReadResult:
|
|
271
|
+
"""Returns up to `limit` tuples whose subject exactly matches, in one query.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
subject (Subject): The exact subject to match.
|
|
275
|
+
limit (int): The maximum number of matching tuples to return.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
ReadResult: Matching tuples and whether the enumeration is complete.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
None: This method defines no exception of its own.
|
|
282
|
+
"""
|
|
283
|
+
subject_type, subject_id, subject_relation = decompose(subject)
|
|
284
|
+
cursor: Cursor = self.connection.cursor()
|
|
285
|
+
cursor.execute(
|
|
286
|
+
_READ_TUPLES_FOR_SUBJECT_SQL,
|
|
287
|
+
(subject_type, subject_id, subject_relation, limit + 1),
|
|
288
|
+
)
|
|
289
|
+
rows: list[tuple[Any, ...]] = cursor.fetchall()
|
|
290
|
+
tuples = frozenset(
|
|
291
|
+
Tuple(
|
|
292
|
+
subject=subject,
|
|
293
|
+
relation=row[2],
|
|
294
|
+
object=ObjectReference(type=row[0], id=row[1]),
|
|
295
|
+
)
|
|
296
|
+
for row in rows[:limit]
|
|
297
|
+
)
|
|
298
|
+
return ReadResult(tuples=tuples, complete=len(rows) <= limit)
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Evaluator: resolves check() over a CompiledModel and a TupleStore.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from collections import deque
|
|
5
6
|
from collections.abc import Sequence
|
|
6
7
|
from dataclasses import dataclass, field
|
|
8
|
+
from functools import cached_property
|
|
7
9
|
|
|
8
10
|
from .errors import EvaluationError, UnknownNameError, UnknownTypeError
|
|
9
11
|
from .models import (
|
|
@@ -148,6 +150,156 @@ class Evaluator:
|
|
|
148
150
|
context = EvaluationContext(view=view, budget=budget)
|
|
149
151
|
return [self._check_name(subject, name, object, context) for subject, name, object in requests]
|
|
150
152
|
|
|
153
|
+
def list_objects(
|
|
154
|
+
self,
|
|
155
|
+
subject: Subject,
|
|
156
|
+
name: str,
|
|
157
|
+
object_type: str,
|
|
158
|
+
candidate_ids: frozenset[ObjectReference] | None = None,
|
|
159
|
+
budget: EvaluationBudget | None = None,
|
|
160
|
+
) -> frozenset[ObjectReference]:
|
|
161
|
+
"""Returns objects of `object_type` on which `subject` holds `name`.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
subject (Subject): The subject whose access is queried.
|
|
165
|
+
name (str): The relation or permission name to check.
|
|
166
|
+
object_type (str): The type of objects to return.
|
|
167
|
+
candidate_ids (frozenset[ObjectReference] | None): Optional objects to check instead of discovering them.
|
|
168
|
+
budget (EvaluationBudget | None): Resource limits for this evaluation.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
frozenset[ObjectReference]: Objects of `object_type` allowed by the forward check.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
UnknownTypeError: If `object_type` is not declared in the CompiledModel.
|
|
175
|
+
UnknownNameError: If `name` is not declared on `object_type`.
|
|
176
|
+
EvaluationError: If reverse candidate discovery or a candidate check exhausts the evaluation budget.
|
|
177
|
+
"""
|
|
178
|
+
if object_type not in self._model.types:
|
|
179
|
+
raise UnknownTypeError(
|
|
180
|
+
message=f"type {object_type!r} is not declared in this CompiledModel",
|
|
181
|
+
related=f"object_type={object_type!r}",
|
|
182
|
+
)
|
|
183
|
+
declaration = self._model.types[object_type]
|
|
184
|
+
if name not in declaration.relations and name not in declaration.permissions:
|
|
185
|
+
raise UnknownNameError(
|
|
186
|
+
message=f"{name!r} is not declared as a relation or permission on {object_type!r}",
|
|
187
|
+
related=f"object_type={object_type!r}",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if budget is None:
|
|
191
|
+
budget = EvaluationBudget()
|
|
192
|
+
|
|
193
|
+
with self._store.snapshot() as view:
|
|
194
|
+
context = EvaluationContext(view=view, budget=budget)
|
|
195
|
+
candidates = self._reverse_candidates(subject, context) if candidate_ids is None else candidate_ids
|
|
196
|
+
|
|
197
|
+
return frozenset(
|
|
198
|
+
object
|
|
199
|
+
for object in sorted(candidates, key=repr)
|
|
200
|
+
if object.type == object_type and self._check_name(subject, name, object, context)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _reverse_candidates(self, subject: Subject, context: EvaluationContext) -> frozenset[ObjectReference]:
|
|
204
|
+
"""Walk tuple subject edges to generate possible objects without interpreting permissions.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
subject (Subject): The starting subject for the reverse walk.
|
|
208
|
+
context (EvaluationContext): The shared view and resource-budget state.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
frozenset[ObjectReference]: All candidate objects reached by the tuple walk.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
EvaluationError: If `max_depth` or `max_nodes` is exceeded, or a subject-keyed enumeration is truncated.
|
|
215
|
+
"""
|
|
216
|
+
queue: deque[tuple[Subject, int]] = deque([(subject, 0)])
|
|
217
|
+
visited: set[Subject] = {subject}
|
|
218
|
+
candidates: set[ObjectReference] = set()
|
|
219
|
+
|
|
220
|
+
while queue:
|
|
221
|
+
current, depth = queue.popleft()
|
|
222
|
+
context.node_count += 1
|
|
223
|
+
if context.node_count > context.budget.max_nodes:
|
|
224
|
+
raise EvaluationError(
|
|
225
|
+
message=f"max_nodes={context.budget.max_nodes!r} exceeded during reverse lookup",
|
|
226
|
+
related=f"subject={current!r}",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
self._charge_read(context, current.object, "list_objects")
|
|
230
|
+
result = context.view.read_tuples_for_subject(
|
|
231
|
+
current,
|
|
232
|
+
limit=context.budget.max_nodes - context.node_count,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not result.complete:
|
|
236
|
+
raise EvaluationError(
|
|
237
|
+
message=(f"enumeration for subject {current!r} truncated by remaining max_nodes budget"),
|
|
238
|
+
related=f"subject={current!r}",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if result.tuples and depth >= context.budget.max_depth:
|
|
242
|
+
raise EvaluationError(
|
|
243
|
+
message=(f"max_depth={context.budget.max_depth!r} exceeded during reverse lookup from {current!r}"),
|
|
244
|
+
related=f"subject={current!r}",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
for tuple_ in sorted(result.tuples, key=repr):
|
|
248
|
+
candidates.add(tuple_.object)
|
|
249
|
+
for next_subject in self._reverse_frontier(tuple_.object):
|
|
250
|
+
if next_subject not in visited:
|
|
251
|
+
visited.add(next_subject)
|
|
252
|
+
queue.append((next_subject, depth + 1))
|
|
253
|
+
|
|
254
|
+
return frozenset(candidates)
|
|
255
|
+
|
|
256
|
+
@cached_property
|
|
257
|
+
def _referenceable_shapes(self) -> frozenset[str]:
|
|
258
|
+
"""Return the subject shapes referenced by any relation in the model.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
frozenset[str]: Subject type and type/relation shapes allowed by model relations.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
None.
|
|
265
|
+
"""
|
|
266
|
+
return frozenset(
|
|
267
|
+
allowed_shape
|
|
268
|
+
for type_declaration in self._model.types.values()
|
|
269
|
+
for relation in type_declaration.relations.values()
|
|
270
|
+
for allowed_shape in relation.allowed_subject_types
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def _reverse_frontier(self, object: ObjectReference) -> tuple[Subject, ...]:
|
|
274
|
+
"""Return the declared subject identities that may reference `object`.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
object (ObjectReference): The object whose possible subject identities are generated.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
tuple[Subject, ...]: Direct and userset identities that model relations may reference.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
None.
|
|
284
|
+
"""
|
|
285
|
+
next_subjects: list[Subject] = []
|
|
286
|
+
if object.type in self._referenceable_shapes:
|
|
287
|
+
next_subjects.append(DirectSubject(object=object))
|
|
288
|
+
|
|
289
|
+
declaration = self._model.types.get(object.type)
|
|
290
|
+
if declaration is None:
|
|
291
|
+
return tuple(next_subjects)
|
|
292
|
+
|
|
293
|
+
names = (*declaration.relations, *declaration.permissions)
|
|
294
|
+
next_subjects.extend(
|
|
295
|
+
[
|
|
296
|
+
SubjectSet(object=object, relation=name)
|
|
297
|
+
for name in names
|
|
298
|
+
if f"{object.type}#{name}" in self._referenceable_shapes
|
|
299
|
+
]
|
|
300
|
+
)
|
|
301
|
+
return tuple(next_subjects)
|
|
302
|
+
|
|
151
303
|
def _check_name(self, subject: Subject, name: str, object: ObjectReference, context: EvaluationContext) -> bool:
|
|
152
304
|
"""
|
|
153
305
|
Dispatches (object, name) to a relation or permission check, with cycle/memo/budget bookkeeping.
|
|
@@ -116,3 +116,24 @@ class View(ABC):
|
|
|
116
116
|
`limit + 1` matching tuples before returning — that would violate the contract.
|
|
117
117
|
"""
|
|
118
118
|
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def read_tuples_for_subject(self, subject: Subject, limit: int) -> ReadResult:
|
|
122
|
+
"""
|
|
123
|
+
Enumerates every tuple whose subject exactly matches `subject`, bounded by `limit`.
|
|
124
|
+
|
|
125
|
+
Unlike `read_relation`, this is not filtered by relation name or
|
|
126
|
+
object; callers needing narrower results filter returned tuples.
|
|
127
|
+
See `ReadResult` for the `complete=False` contract.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
subject (Subject): The exact subject to match.
|
|
131
|
+
limit (int): The maximum number of matching tuples to return.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
ReadResult: Matching tuples and whether the enumeration is complete.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
None: The abstract contract defines no exception of its own.
|
|
138
|
+
"""
|
|
139
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Index supporting View.read_tuples_for_subject(). Without it, a query
|
|
2
|
+
-- filtered only by the subject columns has no index to use — the
|
|
3
|
+
-- primary key is ordered object-first.
|
|
4
|
+
CREATE INDEX IF NOT EXISTS authz_tuples_subject_idx
|
|
5
|
+
ON "authz_tuples" (subject_type, subject_id, subject_relation);
|
|
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.4.1 → policy_pattern-0.6.0}/policy_pattern/errors/index_entry_too_large_error.py
RENAMED
|
File without changes
|
{policy_pattern-0.4.1 → policy_pattern-0.6.0}/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
|