@kya-os/contracts 1.8.0 → 1.9.0
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/dist/agentshield-api/index.js +0 -1
- package/dist/agentshield-api/schemas.d.ts +193 -187
- package/dist/agentshield-api/schemas.js +0 -1
- package/dist/agentshield-api/types.js +0 -1
- package/dist/compute.d.ts +47 -17
- package/dist/compute.js +23 -1
- package/dist/consent/index.d.ts +2 -3
- package/dist/consent/index.js +14 -3
- package/dist/handshake.d.ts +4 -3
- package/dist/handshake.js +4 -3
- package/dist/proof/index.d.ts +3 -4
- package/dist/proof/index.js +2 -2
- package/dist/proof.d.ts +98 -67
- package/dist/proof.js +29 -2
- package/dist/runtime/headers.d.ts +16 -12
- package/dist/runtime/headers.js +16 -12
- package/dist/verifier.d.ts +28 -17
- package/dist/verifier.js +20 -9
- package/package.json +2 -1
- package/parity-vectors/delegation-conformance.json +265 -0
- package/parity-vectors/did-key.json +134 -0
- package/parity-vectors/index.ts +253 -0
- package/parity-vectors/scope-matching.json +330 -0
- package/parity-vectors/vc-jwt.json +210 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Vector Loader
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions to load parity test vectors.
|
|
5
|
+
* These vectors ensure TypeScript and Rust implementations produce identical results.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
|
|
11
|
+
// Types for test vectors
|
|
12
|
+
export interface TestVector<TInput, TExpected> {
|
|
13
|
+
id: string;
|
|
14
|
+
description: string;
|
|
15
|
+
input: TInput;
|
|
16
|
+
expected: TExpected;
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TestVectorFile<TInput, TExpected> {
|
|
21
|
+
$schema?: string;
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
version: string;
|
|
25
|
+
vectors: TestVector<TInput, TExpected>[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// did:key vector types
|
|
29
|
+
export interface DidKeyInput {
|
|
30
|
+
did: string;
|
|
31
|
+
keyId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DidKeyExpected {
|
|
35
|
+
success: boolean;
|
|
36
|
+
keyType?: string;
|
|
37
|
+
publicKeyHex?: string;
|
|
38
|
+
keyId?: string;
|
|
39
|
+
keyIdValid?: boolean;
|
|
40
|
+
errorType?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Scope matching vector types
|
|
44
|
+
export interface ScopeMatchingInput {
|
|
45
|
+
requested: string;
|
|
46
|
+
granted: string[];
|
|
47
|
+
notBefore?: number | null;
|
|
48
|
+
notAfter?: number | null;
|
|
49
|
+
currentTime?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ScopeMatchingExpected {
|
|
53
|
+
permitted: boolean;
|
|
54
|
+
matchType?: "exact" | "wildcard" | "prefix" | "fullAccess" | "none";
|
|
55
|
+
matchedBy?: string | null;
|
|
56
|
+
error?: boolean;
|
|
57
|
+
errorType?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ScopeMatchingVectorFile
|
|
61
|
+
extends TestVectorFile<ScopeMatchingInput, ScopeMatchingExpected> {
|
|
62
|
+
timeBoundaryVectors?: TestVector<ScopeMatchingInput, ScopeMatchingExpected>[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// VC-JWT vector types
|
|
66
|
+
export interface VcJwtInput {
|
|
67
|
+
jwt: string;
|
|
68
|
+
publicKeyHex?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface VcJwtExpected {
|
|
72
|
+
success: boolean;
|
|
73
|
+
header?: {
|
|
74
|
+
alg: string;
|
|
75
|
+
typ?: string | null;
|
|
76
|
+
};
|
|
77
|
+
payload?: Record<string, unknown>;
|
|
78
|
+
issuer?: string;
|
|
79
|
+
verified?: boolean;
|
|
80
|
+
errorType?: string;
|
|
81
|
+
errorMessage?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface VcJwtVectorFile
|
|
85
|
+
extends TestVectorFile<VcJwtInput, VcJwtExpected> {
|
|
86
|
+
signatureVerificationVectors?: TestVector<VcJwtInput, VcJwtExpected>[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load test vectors from JSON file
|
|
91
|
+
*/
|
|
92
|
+
function loadVectors<T>(filename: string): T {
|
|
93
|
+
const filepath = join(__dirname, filename);
|
|
94
|
+
const content = readFileSync(filepath, "utf-8");
|
|
95
|
+
return JSON.parse(content) as T;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Load did:key resolution test vectors
|
|
100
|
+
*/
|
|
101
|
+
export function loadDidKeyVectors(): TestVectorFile<
|
|
102
|
+
DidKeyInput,
|
|
103
|
+
DidKeyExpected
|
|
104
|
+
> {
|
|
105
|
+
return loadVectors<TestVectorFile<DidKeyInput, DidKeyExpected>>(
|
|
106
|
+
"did-key.json"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Load scope matching test vectors
|
|
112
|
+
*/
|
|
113
|
+
export function loadScopeMatchingVectors(): ScopeMatchingVectorFile {
|
|
114
|
+
return loadVectors<ScopeMatchingVectorFile>("scope-matching.json");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Load VC-JWT verification test vectors
|
|
119
|
+
*/
|
|
120
|
+
export function loadVcJwtVectors(): VcJwtVectorFile {
|
|
121
|
+
return loadVectors<VcJwtVectorFile>("vc-jwt.json");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Delegation chain conformance vectors (E3.5 / issue #2904)
|
|
126
|
+
//
|
|
127
|
+
// These vectors codify the reference delegation semantics defined in E3.1
|
|
128
|
+
// (mcp-i-core: cascading-revocation, delegation-graph) plus the CRISP per-hop
|
|
129
|
+
// attenuation primitive (contracts: areChildConstraintsValid), so the TS cores
|
|
130
|
+
// and the reduced-profile Rust edge engine verify against one shared corpus.
|
|
131
|
+
//
|
|
132
|
+
// Each vector carries TWO expected outcomes:
|
|
133
|
+
// - `core`: the full reference semantics a complete verifier must produce.
|
|
134
|
+
// - `edge`: the reduced-profile kya-os-engine (WASM/WASI) verdict. The edge
|
|
135
|
+
// engine formally verifies single-hop delegation only; multi-hop chains
|
|
136
|
+
// fail closed (see issue #2904 "reduced edge profile").
|
|
137
|
+
// `profiles` lists which profiles must satisfy the vector directly; `category`
|
|
138
|
+
// selects which reference primitive owns the vector's `core` assertion.
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
/** Selects which reference primitive owns a vector's `core` assertion. */
|
|
142
|
+
export type DelegationCategory =
|
|
143
|
+
| "cascading-revocation"
|
|
144
|
+
| "chain-integrity"
|
|
145
|
+
| "per-hop-attenuation"
|
|
146
|
+
| "multi-hop-valid"
|
|
147
|
+
| "scope-match";
|
|
148
|
+
|
|
149
|
+
export type DelegationProfile = "core" | "edge";
|
|
150
|
+
|
|
151
|
+
/** Minimal CRISP constraint shape the attenuation primitive consumes. */
|
|
152
|
+
export interface DelegationConstraintsInput {
|
|
153
|
+
notBefore?: number;
|
|
154
|
+
notAfter?: number;
|
|
155
|
+
scopes?: string[];
|
|
156
|
+
crisp?: {
|
|
157
|
+
budget?: { unit: "USD" | "ops" | "points"; cap: number };
|
|
158
|
+
scopes: Array<{ resource: string; matcher: "exact" | "prefix" | "regex" }>;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** One hop in a delegation chain (root→leaf order). */
|
|
163
|
+
export interface DelegationHopInput {
|
|
164
|
+
/** Delegation id (unique within the chain). */
|
|
165
|
+
id: string;
|
|
166
|
+
/** Parent delegation id, or null for the root hop. */
|
|
167
|
+
parentId: string | null;
|
|
168
|
+
/** Issuer (delegator) DID. */
|
|
169
|
+
issuerDid: string;
|
|
170
|
+
/** Subject (delegatee) DID. */
|
|
171
|
+
subjectDid: string;
|
|
172
|
+
/** Flat granted scopes — what the reduced-profile engine's DelegationStep carries. */
|
|
173
|
+
grantedScopes: string[];
|
|
174
|
+
/** StatusList2021 pointer "<statusListUrl>#<index>" for revocation, if any. */
|
|
175
|
+
credentialStatusId?: string;
|
|
176
|
+
/** CRISP constraints — required for per-hop-attenuation vectors. */
|
|
177
|
+
constraints?: DelegationConstraintsInput;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface DelegationChainInput {
|
|
181
|
+
/** The delegation chain, root→leaf. length===1 ⇒ single-hop; >1 ⇒ multi-hop. */
|
|
182
|
+
chain: DelegationHopInput[];
|
|
183
|
+
/** Scope the leaf agent requests (the engine derives this from payload.aud). */
|
|
184
|
+
requestedScope: string;
|
|
185
|
+
/** credentialStatusIds that are currently revoked (drives the cascade check). */
|
|
186
|
+
revokedStatusIds?: string[];
|
|
187
|
+
/** Evaluation time (Unix seconds) for time-bounded checks. */
|
|
188
|
+
currentTime?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Full reference-semantics outcome (the TS cores). */
|
|
192
|
+
export interface DelegationCoreExpected {
|
|
193
|
+
valid: boolean;
|
|
194
|
+
failureReason?:
|
|
195
|
+
| "cascaded-revocation"
|
|
196
|
+
| "direct-revocation"
|
|
197
|
+
| "invalid-chain"
|
|
198
|
+
| "attenuation-violation";
|
|
199
|
+
/** For cascaded revocation: the revoked ancestor's id. */
|
|
200
|
+
revokedAncestor?: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Canonical engine Decision wire shape (kya-os-engine types.rs). */
|
|
204
|
+
export type EngineDecision =
|
|
205
|
+
| { kind: "Permit" }
|
|
206
|
+
| { kind: "Block"; reason: { kind: string; [k: string]: unknown } };
|
|
207
|
+
|
|
208
|
+
/** Reduced-profile edge verdict (kya-os-engine). */
|
|
209
|
+
export interface DelegationEdgeExpected {
|
|
210
|
+
/**
|
|
211
|
+
* 'evaluated' — single-hop / L1: the engine computes the real verdict.
|
|
212
|
+
* 'fail-closed' — multi-hop: the engine refuses (Block PolicyDenied) unless a
|
|
213
|
+
* higher-priority reason (Revoked/Expired) fires first.
|
|
214
|
+
*/
|
|
215
|
+
mode: "evaluated" | "fail-closed";
|
|
216
|
+
decision: EngineDecision;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface DelegationExpected {
|
|
220
|
+
/** Omitted for edge-only vectors (single-hop scope-match), where the engine is itself the reference. */
|
|
221
|
+
core?: DelegationCoreExpected;
|
|
222
|
+
edge: DelegationEdgeExpected;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface DelegationVector
|
|
226
|
+
extends TestVector<DelegationChainInput, DelegationExpected> {
|
|
227
|
+
category: DelegationCategory;
|
|
228
|
+
profiles: DelegationProfile[];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface DelegationVectorFile
|
|
232
|
+
extends TestVectorFile<DelegationChainInput, DelegationExpected> {
|
|
233
|
+
vectors: DelegationVector[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Load delegation chain conformance test vectors
|
|
238
|
+
*/
|
|
239
|
+
export function loadDelegationVectors(): DelegationVectorFile {
|
|
240
|
+
return loadVectors<DelegationVectorFile>("delegation-conformance.json");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get all test vectors as a combined object
|
|
245
|
+
*/
|
|
246
|
+
export function loadAllVectors() {
|
|
247
|
+
return {
|
|
248
|
+
didKey: loadDidKeyVectors(),
|
|
249
|
+
scopeMatching: loadScopeMatchingVectors(),
|
|
250
|
+
vcJwt: loadVcJwtVectors(),
|
|
251
|
+
delegation: loadDelegationVectors(),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "Scope matching test vectors",
|
|
4
|
+
"description": "Test vectors for delegation scope matching parity between TypeScript and Rust",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"id": "exact-match",
|
|
9
|
+
"description": "Exact scope match",
|
|
10
|
+
"input": {
|
|
11
|
+
"requested": "read:email",
|
|
12
|
+
"granted": ["read:email"]
|
|
13
|
+
},
|
|
14
|
+
"expected": {
|
|
15
|
+
"permitted": true,
|
|
16
|
+
"matchType": "exact",
|
|
17
|
+
"matchedBy": "read:email"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "exact-no-match",
|
|
22
|
+
"description": "Exact scope does not match",
|
|
23
|
+
"input": {
|
|
24
|
+
"requested": "write:email",
|
|
25
|
+
"granted": ["read:email"]
|
|
26
|
+
},
|
|
27
|
+
"expected": {
|
|
28
|
+
"permitted": false,
|
|
29
|
+
"matchType": "none",
|
|
30
|
+
"matchedBy": null
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "full-access",
|
|
35
|
+
"description": "Full access wildcard matches anything",
|
|
36
|
+
"input": {
|
|
37
|
+
"requested": "any:thing:at:all",
|
|
38
|
+
"granted": ["*"]
|
|
39
|
+
},
|
|
40
|
+
"expected": {
|
|
41
|
+
"permitted": true,
|
|
42
|
+
"matchType": "fullAccess",
|
|
43
|
+
"matchedBy": "*"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "wildcard-action",
|
|
48
|
+
"description": "Wildcard action matches any action in resource",
|
|
49
|
+
"input": {
|
|
50
|
+
"requested": "read:email",
|
|
51
|
+
"granted": ["read:*"]
|
|
52
|
+
},
|
|
53
|
+
"expected": {
|
|
54
|
+
"permitted": true,
|
|
55
|
+
"matchType": "wildcard",
|
|
56
|
+
"matchedBy": "read:*"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "wildcard-resource",
|
|
61
|
+
"description": "Wildcard resource matches any resource for action",
|
|
62
|
+
"input": {
|
|
63
|
+
"requested": "read:email",
|
|
64
|
+
"granted": ["*:email"]
|
|
65
|
+
},
|
|
66
|
+
"expected": {
|
|
67
|
+
"permitted": true,
|
|
68
|
+
"matchType": "wildcard",
|
|
69
|
+
"matchedBy": "*:email"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "wildcard-depth-mismatch",
|
|
74
|
+
"description": "Wildcard does not match different depth",
|
|
75
|
+
"input": {
|
|
76
|
+
"requested": "read:email:inbox",
|
|
77
|
+
"granted": ["read:*"]
|
|
78
|
+
},
|
|
79
|
+
"expected": {
|
|
80
|
+
"permitted": false,
|
|
81
|
+
"matchType": "none",
|
|
82
|
+
"matchedBy": null
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "prefix-match-three-segments",
|
|
87
|
+
"description": "Prefix match with trailing wildcard (3 segments)",
|
|
88
|
+
"input": {
|
|
89
|
+
"requested": "read:email:inbox",
|
|
90
|
+
"granted": ["read:email:*"]
|
|
91
|
+
},
|
|
92
|
+
"expected": {
|
|
93
|
+
"permitted": true,
|
|
94
|
+
"matchType": "prefix",
|
|
95
|
+
"matchedBy": "read:email:*"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "prefix-match-four-segments",
|
|
100
|
+
"description": "Prefix match with trailing wildcard (4 segments)",
|
|
101
|
+
"input": {
|
|
102
|
+
"requested": "read:email:inbox:unread",
|
|
103
|
+
"granted": ["read:email:inbox:*"]
|
|
104
|
+
},
|
|
105
|
+
"expected": {
|
|
106
|
+
"permitted": true,
|
|
107
|
+
"matchType": "prefix",
|
|
108
|
+
"matchedBy": "read:email:inbox:*"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "deep-prefix-one-level",
|
|
113
|
+
"description": "Deep prefix wildcard matches one level below",
|
|
114
|
+
"input": {
|
|
115
|
+
"requested": "read:email",
|
|
116
|
+
"granted": ["read:**"]
|
|
117
|
+
},
|
|
118
|
+
"expected": {
|
|
119
|
+
"permitted": true,
|
|
120
|
+
"matchType": "prefix",
|
|
121
|
+
"matchedBy": "read:**"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "deep-prefix-two-levels",
|
|
126
|
+
"description": "Deep prefix wildcard matches two levels below",
|
|
127
|
+
"input": {
|
|
128
|
+
"requested": "read:email:inbox",
|
|
129
|
+
"granted": ["read:**"]
|
|
130
|
+
},
|
|
131
|
+
"expected": {
|
|
132
|
+
"permitted": true,
|
|
133
|
+
"matchType": "prefix",
|
|
134
|
+
"matchedBy": "read:**"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "deep-prefix-many-levels",
|
|
139
|
+
"description": "Deep prefix wildcard matches many levels below",
|
|
140
|
+
"input": {
|
|
141
|
+
"requested": "read:email:inbox:unread:important",
|
|
142
|
+
"granted": ["read:**"]
|
|
143
|
+
},
|
|
144
|
+
"expected": {
|
|
145
|
+
"permitted": true,
|
|
146
|
+
"matchType": "prefix",
|
|
147
|
+
"matchedBy": "read:**"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "deep-prefix-no-self-match",
|
|
152
|
+
"description": "Deep prefix wildcard does NOT match same level (strictly below)",
|
|
153
|
+
"input": {
|
|
154
|
+
"requested": "read",
|
|
155
|
+
"granted": ["read:**"]
|
|
156
|
+
},
|
|
157
|
+
"expected": {
|
|
158
|
+
"permitted": false,
|
|
159
|
+
"matchType": "none",
|
|
160
|
+
"matchedBy": null
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "deep-prefix-wrong-prefix",
|
|
165
|
+
"description": "Deep prefix wildcard does not match wrong prefix",
|
|
166
|
+
"input": {
|
|
167
|
+
"requested": "write:email",
|
|
168
|
+
"granted": ["read:**"]
|
|
169
|
+
},
|
|
170
|
+
"expected": {
|
|
171
|
+
"permitted": false,
|
|
172
|
+
"matchType": "none",
|
|
173
|
+
"matchedBy": null
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "multiple-scopes-first-match",
|
|
178
|
+
"description": "Multiple granted scopes, first match wins",
|
|
179
|
+
"input": {
|
|
180
|
+
"requested": "read:email",
|
|
181
|
+
"granted": ["write:calendar", "read:email"]
|
|
182
|
+
},
|
|
183
|
+
"expected": {
|
|
184
|
+
"permitted": true,
|
|
185
|
+
"matchType": "exact",
|
|
186
|
+
"matchedBy": "read:email"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "multiple-scopes-full-access-wins",
|
|
191
|
+
"description": "Full access wins when present",
|
|
192
|
+
"input": {
|
|
193
|
+
"requested": "read:email",
|
|
194
|
+
"granted": ["write:calendar", "*"]
|
|
195
|
+
},
|
|
196
|
+
"expected": {
|
|
197
|
+
"permitted": true,
|
|
198
|
+
"matchType": "fullAccess",
|
|
199
|
+
"matchedBy": "*"
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "empty-scope-error",
|
|
204
|
+
"description": "Empty scope should error",
|
|
205
|
+
"input": {
|
|
206
|
+
"requested": "",
|
|
207
|
+
"granted": ["read:email"]
|
|
208
|
+
},
|
|
209
|
+
"expected": {
|
|
210
|
+
"permitted": false,
|
|
211
|
+
"error": true,
|
|
212
|
+
"errorType": "EmptyScope"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "wildcard-middle-segment",
|
|
217
|
+
"description": "Wildcard in middle segment",
|
|
218
|
+
"input": {
|
|
219
|
+
"requested": "read:email:inbox",
|
|
220
|
+
"granted": ["read:*:inbox"]
|
|
221
|
+
},
|
|
222
|
+
"expected": {
|
|
223
|
+
"permitted": true,
|
|
224
|
+
"matchType": "wildcard",
|
|
225
|
+
"matchedBy": "read:*:inbox"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "wildcard-first-segment",
|
|
230
|
+
"description": "Wildcard in first segment",
|
|
231
|
+
"input": {
|
|
232
|
+
"requested": "read:email:inbox",
|
|
233
|
+
"granted": ["*:email:inbox"]
|
|
234
|
+
},
|
|
235
|
+
"expected": {
|
|
236
|
+
"permitted": true,
|
|
237
|
+
"matchType": "wildcard",
|
|
238
|
+
"matchedBy": "*:email:inbox"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"id": "empty-granted-no-match",
|
|
243
|
+
"description": "No granted scopes means no match",
|
|
244
|
+
"input": {
|
|
245
|
+
"requested": "read:email",
|
|
246
|
+
"granted": []
|
|
247
|
+
},
|
|
248
|
+
"expected": {
|
|
249
|
+
"permitted": false,
|
|
250
|
+
"matchType": "none",
|
|
251
|
+
"matchedBy": null
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"id": "single-segment-scope",
|
|
256
|
+
"description": "Single segment scope exact match",
|
|
257
|
+
"input": {
|
|
258
|
+
"requested": "admin",
|
|
259
|
+
"granted": ["admin"]
|
|
260
|
+
},
|
|
261
|
+
"expected": {
|
|
262
|
+
"permitted": true,
|
|
263
|
+
"matchType": "exact",
|
|
264
|
+
"matchedBy": "admin"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
"timeBoundaryVectors": [
|
|
269
|
+
{
|
|
270
|
+
"id": "valid-time-window",
|
|
271
|
+
"description": "Current time within valid window",
|
|
272
|
+
"input": {
|
|
273
|
+
"requested": "read:email",
|
|
274
|
+
"granted": ["read:email"],
|
|
275
|
+
"notBefore": 1704067200,
|
|
276
|
+
"notAfter": 1767225600,
|
|
277
|
+
"currentTime": 1735689600
|
|
278
|
+
},
|
|
279
|
+
"expected": {
|
|
280
|
+
"permitted": true
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "expired-delegation",
|
|
285
|
+
"description": "Current time after notAfter",
|
|
286
|
+
"input": {
|
|
287
|
+
"requested": "read:email",
|
|
288
|
+
"granted": ["read:email"],
|
|
289
|
+
"notBefore": null,
|
|
290
|
+
"notAfter": 1704067200,
|
|
291
|
+
"currentTime": 1735689600
|
|
292
|
+
},
|
|
293
|
+
"expected": {
|
|
294
|
+
"permitted": false,
|
|
295
|
+
"error": true,
|
|
296
|
+
"errorType": "Expired"
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"id": "not-yet-valid",
|
|
301
|
+
"description": "Current time before notBefore",
|
|
302
|
+
"input": {
|
|
303
|
+
"requested": "read:email",
|
|
304
|
+
"granted": ["read:email"],
|
|
305
|
+
"notBefore": 1735689600,
|
|
306
|
+
"notAfter": null,
|
|
307
|
+
"currentTime": 1704067200
|
|
308
|
+
},
|
|
309
|
+
"expected": {
|
|
310
|
+
"permitted": false,
|
|
311
|
+
"error": true,
|
|
312
|
+
"errorType": "NotYetValid"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"id": "skip-time-check",
|
|
317
|
+
"description": "Time check skipped when currentTime is 0",
|
|
318
|
+
"input": {
|
|
319
|
+
"requested": "read:email",
|
|
320
|
+
"granted": ["read:email"],
|
|
321
|
+
"notBefore": 9999999999,
|
|
322
|
+
"notAfter": 1,
|
|
323
|
+
"currentTime": 0
|
|
324
|
+
},
|
|
325
|
+
"expected": {
|
|
326
|
+
"permitted": true
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
}
|