@jantstack/adonis-authz 2.5.0-alpha.3 → 2.5.0-rc.1
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.
- package/README.md +437 -48
- package/build/commands/authz_catalog_diff.d.ts +0 -1
- package/build/commands/authz_catalog_diff.js +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts +17 -3
- package/build/commands/authz_catalog_prune_orphans.js +28 -6
- package/build/commands/authz_catalog_sync.d.ts +0 -1
- package/build/commands/authz_catalog_sync.js +0 -1
- package/build/commands/authz_freeze.d.ts +0 -1
- package/build/commands/authz_freeze.js +0 -1
- package/build/commands/authz_reconcile.d.ts +0 -1
- package/build/commands/authz_reconcile.js +0 -1
- package/build/commands/authz_relations_reconcile.d.ts +0 -1
- package/build/commands/authz_relations_reconcile.js +56 -8
- package/build/commands/authz_scopes_relay.d.ts +0 -1
- package/build/commands/authz_scopes_relay.js +54 -6
- package/build/commands/authz_scopes_requeue.d.ts +0 -1
- package/build/commands/authz_scopes_requeue.js +16 -3
- package/build/commands/authz_unfreeze.d.ts +0 -1
- package/build/commands/authz_unfreeze.js +0 -1
- package/build/commands/main.d.ts +0 -1
- package/build/commands/main.js +0 -1
- package/build/commands/openfga_provision.d.ts +10 -1
- package/build/commands/openfga_provision.js +43 -3
- package/build/configure.d.ts +0 -1
- package/build/configure.js +0 -1
- package/build/index.d.ts +9 -7
- package/build/index.js +10 -3
- package/build/providers/authz_provider.d.ts +0 -1
- package/build/providers/authz_provider.js +0 -1
- package/build/services/main.d.ts +0 -1
- package/build/services/main.js +0 -1
- package/build/services/relations.d.ts +0 -1
- package/build/services/relations.js +0 -1
- package/build/src/catalog/catalog.d.ts +0 -1
- package/build/src/catalog/catalog.js +0 -1
- package/build/src/catalog/catalog_cache.d.ts +13 -1
- package/build/src/catalog/catalog_cache.js +27 -2
- package/build/src/clock.d.ts +0 -1
- package/build/src/clock.js +0 -1
- package/build/src/define_config.d.ts +0 -1
- package/build/src/define_config.js +24 -1
- package/build/src/drivers/database_driver.d.ts +60 -3
- package/build/src/drivers/database_driver.js +160 -25
- package/build/src/drivers/database_relations_driver.d.ts +11 -5
- package/build/src/drivers/database_relations_driver.js +115 -33
- package/build/src/drivers/openfga_driver.d.ts +159 -26
- package/build/src/drivers/openfga_driver.js +882 -199
- package/build/src/drivers/openfga_facts.d.ts +91 -1
- package/build/src/drivers/openfga_facts.js +277 -3
- package/build/src/drivers/openfga_relations_driver.d.ts +41 -13
- package/build/src/drivers/openfga_relations_driver.js +422 -228
- package/build/src/errors.d.ts +160 -1
- package/build/src/errors.js +161 -1
- package/build/src/expiry.d.ts +51 -2
- package/build/src/expiry.js +96 -2
- package/build/src/freeze.d.ts +0 -1
- package/build/src/freeze.js +0 -1
- package/build/src/hierarchical_resolver.d.ts +0 -1
- package/build/src/hierarchical_resolver.js +0 -1
- package/build/src/http/app_access_middleware.d.ts +0 -1
- package/build/src/http/app_access_middleware.js +0 -1
- package/build/src/http/resource_access_middleware.d.ts +0 -1
- package/build/src/http/resource_access_middleware.js +0 -1
- package/build/src/identity.d.ts +9 -2
- package/build/src/identity.js +33 -1
- package/build/src/manager.d.ts +24 -1
- package/build/src/manager.js +381 -85
- package/build/src/memoize_ancestors.d.ts +0 -1
- package/build/src/memoize_ancestors.js +0 -1
- package/build/src/models/authz_assignment.d.ts +5 -6
- package/build/src/models/authz_assignment.js +0 -1
- package/build/src/models/authz_deny.d.ts +5 -6
- package/build/src/models/authz_deny.js +0 -1
- package/build/src/models/authz_permission.d.ts +5 -6
- package/build/src/models/authz_permission.js +0 -1
- package/build/src/models/authz_role.d.ts +5 -6
- package/build/src/models/authz_role.js +0 -1
- package/build/src/models/authz_role_permission.d.ts +5 -6
- package/build/src/models/authz_role_permission.js +0 -1
- package/build/src/openfga.d.ts +2 -3
- package/build/src/openfga.js +4 -2
- package/build/src/reconcile.d.ts +0 -1
- package/build/src/reconcile.js +0 -1
- package/build/src/relation_partition_trigger.d.ts +0 -1
- package/build/src/relation_partition_trigger.js +0 -1
- package/build/src/relations/check_many.d.ts +0 -1
- package/build/src/relations/check_many.js +0 -1
- package/build/src/relations/define_relations_config.d.ts +25 -3
- package/build/src/relations/define_relations_config.js +8 -3
- package/build/src/relations/deny_reach.d.ts +102 -0
- package/build/src/relations/deny_reach.js +230 -0
- package/build/src/relations/group_depth.d.ts +215 -0
- package/build/src/relations/group_depth.js +453 -0
- package/build/src/relations/manager.d.ts +14 -7
- package/build/src/relations/manager.js +60 -15
- package/build/src/relations/page.d.ts +19 -0
- package/build/src/relations/page.js +74 -0
- package/build/src/relations/reconcile.d.ts +58 -4
- package/build/src/relations/reconcile.js +266 -12
- package/build/src/relations/tuple_key.d.ts +0 -1
- package/build/src/relations/tuple_key.js +0 -1
- package/build/src/relations/tuple_order.d.ts +5 -4
- package/build/src/relations/tuple_order.js +5 -4
- package/build/src/relations_config_store.d.ts +57 -2
- package/build/src/relations_config_store.js +155 -5
- package/build/src/relations_reconcile.d.ts +0 -1
- package/build/src/relations_reconcile.js +0 -1
- package/build/src/scope_outbox.d.ts +36 -3
- package/build/src/scope_outbox.js +78 -18
- package/build/src/shared/backend_guard.d.ts +52 -1
- package/build/src/shared/backend_guard.js +164 -2
- package/build/src/shared/sql_expiry.d.ts +0 -1
- package/build/src/shared/sql_expiry.js +0 -1
- package/build/src/shared/transaction_guard.d.ts +0 -1
- package/build/src/shared/transaction_guard.js +0 -1
- package/build/src/sql_descendants.d.ts +0 -1
- package/build/src/sql_descendants.js +0 -1
- package/build/src/testing/contract.d.ts +26 -3
- package/build/src/testing/contract.js +258 -20
- package/build/src/testing/main.d.ts +0 -1
- package/build/src/testing/main.js +0 -1
- package/build/src/testing/migration_contract.d.ts +0 -1
- package/build/src/testing/migration_contract.js +0 -1
- package/build/src/testing/relations_contract.d.ts +7 -1
- package/build/src/testing/relations_contract.js +174 -7
- package/build/src/testing/relations_reconcile_contract.d.ts +0 -1
- package/build/src/testing/relations_reconcile_contract.js +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts +0 -1
- package/build/src/testing/scope_outbox_contract.js +0 -1
- package/build/src/testing/scope_tree.d.ts +0 -1
- package/build/src/testing/scope_tree.js +0 -1
- package/build/src/testing/test_clock.d.ts +0 -1
- package/build/src/testing/test_clock.js +0 -1
- package/build/src/traits/authz_scopes.d.ts +0 -1
- package/build/src/traits/authz_scopes.js +0 -1
- package/build/src/traits/has_uuid.d.ts +5 -6
- package/build/src/traits/has_uuid.js +0 -1
- package/build/src/types.d.ts +138 -14
- package/build/src/types.js +6 -1
- package/build/stubs/config/authorization.stub +22 -6
- package/build/stubs/main.d.ts +0 -1
- package/build/stubs/main.js +0 -1
- package/package.json +4 -3
- package/build/commands/authz_catalog_diff.d.ts.map +0 -1
- package/build/commands/authz_catalog_diff.js.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
- package/build/commands/authz_catalog_sync.d.ts.map +0 -1
- package/build/commands/authz_catalog_sync.js.map +0 -1
- package/build/commands/authz_freeze.d.ts.map +0 -1
- package/build/commands/authz_freeze.js.map +0 -1
- package/build/commands/authz_reconcile.d.ts.map +0 -1
- package/build/commands/authz_reconcile.js.map +0 -1
- package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
- package/build/commands/authz_relations_reconcile.js.map +0 -1
- package/build/commands/authz_scopes_relay.d.ts.map +0 -1
- package/build/commands/authz_scopes_relay.js.map +0 -1
- package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
- package/build/commands/authz_scopes_requeue.js.map +0 -1
- package/build/commands/authz_unfreeze.d.ts.map +0 -1
- package/build/commands/authz_unfreeze.js.map +0 -1
- package/build/commands/main.d.ts.map +0 -1
- package/build/commands/main.js.map +0 -1
- package/build/commands/openfga_provision.d.ts.map +0 -1
- package/build/commands/openfga_provision.js.map +0 -1
- package/build/configure.d.ts.map +0 -1
- package/build/configure.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/providers/authz_provider.d.ts.map +0 -1
- package/build/providers/authz_provider.js.map +0 -1
- package/build/services/main.d.ts.map +0 -1
- package/build/services/main.js.map +0 -1
- package/build/services/relations.d.ts.map +0 -1
- package/build/services/relations.js.map +0 -1
- package/build/src/catalog/catalog.d.ts.map +0 -1
- package/build/src/catalog/catalog.js.map +0 -1
- package/build/src/catalog/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog/catalog_cache.js.map +0 -1
- package/build/src/clock.d.ts.map +0 -1
- package/build/src/clock.js.map +0 -1
- package/build/src/define_config.d.ts.map +0 -1
- package/build/src/define_config.js.map +0 -1
- package/build/src/drivers/database_driver.d.ts.map +0 -1
- package/build/src/drivers/database_driver.js.map +0 -1
- package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/database_relations_driver.js.map +0 -1
- package/build/src/drivers/openfga_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_driver.js.map +0 -1
- package/build/src/drivers/openfga_facts.d.ts.map +0 -1
- package/build/src/drivers/openfga_facts.js.map +0 -1
- package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_relations_driver.js.map +0 -1
- package/build/src/errors.d.ts.map +0 -1
- package/build/src/errors.js.map +0 -1
- package/build/src/expiry.d.ts.map +0 -1
- package/build/src/expiry.js.map +0 -1
- package/build/src/freeze.d.ts.map +0 -1
- package/build/src/freeze.js.map +0 -1
- package/build/src/hierarchical_resolver.d.ts.map +0 -1
- package/build/src/hierarchical_resolver.js.map +0 -1
- package/build/src/http/app_access_middleware.d.ts.map +0 -1
- package/build/src/http/app_access_middleware.js.map +0 -1
- package/build/src/http/resource_access_middleware.d.ts.map +0 -1
- package/build/src/http/resource_access_middleware.js.map +0 -1
- package/build/src/identity.d.ts.map +0 -1
- package/build/src/identity.js.map +0 -1
- package/build/src/manager.d.ts.map +0 -1
- package/build/src/manager.js.map +0 -1
- package/build/src/memoize_ancestors.d.ts.map +0 -1
- package/build/src/memoize_ancestors.js.map +0 -1
- package/build/src/models/authz_assignment.d.ts.map +0 -1
- package/build/src/models/authz_assignment.js.map +0 -1
- package/build/src/models/authz_deny.d.ts.map +0 -1
- package/build/src/models/authz_deny.js.map +0 -1
- package/build/src/models/authz_permission.d.ts.map +0 -1
- package/build/src/models/authz_permission.js.map +0 -1
- package/build/src/models/authz_role.d.ts.map +0 -1
- package/build/src/models/authz_role.js.map +0 -1
- package/build/src/models/authz_role_permission.d.ts.map +0 -1
- package/build/src/models/authz_role_permission.js.map +0 -1
- package/build/src/openfga.d.ts.map +0 -1
- package/build/src/openfga.js.map +0 -1
- package/build/src/reconcile.d.ts.map +0 -1
- package/build/src/reconcile.js.map +0 -1
- package/build/src/relation_partition_trigger.d.ts.map +0 -1
- package/build/src/relation_partition_trigger.js.map +0 -1
- package/build/src/relations/check_many.d.ts.map +0 -1
- package/build/src/relations/check_many.js.map +0 -1
- package/build/src/relations/define_relations_config.d.ts.map +0 -1
- package/build/src/relations/define_relations_config.js.map +0 -1
- package/build/src/relations/manager.d.ts.map +0 -1
- package/build/src/relations/manager.js.map +0 -1
- package/build/src/relations/reconcile.d.ts.map +0 -1
- package/build/src/relations/reconcile.js.map +0 -1
- package/build/src/relations/tuple_key.d.ts.map +0 -1
- package/build/src/relations/tuple_key.js.map +0 -1
- package/build/src/relations/tuple_order.d.ts.map +0 -1
- package/build/src/relations/tuple_order.js.map +0 -1
- package/build/src/relations_config_store.d.ts.map +0 -1
- package/build/src/relations_config_store.js.map +0 -1
- package/build/src/relations_reconcile.d.ts.map +0 -1
- package/build/src/relations_reconcile.js.map +0 -1
- package/build/src/scope_outbox.d.ts.map +0 -1
- package/build/src/scope_outbox.js.map +0 -1
- package/build/src/shared/backend_guard.d.ts.map +0 -1
- package/build/src/shared/backend_guard.js.map +0 -1
- package/build/src/shared/sql_expiry.d.ts.map +0 -1
- package/build/src/shared/sql_expiry.js.map +0 -1
- package/build/src/shared/transaction_guard.d.ts.map +0 -1
- package/build/src/shared/transaction_guard.js.map +0 -1
- package/build/src/sql_descendants.d.ts.map +0 -1
- package/build/src/sql_descendants.js.map +0 -1
- package/build/src/testing/contract.d.ts.map +0 -1
- package/build/src/testing/contract.js.map +0 -1
- package/build/src/testing/main.d.ts.map +0 -1
- package/build/src/testing/main.js.map +0 -1
- package/build/src/testing/migration_contract.d.ts.map +0 -1
- package/build/src/testing/migration_contract.js.map +0 -1
- package/build/src/testing/relations_contract.d.ts.map +0 -1
- package/build/src/testing/relations_contract.js.map +0 -1
- package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
- package/build/src/testing/relations_reconcile_contract.js.map +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
- package/build/src/testing/scope_outbox_contract.js.map +0 -1
- package/build/src/testing/scope_tree.d.ts.map +0 -1
- package/build/src/testing/scope_tree.js.map +0 -1
- package/build/src/testing/test_clock.d.ts.map +0 -1
- package/build/src/testing/test_clock.js.map +0 -1
- package/build/src/traits/authz_scopes.d.ts.map +0 -1
- package/build/src/traits/authz_scopes.js.map +0 -1
- package/build/src/traits/has_uuid.d.ts.map +0 -1
- package/build/src/traits/has_uuid.js.map +0 -1
- package/build/src/types.d.ts.map +0 -1
- package/build/src/types.js.map +0 -1
- package/build/stubs/main.d.ts.map +0 -1
- package/build/stubs/main.js.map +0 -1
|
@@ -22,6 +22,13 @@ export declare const RELATIONS_CONTRACT_OBJECT_TYPES: readonly ["document", "wor
|
|
|
22
22
|
* editor ⊆ viewer` (includes de un nivel) y `membersOf` habilitado en `database`.
|
|
23
23
|
*/
|
|
24
24
|
export declare function contractRelationsConfig(): RelationsConfig;
|
|
25
|
+
/**
|
|
26
|
+
* **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
|
|
27
|
+
* tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
|
|
28
|
+
* `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
|
|
29
|
+
* `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
|
|
30
|
+
*/
|
|
31
|
+
export declare function contractGroupDepthRelationsConfig(maxGroupDepth?: number): RelationsConfig;
|
|
25
32
|
/**
|
|
26
33
|
* **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
|
|
27
34
|
* un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
|
|
@@ -102,4 +109,3 @@ export declare function registerRelationsDriverContract(harness: RelationsDriver
|
|
|
102
109
|
registered: number;
|
|
103
110
|
covered: Set<keyof RelationsDriverCapabilities>;
|
|
104
111
|
};
|
|
105
|
-
//# sourceMappingURL=relations_contract.d.ts.map
|
|
@@ -25,6 +25,7 @@ import { RenewalUnsafeError } from '../errors.js';
|
|
|
25
25
|
import { RelationsManager } from '../relations/manager.js';
|
|
26
26
|
import { defineRelationsConfig, assertRelationDeclared, assertObjectTypeDeclared, assertUsersetSubject, assertRelationDenyable, assertDenySubject, assertDenyNoExpiry, cutPathAllows, decideFrom, deniedRelationOf, denyPhaseOf, storedRelationKind, relationsDown, RELATION_DENY_PREFIX, } from '../relations/define_relations_config.js';
|
|
27
27
|
import { assertCheckManyObjects, distinctObjects, expandByPosition, RELATIONS_CHECK_MANY_MAX } from '../relations/check_many.js';
|
|
28
|
+
import { assertEnumeratePage, assertNoListPage } from '../relations/page.js';
|
|
28
29
|
import { systemClock } from '../clock.js';
|
|
29
30
|
import { resolveGrantExpiry, sameInstant } from '../expiry.js';
|
|
30
31
|
import { isRelUserset } from '../types.js';
|
|
@@ -59,6 +60,23 @@ export function contractRelationsConfig() {
|
|
|
59
60
|
database: { membersOf: true },
|
|
60
61
|
});
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
|
|
65
|
+
* tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
|
|
66
|
+
* `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
|
|
67
|
+
* `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
|
|
68
|
+
*/
|
|
69
|
+
export function contractGroupDepthRelationsConfig(maxGroupDepth = 3) {
|
|
70
|
+
return defineRelationsConfig({
|
|
71
|
+
objectTypes: RELATIONS_CONTRACT_OBJECT_TYPES.map((type) => ({
|
|
72
|
+
type,
|
|
73
|
+
relations: [{ name: 'owner' }, { name: 'editor', includes: ['owner'] }, { name: 'viewer', includes: ['editor'] }],
|
|
74
|
+
})),
|
|
75
|
+
holderTypes: [...RELATIONS_HOLDER_TYPES],
|
|
76
|
+
database: { membersOf: true, enforceGroupDepth: true },
|
|
77
|
+
maxGroupDepth,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
62
80
|
/**
|
|
63
81
|
* **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
|
|
64
82
|
* un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
|
|
@@ -308,10 +326,12 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
308
326
|
assertSubjectDeclared(subject);
|
|
309
327
|
return resolves(subject, relation, object, scopeKeyOf(partition));
|
|
310
328
|
},
|
|
311
|
-
async listObjects(subject, relation, objectType, partition
|
|
329
|
+
async listObjects(subject, relation, objectType, partition) {
|
|
312
330
|
assertRelationDeclared(config, { type: objectType }, relation);
|
|
313
331
|
assertSubjectDeclared(subject);
|
|
314
332
|
const partitionKey = scopeKeyOf(partition);
|
|
333
|
+
// 7.4: el doble tampoco pagina (un quinto argumento ≠ undefined ⇒ 500), después de F-05 y la partición.
|
|
334
|
+
assertNoListPage('listObjects', arguments[4], 'double');
|
|
315
335
|
const ids = new Set();
|
|
316
336
|
for (const t of tuples) {
|
|
317
337
|
if (t.partition === partitionKey && t.objectType === objectType && active(t))
|
|
@@ -332,9 +352,10 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
332
352
|
}
|
|
333
353
|
return { objects: matches };
|
|
334
354
|
},
|
|
335
|
-
async listSubjects(relation, object, partition
|
|
355
|
+
async listSubjects(relation, object, partition) {
|
|
336
356
|
assertRelationDeclared(config, object, relation);
|
|
337
357
|
const partitionKey = scopeKeyOf(partition);
|
|
358
|
+
assertNoListPage('listSubjects', arguments[3], 'double');
|
|
338
359
|
// DIRECTOS del relation EXACTO (invariante 7): ni transitivo (eso es
|
|
339
360
|
// `membersOf`) ni derivado. `usersetSubjects` decide si los usersets
|
|
340
361
|
// (`group:g#member`) salen o solo los holders.
|
|
@@ -422,9 +443,11 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
422
443
|
assertRelationDeclared(config, object, relation);
|
|
423
444
|
return denyPhaseOf(config, object.type) ?? null;
|
|
424
445
|
};
|
|
425
|
-
driver.listDenies = async (relation, object, partition)
|
|
446
|
+
driver.listDenies = async function (relation, object, partition) {
|
|
426
447
|
assertRelationDeclared(config, object, relation);
|
|
427
|
-
const
|
|
448
|
+
const key = scopeKeyOf(partition);
|
|
449
|
+
assertNoListPage('listDenies', arguments[3], 'double');
|
|
450
|
+
const direct = directSubjects(key, object.type, object.id, deniedRelationOf(relation));
|
|
428
451
|
return { subjects: dedupeSubjects(direct.map((t) => t.subject)) };
|
|
429
452
|
};
|
|
430
453
|
driver.decideMany = async (subject, relation, objects, partition) => {
|
|
@@ -444,16 +467,19 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
444
467
|
};
|
|
445
468
|
}
|
|
446
469
|
if (capabilities.membersOfNative) {
|
|
447
|
-
driver.membersOf = async (object, relation, partition)
|
|
470
|
+
driver.membersOf = async function (object, relation, partition) {
|
|
448
471
|
assertRelationDeclared(config, object, relation);
|
|
449
472
|
// Lote D: `membersOf` RESTA (corte de camino por candidato, como `database`).
|
|
450
473
|
const key = scopeKeyOf(partition);
|
|
474
|
+
assertNoListPage('membersOf', arguments[3], 'double');
|
|
451
475
|
return { subjects: collectMembers(object, relation, key).filter((s) => resolves(s, relation, object, key)) };
|
|
452
476
|
};
|
|
453
477
|
}
|
|
454
478
|
if (capabilities.enumerateRelations) {
|
|
455
|
-
driver.enumerateRelations = async (partition) => {
|
|
479
|
+
driver.enumerateRelations = async (partition, page) => {
|
|
456
480
|
const partitionKey = scopeKeyOf(partition);
|
|
481
|
+
// 7.4: la misma forma que los drivers reales; el doble no emite cursor (todo en una página) ⇒ after es 500.
|
|
482
|
+
assertEnumeratePage('enumerateRelations', page, 'none', 'double');
|
|
457
483
|
// SIN filtrar la caducada (R-15): llega con su `expiresAt` para que el
|
|
458
484
|
// destino de reconcile la cuente en `skipped`, no para que desaparezca.
|
|
459
485
|
const out = tuples
|
|
@@ -703,6 +729,46 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
703
729
|
await state.manager.relate({ object: g2, relation: 'member' }, 'viewer', doc, p);
|
|
704
730
|
assert.isTrue(await state.manager.check(u, 'viewer', doc, p));
|
|
705
731
|
});
|
|
732
|
+
/* ── R1 (lote «antes de RC»): el techo de anidación de grupos, si el driver lo declara ── */
|
|
733
|
+
test('R1 · groupDepthLimit (si el driver lo declara): con maxGroupDepth 3 + database.enforceGroupDepth el techo es ≤ 3 y se APLICA — la arista que deja una cadena más larga, por ARRIBA o por ABAJO, o que cierra un ciclo ⇒ 422 E_AUTHZ_GROUP_DEPTH_LIMIT sin escribir (check sigue F) y sin evento por el manager; la membresía de un holder y una relación de dominio no son anidación', async ({ assert, }) => {
|
|
734
|
+
const config = contractGroupDepthRelationsConfig(3);
|
|
735
|
+
const driver = await harness.makeDriver(config);
|
|
736
|
+
// `groupDepthLimit` es OPCIONAL en el puerto: un driver sin él no declara techo (el plan del reconcile no tiene
|
|
737
|
+
// nada que negar) y no hay gate que juzgar. Lo mismo un driver que responde `null` (sin techo con esta config).
|
|
738
|
+
const limit = typeof driver.groupDepthLimit === 'function' ? await driver.groupDepthLimit() : null;
|
|
739
|
+
if (limit === null)
|
|
740
|
+
return;
|
|
741
|
+
assert.isTrue(Number.isInteger(limit) && limit >= 1 && limit <= 3, `el techo con maxGroupDepth 3 tiene que estar en 1..3 (llegó ${String(limit)})`);
|
|
742
|
+
const events = [];
|
|
743
|
+
const manager = new RelationsManager(driver, config, { driverName: harness.name, onRelationWrite: (e) => void events.push(e) });
|
|
744
|
+
const p = partition();
|
|
745
|
+
const g = Array.from({ length: limit + 2 }, () => ({ type: 'group', id: docId() }));
|
|
746
|
+
const member = (o) => ({ object: o, relation: 'member' });
|
|
747
|
+
for (let i = 0; i < limit; i++)
|
|
748
|
+
await manager.relate(member(g[i]), 'member', g[i + 1], p);
|
|
749
|
+
const u = holder();
|
|
750
|
+
await manager.relate(u, 'member', g[0], p);
|
|
751
|
+
assert.isTrue(await manager.check(u, 'member', g[limit], p), `una cadena de ${limit} saltos (el techo) entra y resuelve`);
|
|
752
|
+
const before = events.length;
|
|
753
|
+
const refused = async (label, run) => {
|
|
754
|
+
const error = await run().then(() => null, (e) => e);
|
|
755
|
+
assert.equal(error?.status, 422, `${label}: ${error?.message ?? 'entró'}`);
|
|
756
|
+
assert.equal(error?.code, 'E_AUTHZ_GROUP_DEPTH_LIMIT', label);
|
|
757
|
+
return error;
|
|
758
|
+
};
|
|
759
|
+
await refused('por ARRIBA (la cima de la cadena dentro de otro grupo)', () => manager.relate(member(g[limit]), 'member', g[limit + 1], p));
|
|
760
|
+
assert.isFalse(await manager.check(u, 'member', g[limit + 1], p), 'la arista rechazada no se escribió');
|
|
761
|
+
const x = { type: 'group', id: docId() };
|
|
762
|
+
await refused('por ABAJO (otro grupo dentro de la base de la cadena)', () => manager.relate(member(x), 'member', g[0], p));
|
|
763
|
+
const v = holder();
|
|
764
|
+
await manager.relate(v, 'member', x, p);
|
|
765
|
+
assert.isFalse(await manager.check(v, 'member', g[0], p), 'x ⊂ g0 no se escribió');
|
|
766
|
+
await refused('el CICLO', () => manager.relate(member(g[limit]), 'member', g[0], p));
|
|
767
|
+
assert.equal(events.length, before + 1, '422 ⇒ esa escritura no ocurrió y no hay evento (invariante 13): solo el del holder v');
|
|
768
|
+
// No es anidación: la membresía de un holder y una relación de dominio sobre la cima.
|
|
769
|
+
await manager.relate(holder(), 'member', g[limit], p);
|
|
770
|
+
await manager.relate(member(g[limit]), 'viewer', { type: 'document', id: docId() }, p);
|
|
771
|
+
});
|
|
706
772
|
/* ── R-05: la partición aísla ── */
|
|
707
773
|
test('R-05 · partición: relate en A no concede en B', async ({ assert }) => {
|
|
708
774
|
const u = holder();
|
|
@@ -750,6 +816,93 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
750
816
|
const page = await state.manager.listObjects(u, 'viewer', 'document', p);
|
|
751
817
|
assert.sameMembers(page.objects.map((o) => o.id).sort(), [d1.id, d2.id].sort());
|
|
752
818
|
});
|
|
819
|
+
/* ── 7.4 (lote «antes de RC», opción 4 del juez): los listados NO paginan ── */
|
|
820
|
+
/**
|
|
821
|
+
* **P13 · «sin página ⇒ lista completa y sin cursor»** (injerto 1 del juez; auditor R1-2, tester P13). N = 150:
|
|
822
|
+
* por encima del default de 100 de `enumerate*` de `database` y del `page_size` 100 de `Read` en OpenFGA, así que
|
|
823
|
+
* un driver que pagina por dentro y devuelve la PRIMERA página (o un cursor que nadie lee) es rojo aquí. El tipo ya
|
|
824
|
+
* no tiene `cursor` (protege a quien compila); este caso protege en EJECUCIÓN frente a un driver de terceros y a un
|
|
825
|
+
* default de `limit` que alguien meta en 2.6. `listObjects` puede venir `truncated` SOLO si el driver declara
|
|
826
|
+
* `listObjectsTruncation` (y entonces lo que trae ⊆ lo escrito); los demás, el conjunto exacto.
|
|
827
|
+
*/
|
|
828
|
+
test('7.4 · P13 · sin page los listados son EXHAUSTIVOS y sin cursor: N=150 (> 100 de enumerate y del page_size de Read) en listSubjects y listObjects —y listDenies con relationDenies, membersOf con membersOfNative—; listObjects truncated solo con listObjectsTruncation', async ({ assert, }) => {
|
|
829
|
+
const N = 150;
|
|
830
|
+
const p = partition();
|
|
831
|
+
const users = Array.from({ length: N }, () => holder());
|
|
832
|
+
const ids = (subjects) => subjects.map((s) => s.uuid).sort();
|
|
833
|
+
const expected = users.map((u) => u.uuid).sort();
|
|
834
|
+
const doc = { type: 'document', id: docId() };
|
|
835
|
+
for (const u of users)
|
|
836
|
+
await state.manager.relate(u, 'viewer', doc, p);
|
|
837
|
+
const subjects = await state.manager.listSubjects('viewer', doc, p);
|
|
838
|
+
assert.notProperty(subjects, 'cursor', 'listSubjects sin page no devuelve cursor');
|
|
839
|
+
assert.notEqual(subjects.truncated, true, 'listSubjects no corta (Read se agota o lanza)');
|
|
840
|
+
assert.deepEqual(ids(subjects.subjects), expected, `listSubjects devuelve los ${N}, no una página`);
|
|
841
|
+
const reader = holder();
|
|
842
|
+
const docs = Array.from({ length: N }, () => docId());
|
|
843
|
+
for (const id of docs)
|
|
844
|
+
await state.manager.relate(reader, 'viewer', { type: 'document', id }, p);
|
|
845
|
+
const objects = await state.manager.listObjects(reader, 'viewer', 'document', p);
|
|
846
|
+
assert.notProperty(objects, 'cursor', 'listObjects sin page no devuelve cursor');
|
|
847
|
+
const got = objects.objects.map((o) => o.id).sort();
|
|
848
|
+
if (objects.truncated === true) {
|
|
849
|
+
assert.isTrue(harness.capabilities.listObjectsTruncation, 'truncated sin declarar listObjectsTruncation');
|
|
850
|
+
assert.includeMembers(docs, got, 'lo que trae una lista truncada es de lo escrito');
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
assert.deepEqual(got, [...docs].sort(), `listObjects devuelve los ${N}, no una página`);
|
|
854
|
+
}
|
|
855
|
+
if (harness.capabilities.membersOfNative) {
|
|
856
|
+
const group = { type: 'group', id: docId() };
|
|
857
|
+
for (const u of users)
|
|
858
|
+
await state.manager.relate(u, 'member', group, p);
|
|
859
|
+
const members = await state.manager.membersOf(group, 'member', p);
|
|
860
|
+
assert.notProperty(members, 'cursor', 'membersOf sin page no devuelve cursor');
|
|
861
|
+
assert.deepEqual(ids(members.subjects), expected, `membersOf devuelve los ${N}`);
|
|
862
|
+
}
|
|
863
|
+
if (harness.capabilities.relationDenies) {
|
|
864
|
+
const { manager } = await denyManager();
|
|
865
|
+
const denied = { type: 'document', id: docId() };
|
|
866
|
+
for (const u of users)
|
|
867
|
+
await manager.deny(u, 'viewer', denied, p);
|
|
868
|
+
const denies = await manager.listDenies('viewer', denied, p);
|
|
869
|
+
assert.notProperty(denies, 'cursor', 'listDenies sin page no devuelve cursor');
|
|
870
|
+
assert.deepEqual(ids(denies.subjects), expected, `listDenies devuelve los ${N}`);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
/**
|
|
874
|
+
* **RC74-1 · página en los cuatro listados ⇒ 500, cero llamadas** (capa MANAGER, sobre el driver del harness). En
|
|
875
|
+
* 2.6 este caso pasa a ser la cara `whenFalse` de cada capacidad de paginación. `undefined` EXPLÍCITO entra
|
|
876
|
+
* (RC74-3). No se exige al driver en directo: obligaría a un tercero a un 500 por un parámetro que ya no está en
|
|
877
|
+
* el tipo (lo cubren la guardia y los specs de los drivers del paquete).
|
|
878
|
+
*/
|
|
879
|
+
test('7.4 · RC74-1 · listObjects/listSubjects/listDenies/membersOf con page ({ limit: 1 }, null, {}) ⇒ 500 E_AUTHZ_UNSUPPORTED nombrando la operación, con CERO llamadas al driver; undefined explícito entra (RC74-3)', async ({ assert, }) => {
|
|
880
|
+
const u = holder();
|
|
881
|
+
const doc = { type: 'document', id: docId() };
|
|
882
|
+
const group = { type: 'group', id: docId() };
|
|
883
|
+
const p = partition();
|
|
884
|
+
const m = state.manager;
|
|
885
|
+
const calls = [
|
|
886
|
+
['listObjects', (page) => m.listObjects(u, 'viewer', 'document', p, page)],
|
|
887
|
+
['listSubjects', (page) => m.listSubjects('viewer', doc, p, page)],
|
|
888
|
+
['listDenies', (page) => m.listDenies('viewer', doc, p, page)],
|
|
889
|
+
['membersOf', (page) => m.membersOf(group, 'member', p, page)],
|
|
890
|
+
];
|
|
891
|
+
for (const [operation, call] of calls) {
|
|
892
|
+
for (const page of [{ limit: 1 }, null, {}]) {
|
|
893
|
+
const before = state.calls();
|
|
894
|
+
const caught = await call(page).then(() => null, (e) => e);
|
|
895
|
+
assert.isNotNull(caught, `${operation}(…, ${JSON.stringify(page)}) entró: una página que no hace nada es una lista entera que parece una página`);
|
|
896
|
+
assert.equal(caught.status, 500, `${operation}: ${caught.message}`);
|
|
897
|
+
assert.equal(caught.code, 'E_AUTHZ_UNSUPPORTED', `${operation}: ${caught.message}`);
|
|
898
|
+
assert.include(caught.message, operation);
|
|
899
|
+
assert.equal(state.calls(), before, `${operation} con page: CERO llamadas al driver`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// RC74-3: el argumento `undefined` explícito es «sin página» (quien reenvía un opcional vacío no se rompe).
|
|
903
|
+
assert.isArray((await m.listObjects(u, 'viewer', 'document', p, undefined)).objects);
|
|
904
|
+
assert.isArray((await m.listSubjects('viewer', doc, p, undefined)).subjects);
|
|
905
|
+
});
|
|
753
906
|
/* ── R-12: identidad mal formada ⇒ 422 ── */
|
|
754
907
|
test('R-12 · partición mal formada ⇒ 422 con code', async ({ assert }) => {
|
|
755
908
|
const u = holder();
|
|
@@ -1783,6 +1936,21 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
1783
1936
|
for (const r of denies)
|
|
1784
1937
|
await m.deny(u, r, object, p);
|
|
1785
1938
|
}
|
|
1939
|
+
/* ── Estrategia 2.5.0 · firma 4 · el par `experimental` (regla D15; mismo nombre que en roles) ── */
|
|
1940
|
+
// Dice lo que el consumidor tiene que saber («fuera de la garantía de la versión») y no apaga casos: el runner corre
|
|
1941
|
+
// ENTERO contra un driver experimental. Se observa la declaración del driver y la copia normalizada del manager.
|
|
1942
|
+
caseFor('experimental', {
|
|
1943
|
+
whenTrue: () => test('experimental:true · el driver se declara EXPERIMENTAL (capabilities.experimental === true) y `relations.capabilities()` lo publica tal cual; el runner corre igual contra él', async ({ assert }) => {
|
|
1944
|
+
assert.strictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: true y el driver no: declara lo observable');
|
|
1945
|
+
const published = state.manager.capabilities();
|
|
1946
|
+
assert.strictEqual(published.experimental, true);
|
|
1947
|
+
assert.isTrue(Object.isFrozen(published));
|
|
1948
|
+
}),
|
|
1949
|
+
whenFalse: () => test('experimental:false · el driver NO se declara experimental (ausente o false) y `relations.capabilities().experimental` es false, nunca undefined (D15)', async ({ assert }) => {
|
|
1950
|
+
assert.notStrictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: false y el driver declara true: declara lo observable');
|
|
1951
|
+
assert.strictEqual(state.manager.capabilities().experimental, false);
|
|
1952
|
+
}),
|
|
1953
|
+
});
|
|
1786
1954
|
/* ── Cierre 2 de D2 · 🟠 N1 · el par `atomicRenewal` (renovar una membresía que TRANSPORTA un deny) ── */
|
|
1787
1955
|
/**
|
|
1788
1956
|
* Siembra: U ∈ g con caducidad (+1 h), un allow DIRECTO de U sobre X y —si el driver deniega— el deny de `g#member`
|
|
@@ -2361,4 +2529,3 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
2361
2529
|
});
|
|
2362
2530
|
return { registered, covered };
|
|
2363
2531
|
}
|
|
2364
|
-
//# sourceMappingURL=relations_contract.js.map
|
|
@@ -12,4 +12,3 @@ export interface TestClock {
|
|
|
12
12
|
}
|
|
13
13
|
/** Un reloj de test parado en `start` (default: la hora de pared al crearlo), controlable con `set`/`advance`. */
|
|
14
14
|
export declare function createTestClock(start?: Date | number | string): TestClock;
|
|
15
|
-
//# sourceMappingURL=test_clock.d.ts.map
|
|
@@ -42,4 +42,3 @@ export type WithAuthzScopes<T extends Constructor> = T & AuthzScopesStatics;
|
|
|
42
42
|
*/
|
|
43
43
|
export declare function withAuthzScopes<T extends Constructor | AuthzScopesOptions>(arg: T): T extends Constructor ? WithAuthzScopes<T> : <U extends Constructor>(Base: U) => WithAuthzScopes<U>;
|
|
44
44
|
export {};
|
|
45
|
-
//# sourceMappingURL=authz_scopes.d.ts.map
|
|
@@ -81,6 +81,11 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
81
81
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_1>>, "paginate">): void;
|
|
82
82
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event>): void;
|
|
83
83
|
};
|
|
84
|
+
before: {
|
|
85
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
|
|
86
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
|
|
87
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
88
|
+
};
|
|
84
89
|
readonly booted: boolean;
|
|
85
90
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
86
91
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
@@ -117,11 +122,6 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
117
122
|
};
|
|
118
123
|
$defineProperty: <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Prop extends keyof Model_1>(this: Model_1, propertyName: Prop, defaultValue: Model_1[Prop], strategy: "inherit" | "define" | ((value: Model_1[Prop]) => Model_1[Prop])) => void;
|
|
119
124
|
boot: () => void;
|
|
120
|
-
before: {
|
|
121
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
|
|
122
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
|
|
123
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
124
|
-
};
|
|
125
125
|
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
126
126
|
createQuietly: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
127
127
|
createMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
@@ -148,4 +148,3 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
148
148
|
fetchOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
149
149
|
updateOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
150
150
|
} & Model;
|
|
151
|
-
//# sourceMappingURL=has_uuid.d.ts.map
|