@authup/access 1.0.0-beta.27 → 1.0.0-beta.29
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/LICENSE +201 -201
- package/dist/index.mjs +758 -541
- package/dist/index.mjs.map +1 -1
- package/dist/permission/checker/index.d.ts +3 -0
- package/dist/permission/checker/index.d.ts.map +1 -0
- package/dist/permission/{module.d.ts → checker/module.d.ts} +8 -7
- package/dist/permission/checker/module.d.ts.map +1 -0
- package/dist/permission/checker/types.d.ts +26 -0
- package/dist/permission/checker/types.d.ts.map +1 -0
- package/dist/permission/error/index.d.ts +0 -1
- package/dist/permission/error/index.d.ts.map +1 -1
- package/dist/permission/error/module.d.ts +4 -5
- package/dist/permission/error/module.d.ts.map +1 -1
- package/dist/permission/index.d.ts +2 -2
- package/dist/permission/index.d.ts.map +1 -1
- package/dist/permission/repository/index.d.ts.map +1 -0
- package/dist/permission/repository/memory.d.ts +9 -0
- package/dist/permission/repository/memory.d.ts.map +1 -0
- package/dist/permission/{provider → repository}/types.d.ts +2 -2
- package/dist/permission/repository/types.d.ts.map +1 -0
- package/dist/permission/types.d.ts +1 -19
- package/dist/permission/types.d.ts.map +1 -1
- package/dist/policy/built-in/attribute-names/evaluator.d.ts +5 -8
- package/dist/policy/built-in/attribute-names/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/attributes/evaluator.d.ts +5 -9
- package/dist/policy/built-in/attributes/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/attributes/validator.d.ts.map +1 -1
- package/dist/policy/built-in/composite/evaluator.d.ts +3 -8
- package/dist/policy/built-in/composite/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/composite/validator.d.ts.map +1 -1
- package/dist/policy/built-in/date/evaluator.d.ts +3 -8
- package/dist/policy/built-in/date/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/identity/data/index.d.ts +2 -0
- package/dist/policy/built-in/identity/data/index.d.ts.map +1 -0
- package/dist/policy/built-in/identity/data/validator.d.ts +6 -0
- package/dist/policy/built-in/identity/data/validator.d.ts.map +1 -0
- package/dist/policy/built-in/identity/evaluator.d.ts +7 -8
- package/dist/policy/built-in/identity/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/identity/types.d.ts +22 -0
- package/dist/policy/built-in/identity/types.d.ts.map +1 -1
- package/dist/policy/built-in/permission-binding/module.d.ts +3 -8
- package/dist/policy/built-in/permission-binding/module.d.ts.map +1 -1
- package/dist/policy/built-in/realm-match/evaluator.d.ts +7 -8
- package/dist/policy/built-in/realm-match/evaluator.d.ts.map +1 -1
- package/dist/policy/built-in/time/evaluator.d.ts +3 -8
- package/dist/policy/built-in/time/evaluator.d.ts.map +1 -1
- package/dist/policy/constants.d.ts +12 -0
- package/dist/policy/constants.d.ts.map +1 -0
- package/dist/policy/data.d.ts +20 -0
- package/dist/policy/data.d.ts.map +1 -0
- package/dist/policy/engine/module.d.ts +9 -9
- package/dist/policy/engine/module.d.ts.map +1 -1
- package/dist/policy/engine/types.d.ts +6 -3
- package/dist/policy/engine/types.d.ts.map +1 -1
- package/dist/policy/error/module.d.ts +5 -2
- package/dist/policy/error/module.d.ts.map +1 -1
- package/dist/policy/evaluation/context/define.d.ts +3 -0
- package/dist/policy/evaluation/context/define.d.ts.map +1 -0
- package/dist/policy/evaluation/context/index.d.ts +3 -0
- package/dist/policy/evaluation/context/index.d.ts.map +1 -0
- package/dist/policy/evaluation/context/types.d.ts +11 -0
- package/dist/policy/evaluation/context/types.d.ts.map +1 -0
- package/dist/policy/evaluation/index.d.ts +3 -0
- package/dist/policy/evaluation/index.d.ts.map +1 -0
- package/dist/policy/evaluation/types.d.ts +14 -0
- package/dist/policy/evaluation/types.d.ts.map +1 -0
- package/dist/policy/index.d.ts +4 -1
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/issue/constants.d.ts +21 -0
- package/dist/policy/issue/constants.d.ts.map +1 -0
- package/dist/policy/issue/define.d.ts +4 -0
- package/dist/policy/issue/define.d.ts.map +1 -0
- package/dist/policy/issue/index.d.ts +4 -0
- package/dist/policy/issue/index.d.ts.map +1 -0
- package/dist/policy/issue/types.d.ts +5 -0
- package/dist/policy/issue/types.d.ts.map +1 -0
- package/dist/policy/types.d.ts +3 -41
- package/dist/policy/types.d.ts.map +1 -1
- package/package.json +19 -15
- package/dist/index.cjs +0 -1055
- package/dist/index.cjs.map +0 -1
- package/dist/permission/error/types.d.ts +0 -11
- package/dist/permission/error/types.d.ts.map +0 -1
- package/dist/permission/module.d.ts.map +0 -1
- package/dist/permission/provider/index.d.ts.map +0 -1
- package/dist/permission/provider/memory.d.ts +0 -9
- package/dist/permission/provider/memory.d.ts.map +0 -1
- package/dist/permission/provider/types.d.ts.map +0 -1
- package/dist/policy/evaluator/data/identity/index.d.ts +0 -2
- package/dist/policy/evaluator/data/identity/index.d.ts.map +0 -1
- package/dist/policy/evaluator/data/identity/validator.d.ts +0 -6
- package/dist/policy/evaluator/data/identity/validator.d.ts.map +0 -1
- package/dist/policy/evaluator/data/index.d.ts +0 -4
- package/dist/policy/evaluator/data/index.d.ts.map +0 -1
- package/dist/policy/evaluator/data/permission/index.d.ts +0 -2
- package/dist/policy/evaluator/data/permission/index.d.ts.map +0 -1
- package/dist/policy/evaluator/data/permission/validator.d.ts +0 -6
- package/dist/policy/evaluator/data/permission/validator.d.ts.map +0 -1
- package/dist/policy/evaluator/data/validator.d.ts +0 -6
- package/dist/policy/evaluator/data/validator.d.ts.map +0 -1
- package/dist/policy/evaluator/evaluate.d.ts +0 -4
- package/dist/policy/evaluator/evaluate.d.ts.map +0 -1
- package/dist/policy/evaluator/index.d.ts +0 -4
- package/dist/policy/evaluator/index.d.ts.map +0 -1
- package/dist/policy/evaluator/types.d.ts +0 -44
- package/dist/policy/evaluator/types.d.ts.map +0 -1
- /package/dist/permission/{provider → repository}/index.d.ts +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,101 @@
|
|
|
1
1
|
import { AuthupError, ErrorCode } from '@authup/errors';
|
|
2
|
+
import { defineIssueItem, defineIssueGroup, Container } from 'validup';
|
|
2
3
|
import { isObject, flattenObject, hasOwnProperty } from '@authup/kit';
|
|
3
4
|
import { createValidator } from '@validup/adapter-zod';
|
|
4
|
-
import { Container } from 'validup';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { guard } from '@ucast/mongo2js';
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
class PermissionError extends AuthupError {
|
|
9
|
+
constructor(options = {}){
|
|
10
|
+
super({
|
|
11
|
+
...options,
|
|
12
|
+
message: options.message || 'A permission error occurred.',
|
|
13
|
+
statusCode: options.statusCode || 403
|
|
14
|
+
});
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
addIssue(data) {
|
|
17
|
+
this.issues.push(data);
|
|
14
18
|
}
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
addIssues(data) {
|
|
20
|
+
this.issues.push(...data);
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
message
|
|
21
|
-
code
|
|
22
|
+
static notFound(name) {
|
|
23
|
+
return new PermissionError({
|
|
24
|
+
message: `The permission ${name} was not found.`,
|
|
25
|
+
code: ErrorCode.PERMISSION_NOT_FOUND
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
static denied(name) {
|
|
29
|
+
return new PermissionError({
|
|
30
|
+
message: `The permission ${name} has not been granted.`,
|
|
31
|
+
code: ErrorCode.PERMISSION_DENIED
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
static deniedAll(names) {
|
|
35
|
+
return new PermissionError({
|
|
36
|
+
message: `None of the permissions ${names.join(', ')} has been granted.`,
|
|
37
|
+
code: ErrorCode.PERMISSION_DENIED
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
static evaluationFailed(name) {
|
|
41
|
+
if (Array.isArray(name)) {
|
|
42
|
+
return new PermissionError({
|
|
43
|
+
message: `The evaluation of permissions ${name.join(', ')} failed`,
|
|
44
|
+
code: ErrorCode.PERMISSION_EVALUATION_FAILED
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return new PermissionError({
|
|
48
|
+
message: `The evaluation of permission ${name} failed`,
|
|
49
|
+
code: ErrorCode.PERMISSION_EVALUATION_FAILED
|
|
22
50
|
});
|
|
23
51
|
}
|
|
24
52
|
}
|
|
25
53
|
|
|
54
|
+
/*
|
|
55
|
+
* Copyright (c) 2025.
|
|
56
|
+
* Author Peter Placzek (tada5hi)
|
|
57
|
+
* For the full copyright and license information,
|
|
58
|
+
* view the LICENSE file that was distributed with this source code.
|
|
59
|
+
*/ function buildPermissionItemKey(input) {
|
|
60
|
+
return `${input.realmId || '_'}/${input.clientId || '_'}/${input.name}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Copyright (c) 2024-2024.
|
|
65
|
+
* Author Peter Placzek (tada5hi)
|
|
66
|
+
* For the full copyright and license information,
|
|
67
|
+
* view the LICENSE file that was distributed with this source code.
|
|
68
|
+
*/ function isPermissionItemEqual(a, b) {
|
|
69
|
+
if (a.name !== b.name) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (typeof a.realmId === 'string' || typeof b.realmId === 'string') {
|
|
73
|
+
return a.realmId === b.realmId;
|
|
74
|
+
}
|
|
75
|
+
if (typeof a.clientId === 'string' || typeof b.clientId === 'string') {
|
|
76
|
+
return a.clientId === b.clientId;
|
|
77
|
+
}
|
|
78
|
+
return !!a.realmId === !!b.realmId && !!a.clientId === !!b.clientId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Copyright (c) 2024-2024.
|
|
83
|
+
* Author Peter Placzek (tada5hi)
|
|
84
|
+
* For the full copyright and license information,
|
|
85
|
+
* view the LICENSE file that was distributed with this source code.
|
|
86
|
+
*/ var DecisionStrategy = /*#__PURE__*/ function(DecisionStrategy) {
|
|
87
|
+
/**
|
|
88
|
+
* One or more positive
|
|
89
|
+
*/ DecisionStrategy["AFFIRMATIVE"] = "affirmative";
|
|
90
|
+
/**
|
|
91
|
+
* All positive
|
|
92
|
+
*/ DecisionStrategy["UNANIMOUS"] = "unanimous";
|
|
93
|
+
/**
|
|
94
|
+
* More positive than negative
|
|
95
|
+
*/ DecisionStrategy["CONSENSUS"] = "consensus";
|
|
96
|
+
return DecisionStrategy;
|
|
97
|
+
}({});
|
|
98
|
+
|
|
26
99
|
/*
|
|
27
100
|
* Copyright (c) 2024-2024.
|
|
28
101
|
* Author Peter Placzek (tada5hi)
|
|
@@ -41,6 +114,226 @@ class PolicyError extends AuthupError {
|
|
|
41
114
|
return !outcome;
|
|
42
115
|
}
|
|
43
116
|
|
|
117
|
+
class PolicyError extends AuthupError {
|
|
118
|
+
constructor(message, code){
|
|
119
|
+
super({
|
|
120
|
+
message,
|
|
121
|
+
code
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
addIssue(data) {
|
|
125
|
+
this.issues.push(data);
|
|
126
|
+
}
|
|
127
|
+
addIssues(data) {
|
|
128
|
+
this.issues.push(...data);
|
|
129
|
+
}
|
|
130
|
+
static evaluatorNotFound(type) {
|
|
131
|
+
return new PolicyError(`No evaluator is registered to handle the policy: ${type}`, ErrorCode.POLICY_EVALUATOR_NOT_FOUND);
|
|
132
|
+
}
|
|
133
|
+
static evaluatorNotProcessable() {
|
|
134
|
+
return new PolicyError('The evaluator can not process the policy.', ErrorCode.POLICY_EVALUATOR_NOT_PROCESSABLE);
|
|
135
|
+
}
|
|
136
|
+
static evaluatorContextInvalid() {
|
|
137
|
+
return new PolicyError('The evaluator context is not valid.', ErrorCode.POLICY_EVALUATOR_CONTEXT_INVALID);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The policy engine is a component that interprets defined policies and makes decisions
|
|
143
|
+
* on whether to allow or deny a particular access.
|
|
144
|
+
*/ class PolicyEngine {
|
|
145
|
+
evaluators;
|
|
146
|
+
constructor(evaluators = {}){
|
|
147
|
+
this.evaluators = evaluators;
|
|
148
|
+
}
|
|
149
|
+
registerEvaluator(type, evaluator) {
|
|
150
|
+
this.evaluators[type] = evaluator;
|
|
151
|
+
}
|
|
152
|
+
registerEvaluators(evaluators) {
|
|
153
|
+
const keys = Object.keys(evaluators);
|
|
154
|
+
for(let i = 0; i < keys.length; i++){
|
|
155
|
+
this.registerEvaluator(keys[i], evaluators[keys[i]]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @param policy
|
|
160
|
+
* @param ctx
|
|
161
|
+
*/ async evaluate(policy, ctx) {
|
|
162
|
+
if (ctx.exclude && ctx.exclude.length > 0 && ctx.exclude.indexOf(policy.type) !== -1) {
|
|
163
|
+
return {
|
|
164
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (ctx.include && ctx.include.length > 0 && ctx.include.indexOf(policy.type) === -1) {
|
|
168
|
+
return {
|
|
169
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const issues = [];
|
|
173
|
+
const evaluator = this.evaluators[policy.type];
|
|
174
|
+
if (!evaluator) {
|
|
175
|
+
// todo: add issue here instead + return false ?
|
|
176
|
+
issues.push(defineIssueItem({
|
|
177
|
+
path: [
|
|
178
|
+
policy.type
|
|
179
|
+
],
|
|
180
|
+
message: `The policy ${policy.type} can not be handled by any evaluator.`,
|
|
181
|
+
code: ErrorCode.POLICY_EVALUATOR_NOT_FOUND
|
|
182
|
+
}));
|
|
183
|
+
return {
|
|
184
|
+
success: maybeInvertPolicyOutcome(false, policy.invert),
|
|
185
|
+
issues
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return await evaluator.evaluate(policy, {
|
|
190
|
+
...ctx,
|
|
191
|
+
evaluators: {
|
|
192
|
+
...this.evaluators,
|
|
193
|
+
...ctx.evaluators || {}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
} catch (e) {
|
|
197
|
+
issues.push(defineIssueItem({
|
|
198
|
+
path: [
|
|
199
|
+
policy.type
|
|
200
|
+
],
|
|
201
|
+
message: `The ${policy.type} evaluator can not process the policy specification.`,
|
|
202
|
+
code: ErrorCode.POLICY_EVALUATOR_NOT_PROCESSABLE
|
|
203
|
+
}));
|
|
204
|
+
return {
|
|
205
|
+
success: maybeInvertPolicyOutcome(false, policy.invert),
|
|
206
|
+
issues
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async evaluateOrFail(policy, ctx) {
|
|
211
|
+
const issues = [];
|
|
212
|
+
try {
|
|
213
|
+
const outcome = await this.evaluate(policy, ctx);
|
|
214
|
+
if (outcome.success) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (outcome.issues) {
|
|
218
|
+
issues.push(...outcome.issues);
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
if (e instanceof PolicyError) {
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const error = new PolicyError(`The policy ${policy.type} evaluation failed.`);
|
|
226
|
+
error.addIssues(issues);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* Copyright (c) 2026.
|
|
233
|
+
* Author Peter Placzek (tada5hi)
|
|
234
|
+
* For the full copyright and license information,
|
|
235
|
+
* view the LICENSE file that was distributed with this source code.
|
|
236
|
+
*/ class PolicyData {
|
|
237
|
+
data;
|
|
238
|
+
validated;
|
|
239
|
+
constructor(data = {}, validated = new Set()){
|
|
240
|
+
this.data = data;
|
|
241
|
+
this.validated = validated;
|
|
242
|
+
}
|
|
243
|
+
set(key, value) {
|
|
244
|
+
this.data[key] = value;
|
|
245
|
+
this.validated.delete(key);
|
|
246
|
+
}
|
|
247
|
+
get(key) {
|
|
248
|
+
if (key in this.data) {
|
|
249
|
+
return this.data[key];
|
|
250
|
+
}
|
|
251
|
+
throw new Error(`Policy data item ${key} does not exist.`);
|
|
252
|
+
}
|
|
253
|
+
has(key) {
|
|
254
|
+
return key in this.data;
|
|
255
|
+
}
|
|
256
|
+
setValidated(key) {
|
|
257
|
+
this.validated.add(key);
|
|
258
|
+
}
|
|
259
|
+
isValidated(key) {
|
|
260
|
+
return this.validated.has(key);
|
|
261
|
+
}
|
|
262
|
+
clone() {
|
|
263
|
+
return new PolicyData({
|
|
264
|
+
...this.data
|
|
265
|
+
}, new Set(this.validated));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function definePolicyEvaluationContext(input = {}) {
|
|
270
|
+
return {
|
|
271
|
+
evaluators: input.evaluators || {},
|
|
272
|
+
path: input.path || [],
|
|
273
|
+
data: input.data || new PolicyData(),
|
|
274
|
+
include: input.include || [],
|
|
275
|
+
exclude: input.exclude || []
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/*
|
|
280
|
+
* Copyright (c) 2026-2026.
|
|
281
|
+
* Author Peter Placzek (tada5hi)
|
|
282
|
+
* For the full copyright and license information,
|
|
283
|
+
* view the LICENSE file that was distributed with this source code.
|
|
284
|
+
*/ var PolicyIssueSeverity = /*#__PURE__*/ function(PolicyIssueSeverity) {
|
|
285
|
+
PolicyIssueSeverity["WARNING"] = "warning";
|
|
286
|
+
PolicyIssueSeverity["ERROR"] = "error";
|
|
287
|
+
return PolicyIssueSeverity;
|
|
288
|
+
}({});
|
|
289
|
+
var PolicyIssueCode = /*#__PURE__*/ function(PolicyIssueCode) {
|
|
290
|
+
// Data/Input
|
|
291
|
+
PolicyIssueCode["DATA_MISSING"] = "DATA_MISSING";
|
|
292
|
+
PolicyIssueCode["DATA_INVALID"] = "DATA_INVALID";
|
|
293
|
+
// Policy Spec / Config
|
|
294
|
+
PolicyIssueCode["INVALID"] = "INVALID";
|
|
295
|
+
PolicyIssueCode["TYPE_UNKNOWN"] = "TYPE_UNKNOWN";
|
|
296
|
+
PolicyIssueCode["FIELD_MISSING"] = "FIELD_MISSING";
|
|
297
|
+
PolicyIssueCode["FIELD_INVALID"] = "FIELD_INVALID";
|
|
298
|
+
// Composite / Tree evaluation
|
|
299
|
+
PolicyIssueCode["CHILD_INVALID"] = "CHILD_INVALID";
|
|
300
|
+
PolicyIssueCode["CHILD_EVALUATION_FAILED"] = "CHILD_EVALUATION_FAILED";
|
|
301
|
+
// Engine / Evaluator registry
|
|
302
|
+
PolicyIssueCode["EVALUATION_DENIED"] = "EVALUATION_DENIED";
|
|
303
|
+
PolicyIssueCode["EVALUATOR_NOT_FOUND"] = "EVALUATOR_NOT_FOUND";
|
|
304
|
+
PolicyIssueCode["EVALUATOR_ERROR"] = "EVALUATOR_ERROR";
|
|
305
|
+
// Execution control
|
|
306
|
+
PolicyIssueCode["SKIPPED_EXCLUDED"] = "SKIPPED_EXCLUDED";
|
|
307
|
+
PolicyIssueCode["SKIPPED_NOT_INCLUDED"] = "SKIPPED_NOT_INCLUDED";
|
|
308
|
+
// Internal / unexpected
|
|
309
|
+
PolicyIssueCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
310
|
+
return PolicyIssueCode;
|
|
311
|
+
}({});
|
|
312
|
+
|
|
313
|
+
function definePolicyIssueItem(input) {
|
|
314
|
+
return defineIssueItem(input);
|
|
315
|
+
}
|
|
316
|
+
function definePolicyIssueGroup(input) {
|
|
317
|
+
return defineIssueGroup(input);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
class AttributeNamesPolicyValidator extends Container {
|
|
321
|
+
initialize() {
|
|
322
|
+
super.initialize();
|
|
323
|
+
this.mount('names', createValidator(z.array(z.string().min(3).max(128))));
|
|
324
|
+
this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* Copyright (c) 2025.
|
|
330
|
+
* Author Peter Placzek (tada5hi)
|
|
331
|
+
* For the full copyright and license information,
|
|
332
|
+
* view the LICENSE file that was distributed with this source code.
|
|
333
|
+
*/ function defineAttributesPolicy(data) {
|
|
334
|
+
return data;
|
|
335
|
+
}
|
|
336
|
+
|
|
44
337
|
/*
|
|
45
338
|
* Copyright (c) 2024-2024.
|
|
46
339
|
* Author Peter Placzek (tada5hi)
|
|
@@ -74,277 +367,218 @@ class PolicyError extends AuthupError {
|
|
|
74
367
|
return BuiltInPolicyType;
|
|
75
368
|
}({});
|
|
76
369
|
|
|
77
|
-
class AttributeNamesPolicyValidator extends Container {
|
|
78
|
-
initialize() {
|
|
79
|
-
super.initialize();
|
|
80
|
-
this.mount('names', createValidator(z.array(z.string().min(3).max(128))));
|
|
81
|
-
this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
class AttributeNamesPolicyEvaluator {
|
|
86
|
-
async can(ctx) {
|
|
87
|
-
return ctx.config.type === BuiltInPolicyType.ATTRIBUTE_NAMES;
|
|
88
|
-
}
|
|
89
|
-
async validateConfig(ctx) {
|
|
90
|
-
return this.validator.run(ctx.config);
|
|
91
|
-
}
|
|
92
|
-
async validateInput(ctx) {
|
|
93
|
-
if (!isObject(ctx.input.attributes)) {
|
|
94
|
-
throw PolicyError.evaluatorContextInvalid();
|
|
95
|
-
}
|
|
96
|
-
return ctx.input;
|
|
97
|
-
}
|
|
98
|
-
async evaluate(ctx) {
|
|
99
|
-
if (!ctx.input.attributes) {
|
|
100
|
-
throw PolicyError.evaluatorContextInvalid();
|
|
101
|
-
}
|
|
102
|
-
const attributes = flattenObject(ctx.input.attributes);
|
|
103
|
-
const keys = Object.keys(attributes);
|
|
104
|
-
for(let i = 0; i < keys.length; i++){
|
|
105
|
-
const index = ctx.config.names.indexOf(keys[i]);
|
|
106
|
-
if (index === -1) {
|
|
107
|
-
return maybeInvertPolicyOutcome(false, ctx.config.invert);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return maybeInvertPolicyOutcome(true, ctx.config.invert);
|
|
111
|
-
}
|
|
112
|
-
constructor(){
|
|
113
|
-
this.validator = new AttributeNamesPolicyValidator();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/*
|
|
118
|
-
* Copyright (c) 2025.
|
|
119
|
-
* Author Peter Placzek (tada5hi)
|
|
120
|
-
* For the full copyright and license information,
|
|
121
|
-
* view the LICENSE file that was distributed with this source code.
|
|
122
|
-
*/ function defineAttributesPolicy(data) {
|
|
123
|
-
return data;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
370
|
class AttributesPolicyValidator extends Container {
|
|
127
371
|
initialize() {
|
|
128
372
|
super.initialize();
|
|
129
|
-
this.mount('query', createValidator(z.
|
|
373
|
+
this.mount('query', createValidator(z.looseObject({})));
|
|
130
374
|
this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
131
375
|
}
|
|
132
376
|
}
|
|
133
377
|
|
|
134
378
|
class AttributesPolicyEvaluator {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
async validateConfig(ctx) {
|
|
139
|
-
return this.validator.run(ctx.config);
|
|
379
|
+
validator;
|
|
380
|
+
constructor(){
|
|
381
|
+
this.validator = new AttributesPolicyValidator();
|
|
140
382
|
}
|
|
141
|
-
async
|
|
142
|
-
if (!
|
|
143
|
-
|
|
383
|
+
async accessData(ctx) {
|
|
384
|
+
if (!ctx.data.has(BuiltInPolicyType.ATTRIBUTES)) {
|
|
385
|
+
return null;
|
|
144
386
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
387
|
+
if (ctx.data.isValidated(BuiltInPolicyType.ATTRIBUTES)) {
|
|
388
|
+
return ctx.data.get(BuiltInPolicyType.ATTRIBUTES);
|
|
389
|
+
}
|
|
390
|
+
// todo: run validator on attributes (isObject ...)
|
|
391
|
+
const data = ctx.data.get(BuiltInPolicyType.ATTRIBUTES);
|
|
392
|
+
ctx.data.set(BuiltInPolicyType.ATTRIBUTES, data);
|
|
393
|
+
ctx.data.setValidated(BuiltInPolicyType.ATTRIBUTES);
|
|
394
|
+
return data;
|
|
395
|
+
}
|
|
396
|
+
async evaluate(value, ctx) {
|
|
397
|
+
// todo: catch errors + transform to issue(s)
|
|
398
|
+
const policy = await this.validator.run(value);
|
|
399
|
+
const data = await this.accessData(ctx);
|
|
400
|
+
if (!data) {
|
|
401
|
+
return {
|
|
402
|
+
success: false,
|
|
403
|
+
issues: [
|
|
404
|
+
definePolicyIssueItem({
|
|
405
|
+
code: PolicyIssueCode.DATA_MISSING,
|
|
406
|
+
message: 'The data property attributes is missing',
|
|
407
|
+
path: ctx.path
|
|
408
|
+
})
|
|
409
|
+
]
|
|
410
|
+
};
|
|
150
411
|
}
|
|
151
|
-
this.fixQuery(
|
|
152
|
-
const testIt = guard(
|
|
153
|
-
return
|
|
412
|
+
this.fixQuery(policy.query);
|
|
413
|
+
const testIt = guard(policy.query);
|
|
414
|
+
return {
|
|
415
|
+
success: maybeInvertPolicyOutcome(testIt(data), policy.invert)
|
|
416
|
+
};
|
|
154
417
|
}
|
|
155
418
|
fixQuery(query) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const value = query[key];
|
|
160
|
-
if (isObject(value) || Array.isArray(value)) {
|
|
161
|
-
this.fixQuery(value);
|
|
162
|
-
continue;
|
|
419
|
+
if (Array.isArray(query)) {
|
|
420
|
+
for(let i = 0; i < query.length; i++){
|
|
421
|
+
this.fixQuery(query[i]);
|
|
163
422
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (isObject(query)) {
|
|
426
|
+
const keys = Object.keys(query);
|
|
427
|
+
for(let i = 0; i < keys.length; i++){
|
|
428
|
+
const key = keys[i];
|
|
429
|
+
const value = query[key];
|
|
430
|
+
if (isObject(value) || Array.isArray(value)) {
|
|
431
|
+
this.fixQuery(value);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (key === '$regex' && typeof value === 'string') {
|
|
435
|
+
const fragments = value.match(/\/(.*?)\/([a-z]*)?$/i);
|
|
436
|
+
if (fragments) {
|
|
437
|
+
query[key] = new RegExp(fragments[1], fragments[2]);
|
|
438
|
+
}
|
|
168
439
|
}
|
|
169
440
|
}
|
|
170
441
|
}
|
|
171
442
|
}
|
|
172
|
-
constructor(){
|
|
173
|
-
this.validator = new AttributesPolicyValidator();
|
|
174
|
-
}
|
|
175
443
|
}
|
|
176
444
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.mount('id', createValidator(z.string().uuid()));
|
|
200
|
-
this.mount('clientId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
|
|
201
|
-
this.mount('realmId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
|
|
202
|
-
this.mount('realmName', createValidator(z.string().min(3).max(128).or(z.null()).or(z.undefined()).optional()));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
class PolicyDataPermissionValidator extends Container {
|
|
207
|
-
initialize() {
|
|
208
|
-
super.initialize();
|
|
209
|
-
this.mount('name', createValidator(z.string().min(3).max(256)));
|
|
210
|
-
this.mount('clientId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
|
|
211
|
-
this.mount('realmId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
|
|
212
|
-
this.mount('policy', createValidator(z.object({
|
|
213
|
-
type: z.string()
|
|
214
|
-
}).optional()));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
class PolicyDataValidator extends Container {
|
|
219
|
-
initialize() {
|
|
220
|
-
super.initialize();
|
|
221
|
-
// todo: optionalValue should be null
|
|
222
|
-
const identity = new PolicyDataIdentityValidator();
|
|
223
|
-
this.mount('identity', {
|
|
224
|
-
optional: true,
|
|
225
|
-
optionalValue: 'undefined',
|
|
226
|
-
optionalInclude: true
|
|
227
|
-
}, identity);
|
|
228
|
-
// todo: optionalValue should be null
|
|
229
|
-
const permission = new PolicyDataPermissionValidator();
|
|
230
|
-
this.mount('permission', {
|
|
231
|
-
optional: true,
|
|
232
|
-
optionalValue: 'undefined',
|
|
233
|
-
optionalInclude: true
|
|
234
|
-
}, permission);
|
|
235
|
-
this.mount('attributes', createValidator(z.object({}).optional()));
|
|
236
|
-
this.mount('dateTime', createValidator(z.date().or(z.string()).or(z.number()).optional()));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function evaluatePolicy(ctx) {
|
|
241
|
-
if (ctx.options.exclude && ctx.options.exclude.length > 0 && ctx.options.exclude.indexOf(ctx.config.type) !== -1) {
|
|
242
|
-
if (typeof ctx.config.invert === 'boolean') {
|
|
243
|
-
return maybeInvertPolicyOutcome(true, ctx.config.invert);
|
|
244
|
-
}
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
if (ctx.options.include && ctx.options.include.length > 0 && ctx.options.include.indexOf(ctx.config.type) === -1) {
|
|
248
|
-
if (typeof ctx.config.invert === 'boolean') {
|
|
249
|
-
return maybeInvertPolicyOutcome(true, ctx.config.invert);
|
|
250
|
-
}
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
const evaluator = ctx.evaluators[ctx.config.type];
|
|
254
|
-
if (!evaluator) {
|
|
255
|
-
throw PolicyError.evaluatorNotFound(ctx.config.type);
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
const canEvaluate = await evaluator.can(ctx);
|
|
259
|
-
if (canEvaluate) {
|
|
260
|
-
const config = await evaluator.validateConfig(ctx);
|
|
261
|
-
const input = await evaluator.validateInput(ctx);
|
|
262
|
-
return await evaluator.evaluate({
|
|
263
|
-
...ctx,
|
|
264
|
-
input,
|
|
265
|
-
config
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
} catch (e) {
|
|
269
|
-
if (e instanceof PolicyError) {
|
|
270
|
-
throw e;
|
|
445
|
+
class AttributeNamesPolicyEvaluator {
|
|
446
|
+
validator;
|
|
447
|
+
attributesEvaluator;
|
|
448
|
+
constructor(){
|
|
449
|
+
this.validator = new AttributeNamesPolicyValidator();
|
|
450
|
+
this.attributesEvaluator = new AttributesPolicyEvaluator();
|
|
451
|
+
}
|
|
452
|
+
async evaluate(value, ctx) {
|
|
453
|
+
// todo: catch errors + transform to issue(s)
|
|
454
|
+
const policy = await this.validator.run(value);
|
|
455
|
+
const data = await this.attributesEvaluator.accessData(ctx);
|
|
456
|
+
if (!data) {
|
|
457
|
+
return {
|
|
458
|
+
success: false,
|
|
459
|
+
issues: [
|
|
460
|
+
definePolicyIssueItem({
|
|
461
|
+
code: PolicyIssueCode.DATA_MISSING,
|
|
462
|
+
message: 'The data property attributes is missing',
|
|
463
|
+
path: ctx.path
|
|
464
|
+
})
|
|
465
|
+
]
|
|
466
|
+
};
|
|
271
467
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
468
|
+
const attributes = flattenObject(data);
|
|
469
|
+
const keys = Object.keys(attributes);
|
|
470
|
+
const issues = [];
|
|
471
|
+
for(let i = 0; i < keys.length; i++){
|
|
472
|
+
const index = policy.names.indexOf(keys[i]);
|
|
473
|
+
if (index === -1) {
|
|
474
|
+
issues.push(definePolicyIssueItem({
|
|
475
|
+
code: PolicyIssueCode.EVALUATION_DENIED,
|
|
476
|
+
message: `The attribute ${keys[i]} is not included`,
|
|
477
|
+
path: [
|
|
478
|
+
...ctx.path,
|
|
479
|
+
keys[i]
|
|
480
|
+
]
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
276
483
|
}
|
|
277
|
-
|
|
484
|
+
return {
|
|
485
|
+
success: maybeInvertPolicyOutcome(issues.length === 0, policy.invert),
|
|
486
|
+
issues
|
|
487
|
+
};
|
|
278
488
|
}
|
|
279
|
-
throw PolicyError.evaluatorNotProcessable();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function isCompositePolicy(input) {
|
|
283
|
-
return input.type === BuiltInPolicyType.COMPOSITE;
|
|
284
489
|
}
|
|
285
490
|
|
|
286
491
|
class CompositePolicyValidator extends Container {
|
|
287
492
|
initialize() {
|
|
288
493
|
super.initialize();
|
|
289
|
-
this.mount('decisionStrategy', createValidator(z.
|
|
290
|
-
this.mount('children', createValidator(z.array(z.
|
|
494
|
+
this.mount('decisionStrategy', createValidator(z.enum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
|
|
495
|
+
this.mount('children', createValidator(z.array(z.looseObject({
|
|
291
496
|
type: z.string().min(3)
|
|
292
|
-
})
|
|
497
|
+
}))));
|
|
293
498
|
this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
294
499
|
}
|
|
295
500
|
}
|
|
296
501
|
|
|
297
502
|
class CompositePolicyEvaluator {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
async validateConfig(ctx) {
|
|
302
|
-
return this.validator.run(ctx.config);
|
|
303
|
-
}
|
|
304
|
-
async validateInput(ctx) {
|
|
305
|
-
return ctx.input;
|
|
503
|
+
validator;
|
|
504
|
+
constructor(){
|
|
505
|
+
this.validator = new CompositePolicyValidator();
|
|
306
506
|
}
|
|
307
|
-
async evaluate(ctx) {
|
|
507
|
+
async evaluate(value, ctx) {
|
|
508
|
+
// todo: catch errors + transform to issue(s)
|
|
509
|
+
const policy = await this.validator.run(value);
|
|
308
510
|
let count = 0;
|
|
309
|
-
const decisionStrategy =
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
if (outcome) {
|
|
511
|
+
const decisionStrategy = policy.decisionStrategy ?? DecisionStrategy.UNANIMOUS;
|
|
512
|
+
const engine = new PolicyEngine(ctx.evaluators);
|
|
513
|
+
const issues = [];
|
|
514
|
+
for(let i = 0; i < policy.children.length; i++){
|
|
515
|
+
const childPolicy = policy.children[i];
|
|
516
|
+
const path = [
|
|
517
|
+
...ctx.path || [],
|
|
518
|
+
childPolicy.type
|
|
519
|
+
];
|
|
520
|
+
const outcome = await engine.evaluate(childPolicy, {
|
|
521
|
+
...ctx,
|
|
522
|
+
path
|
|
523
|
+
});
|
|
524
|
+
if (outcome.success) {
|
|
325
525
|
if (decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
|
|
326
|
-
return
|
|
526
|
+
return {
|
|
527
|
+
success: maybeInvertPolicyOutcome(true, policy.invert),
|
|
528
|
+
issues: []
|
|
529
|
+
};
|
|
327
530
|
}
|
|
328
531
|
count++;
|
|
329
532
|
} else {
|
|
533
|
+
if (outcome.issues) {
|
|
534
|
+
issues.push(defineIssueGroup({
|
|
535
|
+
message: `The evaluation of child policy ${childPolicy.type} failed`,
|
|
536
|
+
issues: outcome.issues || [],
|
|
537
|
+
path
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
330
540
|
if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
|
|
331
|
-
return
|
|
541
|
+
return {
|
|
542
|
+
success: maybeInvertPolicyOutcome(false, policy.invert),
|
|
543
|
+
issues: [
|
|
544
|
+
defineIssueGroup({
|
|
545
|
+
message: `The evaluation of composite policy failed (${DecisionStrategy.UNANIMOUS})`,
|
|
546
|
+
issues,
|
|
547
|
+
path: ctx.path
|
|
548
|
+
})
|
|
549
|
+
]
|
|
550
|
+
};
|
|
332
551
|
}
|
|
333
552
|
count--;
|
|
334
553
|
}
|
|
335
554
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
555
|
+
if (count > 0) {
|
|
556
|
+
return {
|
|
557
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
success: maybeInvertPolicyOutcome(false, policy.invert),
|
|
562
|
+
issues: [
|
|
563
|
+
defineIssueGroup({
|
|
564
|
+
message: `The evaluation of composite policy failed (${DecisionStrategy.CONSENSUS})`,
|
|
565
|
+
issues: issues || [],
|
|
566
|
+
path: ctx.path
|
|
567
|
+
})
|
|
568
|
+
]
|
|
569
|
+
};
|
|
340
570
|
}
|
|
341
571
|
}
|
|
342
572
|
|
|
573
|
+
function isCompositePolicy(input) {
|
|
574
|
+
return input.type === BuiltInPolicyType.COMPOSITE;
|
|
575
|
+
}
|
|
576
|
+
|
|
343
577
|
class DatePolicyValidator extends Container {
|
|
344
578
|
initialize() {
|
|
345
579
|
super.initialize();
|
|
346
|
-
this.mount('start', createValidator(z.date().or(z.
|
|
347
|
-
this.mount('end', createValidator(z.date().or(z.
|
|
580
|
+
this.mount('start', createValidator(z.date().or(z.iso.date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
|
|
581
|
+
this.mount('end', createValidator(z.date().or(z.iso.date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
|
|
348
582
|
this.mount('invert', createValidator(z.boolean().or(z.undefined()).or(z.null()).optional()));
|
|
349
583
|
}
|
|
350
584
|
}
|
|
@@ -362,38 +596,56 @@ function toDate$1(input) {
|
|
|
362
596
|
return input;
|
|
363
597
|
}
|
|
364
598
|
class DatePolicyEvaluator {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
async validateConfig(ctx) {
|
|
369
|
-
return this.validator.run(ctx.config);
|
|
370
|
-
}
|
|
371
|
-
async validateInput(ctx) {
|
|
372
|
-
return ctx.input;
|
|
599
|
+
validator;
|
|
600
|
+
constructor(){
|
|
601
|
+
this.validator = new DatePolicyValidator();
|
|
373
602
|
}
|
|
374
|
-
async evaluate(ctx) {
|
|
603
|
+
async evaluate(value, ctx) {
|
|
604
|
+
// todo: catch errors + transform to issue(s)
|
|
605
|
+
const policy = await this.validator.run(value);
|
|
375
606
|
let now;
|
|
376
|
-
if (ctx.
|
|
377
|
-
|
|
607
|
+
if (ctx.data.has(BuiltInPolicyType.DATE)) {
|
|
608
|
+
if (ctx.data.isValidated(BuiltInPolicyType.DATE)) {
|
|
609
|
+
now = ctx.data.get(BuiltInPolicyType.DATE);
|
|
610
|
+
} else {
|
|
611
|
+
// todo: run validator on attributes (isObject ...)
|
|
612
|
+
now = normalizeDate$1(toDate$1(ctx.data.get(BuiltInPolicyType.DATE)));
|
|
613
|
+
ctx.data.set(BuiltInPolicyType.DATE, now);
|
|
614
|
+
ctx.data.setValidated(BuiltInPolicyType.DATE);
|
|
615
|
+
}
|
|
378
616
|
} else {
|
|
379
617
|
now = normalizeDate$1(new Date());
|
|
380
618
|
}
|
|
381
|
-
if (
|
|
382
|
-
const start = normalizeDate$1(toDate$1(
|
|
619
|
+
if (policy.start) {
|
|
620
|
+
const start = normalizeDate$1(toDate$1(policy.start));
|
|
383
621
|
if (now < start) {
|
|
384
|
-
return
|
|
622
|
+
return {
|
|
623
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
624
|
+
};
|
|
385
625
|
}
|
|
386
626
|
}
|
|
387
|
-
if (
|
|
388
|
-
const end = normalizeDate$1(toDate$1(
|
|
627
|
+
if (policy.end) {
|
|
628
|
+
const end = normalizeDate$1(toDate$1(policy.end));
|
|
389
629
|
if (now > end) {
|
|
390
|
-
return
|
|
630
|
+
return {
|
|
631
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
632
|
+
};
|
|
391
633
|
}
|
|
392
634
|
}
|
|
393
|
-
return
|
|
635
|
+
return {
|
|
636
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
637
|
+
};
|
|
394
638
|
}
|
|
395
|
-
|
|
396
|
-
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
class PolicyIdentityDataValidator extends Container {
|
|
642
|
+
initialize() {
|
|
643
|
+
super.initialize();
|
|
644
|
+
this.mount('type', createValidator(z.string().min(3).max(128)));
|
|
645
|
+
this.mount('id', createValidator(z.uuid()));
|
|
646
|
+
this.mount('clientId', createValidator(z.uuid().or(z.null()).or(z.undefined()).optional()));
|
|
647
|
+
this.mount('realmId', createValidator(z.uuid().or(z.null()).or(z.undefined()).optional()));
|
|
648
|
+
this.mount('realmName', createValidator(z.string().min(3).max(128).or(z.null()).or(z.undefined()).optional()));
|
|
397
649
|
}
|
|
398
650
|
}
|
|
399
651
|
|
|
@@ -405,28 +657,61 @@ class IdentityPolicyValidator extends Container {
|
|
|
405
657
|
}
|
|
406
658
|
|
|
407
659
|
class IdentityPolicyEvaluator {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
async validateInput(ctx) {
|
|
415
|
-
return ctx.input;
|
|
660
|
+
validator;
|
|
661
|
+
dataValidator;
|
|
662
|
+
constructor(){
|
|
663
|
+
this.validator = new IdentityPolicyValidator();
|
|
664
|
+
this.dataValidator = new PolicyIdentityDataValidator();
|
|
416
665
|
}
|
|
417
|
-
async
|
|
418
|
-
if (!
|
|
419
|
-
return
|
|
666
|
+
async accessData(ctx) {
|
|
667
|
+
if (!ctx.data.has('identity')) {
|
|
668
|
+
return null;
|
|
420
669
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return maybeInvertPolicyOutcome(true, ctx.config.invert);
|
|
670
|
+
if (ctx.data.isValidated('identity')) {
|
|
671
|
+
return ctx.data.get('identity');
|
|
424
672
|
}
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
673
|
+
const data = await this.dataValidator.run(ctx.data.get('identity'));
|
|
674
|
+
ctx.data.set('identity', data);
|
|
675
|
+
ctx.data.setValidated('identity');
|
|
676
|
+
return data;
|
|
677
|
+
}
|
|
678
|
+
async evaluate(value, ctx) {
|
|
679
|
+
// todo: catch errors + transform to issue(s)
|
|
680
|
+
const policy = await this.validator.run(value);
|
|
681
|
+
const data = await this.accessData(ctx);
|
|
682
|
+
if (!data) {
|
|
683
|
+
return {
|
|
684
|
+
success: false,
|
|
685
|
+
issues: [
|
|
686
|
+
definePolicyIssueItem({
|
|
687
|
+
code: PolicyIssueCode.DATA_MISSING,
|
|
688
|
+
message: 'The data property identity is missing',
|
|
689
|
+
path: ctx.path
|
|
690
|
+
})
|
|
691
|
+
]
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (!policy.types || policy.types.length === 0) {
|
|
695
|
+
return {
|
|
696
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const typeAllowed = policy.types.indexOf(data.type) !== -1;
|
|
700
|
+
if (typeAllowed) {
|
|
701
|
+
return {
|
|
702
|
+
success: maybeInvertPolicyOutcome(typeAllowed, policy.invert)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
success: maybeInvertPolicyOutcome(typeAllowed, policy.invert),
|
|
707
|
+
issues: [
|
|
708
|
+
definePolicyIssueItem({
|
|
709
|
+
code: PolicyIssueCode.EVALUATION_DENIED,
|
|
710
|
+
message: `The type ${data.type} is not covered by the policy configuration`,
|
|
711
|
+
path: ctx.path
|
|
712
|
+
})
|
|
713
|
+
]
|
|
714
|
+
};
|
|
430
715
|
}
|
|
431
716
|
}
|
|
432
717
|
|
|
@@ -435,31 +720,23 @@ class PermissionBindingPolicyValidator extends Container {
|
|
|
435
720
|
}
|
|
436
721
|
|
|
437
722
|
class PermissionBindingPolicyEvaluator {
|
|
438
|
-
|
|
439
|
-
return ctx.config.type === BuiltInPolicyType.PERMISSION_BINDING;
|
|
440
|
-
}
|
|
441
|
-
async validateConfig(ctx) {
|
|
442
|
-
return this.validator.run(ctx.config);
|
|
443
|
-
}
|
|
444
|
-
async validateInput(ctx) {
|
|
445
|
-
if (!isObject(ctx.input.identity) && !isObject(ctx.input.permission)) {
|
|
446
|
-
throw PolicyError.evaluatorContextInvalid();
|
|
447
|
-
}
|
|
448
|
-
return ctx.input;
|
|
449
|
-
}
|
|
450
|
-
async evaluate(ctx) {
|
|
451
|
-
// todo: this must be changed when the permission-checker not only checks owned permissions.
|
|
452
|
-
return maybeInvertPolicyOutcome(true, ctx.config.invert);
|
|
453
|
-
}
|
|
723
|
+
validator;
|
|
454
724
|
constructor(){
|
|
455
725
|
this.validator = new PermissionBindingPolicyValidator();
|
|
456
726
|
}
|
|
727
|
+
async evaluate(value) {
|
|
728
|
+
const policy = await this.validator.run(value);
|
|
729
|
+
// todo: this must be changed when the permission-checker not only checks owned permissions.
|
|
730
|
+
return {
|
|
731
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
732
|
+
};
|
|
733
|
+
}
|
|
457
734
|
}
|
|
458
735
|
|
|
459
736
|
class RealmMatchPolicyValidator extends Container {
|
|
460
737
|
initialize() {
|
|
461
738
|
super.initialize();
|
|
462
|
-
this.mount('decisionStrategy', createValidator(z.
|
|
739
|
+
this.mount('decisionStrategy', createValidator(z.enum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
|
|
463
740
|
this.mount('attributeName', createValidator(z.string().min(3).or(z.array(z.string().min(3))).or(z.null()).or(z.undefined()).optional()));
|
|
464
741
|
this.mount('attributeNameStrict', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
465
742
|
this.mount('attributeNullMatchAll', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
|
|
@@ -469,33 +746,56 @@ class RealmMatchPolicyValidator extends Container {
|
|
|
469
746
|
}
|
|
470
747
|
|
|
471
748
|
class RealmMatchPolicyEvaluator {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
749
|
+
validator;
|
|
750
|
+
identityEvaluator;
|
|
751
|
+
attributesEvaluator;
|
|
752
|
+
constructor(){
|
|
753
|
+
this.validator = new RealmMatchPolicyValidator();
|
|
754
|
+
this.identityEvaluator = new IdentityPolicyEvaluator();
|
|
755
|
+
this.attributesEvaluator = new AttributesPolicyEvaluator();
|
|
756
|
+
}
|
|
757
|
+
async evaluate(value, ctx) {
|
|
758
|
+
// todo: catch errors + transform to issue(s)
|
|
759
|
+
const policy = await this.validator.run(value);
|
|
760
|
+
const identity = await this.identityEvaluator.accessData(ctx);
|
|
761
|
+
if (!identity) {
|
|
762
|
+
return {
|
|
763
|
+
success: false,
|
|
764
|
+
issues: [
|
|
765
|
+
definePolicyIssueItem({
|
|
766
|
+
code: PolicyIssueCode.DATA_MISSING,
|
|
767
|
+
message: 'The data property identity is missing',
|
|
768
|
+
path: ctx.path
|
|
769
|
+
})
|
|
770
|
+
]
|
|
771
|
+
};
|
|
481
772
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
773
|
+
const attributes = await this.attributesEvaluator.accessData(ctx);
|
|
774
|
+
if (!attributes) {
|
|
775
|
+
return {
|
|
776
|
+
success: false,
|
|
777
|
+
issues: [
|
|
778
|
+
definePolicyIssueItem({
|
|
779
|
+
code: PolicyIssueCode.DATA_MISSING,
|
|
780
|
+
message: 'The data property attributes is missing',
|
|
781
|
+
path: ctx.path
|
|
782
|
+
})
|
|
783
|
+
]
|
|
784
|
+
};
|
|
487
785
|
}
|
|
488
|
-
const identityMasterMatchAll =
|
|
489
|
-
if (identityMasterMatchAll &&
|
|
490
|
-
return
|
|
786
|
+
const identityMasterMatchAll = policy.identityMasterMatchAll ?? true;
|
|
787
|
+
if (identityMasterMatchAll && identity.realmName && identity.realmName === 'master') {
|
|
788
|
+
return {
|
|
789
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
790
|
+
};
|
|
491
791
|
}
|
|
492
792
|
let keys;
|
|
493
|
-
if (
|
|
494
|
-
if (Array.isArray(
|
|
495
|
-
keys =
|
|
793
|
+
if (policy.attributeName) {
|
|
794
|
+
if (Array.isArray(policy.attributeName)) {
|
|
795
|
+
keys = policy.attributeName;
|
|
496
796
|
} else {
|
|
497
797
|
keys = [
|
|
498
|
-
|
|
798
|
+
policy.attributeName
|
|
499
799
|
];
|
|
500
800
|
}
|
|
501
801
|
} else {
|
|
@@ -505,11 +805,11 @@ class RealmMatchPolicyEvaluator {
|
|
|
505
805
|
'realmId',
|
|
506
806
|
'realmName'
|
|
507
807
|
];
|
|
508
|
-
|
|
808
|
+
policy.decisionStrategy = DecisionStrategy.CONSENSUS;
|
|
509
809
|
}
|
|
510
|
-
const attributeNameStrict =
|
|
810
|
+
const attributeNameStrict = policy.attributeNameStrict ?? true;
|
|
511
811
|
if (!attributeNameStrict) {
|
|
512
|
-
const resourceKeys = Object.keys(
|
|
812
|
+
const resourceKeys = Object.keys(attributes);
|
|
513
813
|
const keysToAdd = [];
|
|
514
814
|
for(let i = 0; i < resourceKeys.length; i++){
|
|
515
815
|
let contains = false;
|
|
@@ -529,32 +829,35 @@ class RealmMatchPolicyEvaluator {
|
|
|
529
829
|
}
|
|
530
830
|
let count = 0;
|
|
531
831
|
for(let i = 0; i < keys.length; i++){
|
|
532
|
-
if (!hasOwnProperty(
|
|
832
|
+
if (!hasOwnProperty(attributes, keys[i])) {
|
|
533
833
|
continue;
|
|
534
834
|
}
|
|
535
835
|
let outcome = false;
|
|
536
|
-
const attributeValue =
|
|
537
|
-
if (attributeValue === null &&
|
|
836
|
+
const attributeValue = attributes[keys[i]];
|
|
837
|
+
if (attributeValue === null && policy.attributeNullMatchAll) {
|
|
538
838
|
outcome = true;
|
|
539
|
-
} else if (attributeValue ===
|
|
839
|
+
} else if (attributeValue === identity.realmId || attributeValue === identity.realmName) {
|
|
540
840
|
outcome = true;
|
|
541
841
|
}
|
|
542
842
|
if (outcome) {
|
|
543
|
-
if (
|
|
544
|
-
return
|
|
843
|
+
if (policy.decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
|
|
844
|
+
return {
|
|
845
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
846
|
+
};
|
|
545
847
|
}
|
|
546
848
|
count++;
|
|
547
849
|
} else {
|
|
548
|
-
if (
|
|
549
|
-
return
|
|
850
|
+
if (policy.decisionStrategy === DecisionStrategy.UNANIMOUS) {
|
|
851
|
+
return {
|
|
852
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
853
|
+
};
|
|
550
854
|
}
|
|
551
855
|
count--;
|
|
552
856
|
}
|
|
553
857
|
}
|
|
554
|
-
return
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
this.validator = new RealmMatchPolicyValidator();
|
|
858
|
+
return {
|
|
859
|
+
success: maybeInvertPolicyOutcome(count > 0, policy.invert)
|
|
860
|
+
};
|
|
558
861
|
}
|
|
559
862
|
}
|
|
560
863
|
|
|
@@ -586,10 +889,10 @@ class TimePolicyValidator extends Container {
|
|
|
586
889
|
super.initialize();
|
|
587
890
|
this.mount('dayOfWeek', createValidator(z.number().min(0).max(6).or(z.null()).or(z.undefined()).optional()));
|
|
588
891
|
this.mount('dayOfMonth', createValidator(z.number().min(1).max(31).or(z.null()).or(z.undefined()).optional()));
|
|
589
|
-
this.mount('dayOfYear', createValidator(z.number().min(1).max(
|
|
590
|
-
this.mount('start', createValidator(z.date().or(z.
|
|
591
|
-
this.mount('end', createValidator(z.date().or(z.
|
|
592
|
-
this.mount('interval', createValidator(z.
|
|
892
|
+
this.mount('dayOfYear', createValidator(z.number().min(1).max(365).or(z.null()).or(z.undefined()).optional()));
|
|
893
|
+
this.mount('start', createValidator(z.date().or(z.iso.datetime()).or(z.iso.time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
|
|
894
|
+
this.mount('end', createValidator(z.date().or(z.iso.datetime()).or(z.iso.time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
|
|
895
|
+
this.mount('interval', createValidator(z.enum(TimePolicyInterval).or(z.null()).or(z.undefined()).optional()));
|
|
593
896
|
this.mount('invert', createValidator(z.boolean().or(z.undefined()).or(z.null()).optional()));
|
|
594
897
|
}
|
|
595
898
|
}
|
|
@@ -616,59 +919,72 @@ function toDate(input, dateRef) {
|
|
|
616
919
|
return input;
|
|
617
920
|
}
|
|
618
921
|
class TimePolicyEvaluator {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
async validateConfig(ctx) {
|
|
623
|
-
return this.validator.run(ctx.config);
|
|
624
|
-
}
|
|
625
|
-
async validateInput(ctx) {
|
|
626
|
-
return ctx.input;
|
|
922
|
+
validator;
|
|
923
|
+
constructor(){
|
|
924
|
+
this.validator = new TimePolicyValidator();
|
|
627
925
|
}
|
|
628
|
-
async evaluate(ctx) {
|
|
926
|
+
async evaluate(value, ctx) {
|
|
927
|
+
// todo: catch errors + transform to issue(s)
|
|
928
|
+
const policy = await this.validator.run(value);
|
|
629
929
|
let now;
|
|
630
|
-
if (ctx.
|
|
631
|
-
|
|
930
|
+
if (ctx.data.has(BuiltInPolicyType.TIME)) {
|
|
931
|
+
if (ctx.data.isValidated(BuiltInPolicyType.TIME)) {
|
|
932
|
+
now = ctx.data.get(BuiltInPolicyType.TIME);
|
|
933
|
+
} else {
|
|
934
|
+
// todo: run validator on attributes (isObject ...)
|
|
935
|
+
now = toDate(ctx.data.get(BuiltInPolicyType.TIME));
|
|
936
|
+
ctx.data.set(BuiltInPolicyType.TIME, now);
|
|
937
|
+
ctx.data.setValidated(BuiltInPolicyType.TIME);
|
|
938
|
+
}
|
|
632
939
|
} else {
|
|
633
940
|
now = new Date();
|
|
634
941
|
}
|
|
635
|
-
if (
|
|
636
|
-
const start = normalizeDate(toDate(
|
|
942
|
+
if (policy.start) {
|
|
943
|
+
const start = normalizeDate(toDate(policy.start, now), now);
|
|
637
944
|
if (now < start) {
|
|
638
|
-
return
|
|
945
|
+
return {
|
|
946
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
947
|
+
};
|
|
639
948
|
}
|
|
640
949
|
}
|
|
641
|
-
if (
|
|
642
|
-
const end = normalizeDate(toDate(
|
|
950
|
+
if (policy.end) {
|
|
951
|
+
const end = normalizeDate(toDate(policy.end, now), now);
|
|
643
952
|
if (now > end) {
|
|
644
|
-
return
|
|
953
|
+
return {
|
|
954
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
955
|
+
};
|
|
645
956
|
}
|
|
646
957
|
}
|
|
647
|
-
if (
|
|
648
|
-
if (isIntervalForDayOfWeek(
|
|
649
|
-
if (now.getDay() !==
|
|
650
|
-
return
|
|
958
|
+
if (policy.interval) {
|
|
959
|
+
if (isIntervalForDayOfWeek(policy.interval) && policy.dayOfWeek) {
|
|
960
|
+
if (now.getDay() !== policy.dayOfWeek) {
|
|
961
|
+
return {
|
|
962
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
963
|
+
};
|
|
651
964
|
}
|
|
652
965
|
}
|
|
653
|
-
if (isIntervalForDayOfMonth(
|
|
654
|
-
if (now.getDate() !==
|
|
655
|
-
return
|
|
966
|
+
if (isIntervalForDayOfMonth(policy.interval) && policy.dayOfMonth) {
|
|
967
|
+
if (now.getDate() !== policy.dayOfMonth) {
|
|
968
|
+
return {
|
|
969
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
970
|
+
};
|
|
656
971
|
}
|
|
657
972
|
}
|
|
658
|
-
if (isIntervalForDayOfYear(
|
|
973
|
+
if (isIntervalForDayOfYear(policy.interval) && policy.dayOfYear) {
|
|
659
974
|
const start = new Date(now.getFullYear(), 0, 0);
|
|
660
975
|
const diff = now.getTime() - start.getTime() + (start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000;
|
|
661
976
|
const oneDay = 1000 * 60 * 60 * 24;
|
|
662
977
|
const dayOfYear = Math.floor(diff / oneDay);
|
|
663
|
-
if (dayOfYear !==
|
|
664
|
-
return
|
|
978
|
+
if (dayOfYear !== policy.dayOfYear) {
|
|
979
|
+
return {
|
|
980
|
+
success: maybeInvertPolicyOutcome(false, policy.invert)
|
|
981
|
+
};
|
|
665
982
|
}
|
|
666
983
|
}
|
|
667
984
|
}
|
|
668
|
-
return
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
this.validator = new TimePolicyValidator();
|
|
985
|
+
return {
|
|
986
|
+
success: maybeInvertPolicyOutcome(true, policy.invert)
|
|
987
|
+
};
|
|
672
988
|
}
|
|
673
989
|
}
|
|
674
990
|
|
|
@@ -684,117 +1000,16 @@ class TimePolicyEvaluator {
|
|
|
684
1000
|
};
|
|
685
1001
|
}
|
|
686
1002
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
this.registerEvaluator(keys[i], evaluators[keys[i]]);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
registerDefaultEvaluators() {
|
|
701
|
-
this.registerEvaluator(BuiltInPolicyType.COMPOSITE, new CompositePolicyEvaluator());
|
|
702
|
-
this.registerEvaluator(BuiltInPolicyType.ATTRIBUTES, new AttributesPolicyEvaluator());
|
|
703
|
-
this.registerEvaluator(BuiltInPolicyType.ATTRIBUTE_NAMES, new AttributeNamesPolicyEvaluator());
|
|
704
|
-
this.registerEvaluator(BuiltInPolicyType.DATE, new DatePolicyEvaluator());
|
|
705
|
-
this.registerEvaluator(BuiltInPolicyType.IDENTITY, new IdentityPolicyEvaluator());
|
|
706
|
-
this.registerEvaluator(BuiltInPolicyType.PERMISSION_BINDING, new PermissionBindingPolicyEvaluator());
|
|
707
|
-
this.registerEvaluator(BuiltInPolicyType.REALM_MATCH, new RealmMatchPolicyEvaluator());
|
|
708
|
-
this.registerEvaluator(BuiltInPolicyType.TIME, new TimePolicyEvaluator());
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* @throws PolicyError
|
|
712
|
-
*
|
|
713
|
-
* @param ctx
|
|
714
|
-
*/ async evaluate(ctx) {
|
|
715
|
-
return evaluatePolicy({
|
|
716
|
-
...ctx,
|
|
717
|
-
options: ctx.options || {},
|
|
718
|
-
evaluators: this.evaluators
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
constructor(evaluators = {}){
|
|
722
|
-
this.evaluators = {};
|
|
723
|
-
this.registerDefaultEvaluators();
|
|
724
|
-
this.registerEvaluators(evaluators);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
class PermissionError extends AuthupError {
|
|
729
|
-
static notFound(name) {
|
|
730
|
-
return new PermissionError({
|
|
731
|
-
message: `The permission ${name} was not found.`,
|
|
732
|
-
code: ErrorCode.PERMISSION_NOT_FOUND
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
static denied(name) {
|
|
736
|
-
return new PermissionError({
|
|
737
|
-
message: `The permission ${name} has not been granted.`,
|
|
738
|
-
code: ErrorCode.PERMISSION_DENIED
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
static deniedAll(names) {
|
|
742
|
-
return new PermissionError({
|
|
743
|
-
message: `None of the permissions ${names.join(', ')} has been granted.`,
|
|
744
|
-
code: ErrorCode.PERMISSION_DENIED
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
static evaluationFailed(options) {
|
|
748
|
-
const error = new PermissionError({
|
|
749
|
-
message: `The evaluation of permission ${options.name} failed.`,
|
|
750
|
-
code: ErrorCode.PERMISSION_EVALUATION_FAILED
|
|
751
|
-
});
|
|
752
|
-
if (options.policy) {
|
|
753
|
-
error.policy = options.policy;
|
|
754
|
-
if (options.policy.type === BuiltInPolicyType.IDENTITY) {
|
|
755
|
-
error.statusCode = 401;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
if (options.policyError) {
|
|
759
|
-
error.policyError = options.policyError;
|
|
760
|
-
}
|
|
761
|
-
return error;
|
|
762
|
-
}
|
|
763
|
-
constructor(options = {}){
|
|
764
|
-
super({
|
|
765
|
-
...options,
|
|
766
|
-
message: options.message || 'A permission error occurred.',
|
|
767
|
-
statusCode: options.statusCode || 403
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/*
|
|
773
|
-
* Copyright (c) 2025.
|
|
774
|
-
* Author Peter Placzek (tada5hi)
|
|
775
|
-
* For the full copyright and license information,
|
|
776
|
-
* view the LICENSE file that was distributed with this source code.
|
|
777
|
-
*/ function buildPermissionItemKey(input) {
|
|
778
|
-
return `${input.realmId || '_'}/${input.clientId || '_'}/${input.name}`;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/*
|
|
782
|
-
* Copyright (c) 2024-2024.
|
|
783
|
-
* Author Peter Placzek (tada5hi)
|
|
784
|
-
* For the full copyright and license information,
|
|
785
|
-
* view the LICENSE file that was distributed with this source code.
|
|
786
|
-
*/ function isPermissionItemEqual(a, b) {
|
|
787
|
-
if (a.name !== b.name) {
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
if (typeof a.realmId === 'string' || typeof b.realmId === 'string') {
|
|
791
|
-
return a.realmId === b.realmId;
|
|
792
|
-
}
|
|
793
|
-
if (typeof a.clientId === 'string' || typeof b.clientId === 'string') {
|
|
794
|
-
return a.clientId === b.clientId;
|
|
795
|
-
}
|
|
796
|
-
return !!a.realmId === !!b.realmId && !!a.clientId === !!b.clientId;
|
|
797
|
-
}
|
|
1003
|
+
const PolicyDefaultEvaluators = {
|
|
1004
|
+
[BuiltInPolicyType.COMPOSITE]: new CompositePolicyEvaluator(),
|
|
1005
|
+
[BuiltInPolicyType.ATTRIBUTES]: new AttributesPolicyEvaluator(),
|
|
1006
|
+
[BuiltInPolicyType.ATTRIBUTE_NAMES]: new AttributeNamesPolicyEvaluator(),
|
|
1007
|
+
[BuiltInPolicyType.DATE]: new DatePolicyEvaluator(),
|
|
1008
|
+
[BuiltInPolicyType.IDENTITY]: new IdentityPolicyEvaluator(),
|
|
1009
|
+
[BuiltInPolicyType.PERMISSION_BINDING]: new PermissionBindingPolicyEvaluator(),
|
|
1010
|
+
[BuiltInPolicyType.REALM_MATCH]: new RealmMatchPolicyEvaluator(),
|
|
1011
|
+
[BuiltInPolicyType.TIME]: new TimePolicyEvaluator()
|
|
1012
|
+
};
|
|
798
1013
|
|
|
799
1014
|
function mergePermissionItems(input) {
|
|
800
1015
|
const grouped = input.reduce((previous, current)=>{
|
|
@@ -827,9 +1042,17 @@ function mergePermissionItems(input) {
|
|
|
827
1042
|
return output;
|
|
828
1043
|
}
|
|
829
1044
|
|
|
830
|
-
class
|
|
831
|
-
|
|
832
|
-
|
|
1045
|
+
class PermissionMemoryRepository {
|
|
1046
|
+
items = {};
|
|
1047
|
+
constructor(items = []){
|
|
1048
|
+
this.setMany(items);
|
|
1049
|
+
}
|
|
1050
|
+
async findOne(options) {
|
|
1051
|
+
const entry = this.items[buildPermissionItemKey(options)];
|
|
1052
|
+
if (entry) {
|
|
1053
|
+
return entry;
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
833
1056
|
}
|
|
834
1057
|
setMany(input) {
|
|
835
1058
|
this.items = mergePermissionItems(input).reduce((prev, current)=>{
|
|
@@ -837,19 +1060,38 @@ class PermissionMemoryProvider {
|
|
|
837
1060
|
return prev;
|
|
838
1061
|
}, {});
|
|
839
1062
|
}
|
|
840
|
-
constructor(items = []){
|
|
841
|
-
this.items = {};
|
|
842
|
-
this.setMany(items);
|
|
843
|
-
}
|
|
844
1063
|
}
|
|
845
1064
|
|
|
846
1065
|
class PermissionChecker {
|
|
1066
|
+
provider;
|
|
1067
|
+
policyEngine;
|
|
1068
|
+
clientId;
|
|
1069
|
+
realmId;
|
|
1070
|
+
// ----------------------------------------------
|
|
1071
|
+
constructor(options = {}){
|
|
1072
|
+
if (options.repository) {
|
|
1073
|
+
this.provider = options.repository;
|
|
1074
|
+
} else {
|
|
1075
|
+
this.provider = new PermissionMemoryRepository();
|
|
1076
|
+
}
|
|
1077
|
+
if (options.clientId) {
|
|
1078
|
+
this.clientId = options.clientId;
|
|
1079
|
+
}
|
|
1080
|
+
if (options.realmId) {
|
|
1081
|
+
this.realmId = options.realmId;
|
|
1082
|
+
}
|
|
1083
|
+
if (options.policyEngine) {
|
|
1084
|
+
this.policyEngine = options.policyEngine;
|
|
1085
|
+
} else {
|
|
1086
|
+
this.policyEngine = new PolicyEngine(PolicyDefaultEvaluators);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
847
1089
|
// ----------------------------------------------
|
|
848
1090
|
/**
|
|
849
1091
|
* Get a permission.
|
|
850
1092
|
*
|
|
851
1093
|
* @param input
|
|
852
|
-
*/ async
|
|
1094
|
+
*/ async findOne(input) {
|
|
853
1095
|
const options = {
|
|
854
1096
|
name: input
|
|
855
1097
|
};
|
|
@@ -859,7 +1101,7 @@ class PermissionChecker {
|
|
|
859
1101
|
if (this.realmId) {
|
|
860
1102
|
options.realmId = this.realmId;
|
|
861
1103
|
}
|
|
862
|
-
return this.provider.
|
|
1104
|
+
return this.provider.findOne(options);
|
|
863
1105
|
}
|
|
864
1106
|
// ----------------------------------------------
|
|
865
1107
|
/**
|
|
@@ -880,14 +1122,23 @@ class PermissionChecker {
|
|
|
880
1122
|
}
|
|
881
1123
|
const { options = {} } = ctx;
|
|
882
1124
|
const decisionStrategy = options.decisionStrategy ?? DecisionStrategy.UNANIMOUS;
|
|
883
|
-
|
|
1125
|
+
const issues = [];
|
|
884
1126
|
let count = 0;
|
|
1127
|
+
const dataBase = ctx.input || new PolicyData();
|
|
885
1128
|
for(let i = 0; i < ctx.name.length; i++){
|
|
886
|
-
const entity = await this.
|
|
1129
|
+
const entity = await this.findOne(ctx.name[i]);
|
|
887
1130
|
if (!entity) {
|
|
888
|
-
|
|
1131
|
+
issues.push(defineIssueItem({
|
|
1132
|
+
code: ErrorCode.PERMISSION_NOT_FOUND,
|
|
1133
|
+
message: `The ${ctx.name[i]} permission could not be resolved`,
|
|
1134
|
+
path: [
|
|
1135
|
+
ctx.name[i]
|
|
1136
|
+
]
|
|
1137
|
+
}));
|
|
889
1138
|
if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
|
|
890
|
-
|
|
1139
|
+
const error = PermissionError.evaluationFailed(ctx.name);
|
|
1140
|
+
error.addIssues(issues);
|
|
1141
|
+
throw error;
|
|
891
1142
|
}
|
|
892
1143
|
continue;
|
|
893
1144
|
}
|
|
@@ -898,48 +1149,31 @@ class PermissionChecker {
|
|
|
898
1149
|
count++;
|
|
899
1150
|
continue;
|
|
900
1151
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
options: {
|
|
910
|
-
include: options.policiesIncluded,
|
|
911
|
-
exclude: options.policiesExcluded
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
} catch (e) {
|
|
915
|
-
if (e instanceof PolicyError) {
|
|
916
|
-
lastError = PermissionError.evaluationFailed({
|
|
917
|
-
name: entity.name,
|
|
918
|
-
policy: entity.policy,
|
|
919
|
-
policyError: e
|
|
920
|
-
});
|
|
921
|
-
} else {
|
|
922
|
-
lastError = PermissionError.evaluationFailed({
|
|
923
|
-
name: entity.name,
|
|
924
|
-
policy: entity.policy
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
outcome = false;
|
|
928
|
-
}
|
|
929
|
-
if (outcome) {
|
|
1152
|
+
const data = dataBase.clone();
|
|
1153
|
+
data.set(BuiltInPolicyType.PERMISSION_BINDING, entity);
|
|
1154
|
+
const evaluationResult = await this.policyEngine.evaluate(entity.policy, definePolicyEvaluationContext({
|
|
1155
|
+
include: options.policiesIncluded,
|
|
1156
|
+
exclude: options.policiesExcluded,
|
|
1157
|
+
data
|
|
1158
|
+
}));
|
|
1159
|
+
if (evaluationResult.success) {
|
|
930
1160
|
if (decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
|
|
931
1161
|
return;
|
|
932
1162
|
}
|
|
933
1163
|
count++;
|
|
934
1164
|
} else {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1165
|
+
issues.push(definePolicyIssueGroup({
|
|
1166
|
+
code: ErrorCode.PERMISSION_EVALUATION_FAILED,
|
|
1167
|
+
issues: evaluationResult.issues || [],
|
|
1168
|
+
message: `The ${entity.name} permissions policy evaluation failed`,
|
|
1169
|
+
path: [
|
|
1170
|
+
entity.name
|
|
1171
|
+
]
|
|
1172
|
+
}));
|
|
941
1173
|
if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
|
|
942
|
-
|
|
1174
|
+
const error = PermissionError.evaluationFailed(entity.name);
|
|
1175
|
+
error.addIssues(issues);
|
|
1176
|
+
throw error;
|
|
943
1177
|
}
|
|
944
1178
|
count--;
|
|
945
1179
|
}
|
|
@@ -947,10 +1181,12 @@ class PermissionChecker {
|
|
|
947
1181
|
if (count > 0) {
|
|
948
1182
|
return;
|
|
949
1183
|
}
|
|
950
|
-
if (
|
|
1184
|
+
if (issues.length === 0) {
|
|
951
1185
|
throw PermissionError.deniedAll(ctx.name);
|
|
952
1186
|
} else {
|
|
953
|
-
|
|
1187
|
+
const error = PermissionError.evaluationFailed(ctx.name);
|
|
1188
|
+
error.addIssues(issues);
|
|
1189
|
+
throw error;
|
|
954
1190
|
}
|
|
955
1191
|
}
|
|
956
1192
|
/**
|
|
@@ -991,26 +1227,7 @@ class PermissionChecker {
|
|
|
991
1227
|
}
|
|
992
1228
|
});
|
|
993
1229
|
}
|
|
994
|
-
// ----------------------------------------------
|
|
995
|
-
constructor(options = {}){
|
|
996
|
-
if (options.provider) {
|
|
997
|
-
this.provider = options.provider;
|
|
998
|
-
} else {
|
|
999
|
-
this.provider = new PermissionMemoryProvider();
|
|
1000
|
-
}
|
|
1001
|
-
if (options.clientId) {
|
|
1002
|
-
this.clientId = options.clientId;
|
|
1003
|
-
}
|
|
1004
|
-
if (options.realmId) {
|
|
1005
|
-
this.realmId = options.realmId;
|
|
1006
|
-
}
|
|
1007
|
-
if (options.policyEngine) {
|
|
1008
|
-
this.policyEngine = options.policyEngine;
|
|
1009
|
-
} else {
|
|
1010
|
-
this.policyEngine = new PolicyEngine();
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
1230
|
}
|
|
1014
1231
|
|
|
1015
|
-
export { AttributeNamesPolicyEvaluator, AttributeNamesPolicyValidator, AttributesPolicyEvaluator, AttributesPolicyValidator, BuiltInPolicyType, CompositePolicyEvaluator, CompositePolicyValidator, DatePolicyEvaluator, DatePolicyValidator, DecisionStrategy, IdentityPolicyEvaluator, IdentityPolicyValidator, PermissionBindingPolicyEvaluator, PermissionBindingPolicyValidator, PermissionChecker, PermissionError,
|
|
1232
|
+
export { AttributeNamesPolicyEvaluator, AttributeNamesPolicyValidator, AttributesPolicyEvaluator, AttributesPolicyValidator, BuiltInPolicyType, CompositePolicyEvaluator, CompositePolicyValidator, DatePolicyEvaluator, DatePolicyValidator, DecisionStrategy, IdentityPolicyEvaluator, IdentityPolicyValidator, PermissionBindingPolicyEvaluator, PermissionBindingPolicyValidator, PermissionChecker, PermissionError, PermissionMemoryRepository, PolicyData, PolicyDefaultEvaluators, PolicyEngine, PolicyError, PolicyIssueCode, PolicyIssueSeverity, RealmMatchPolicyEvaluator, RealmMatchPolicyValidator, TimePolicyEvaluator, TimePolicyInterval, TimePolicyValidator, buildPermissionItemKey, defineAttributesPolicy, definePolicyEvaluationContext, definePolicyIssueGroup, definePolicyIssueItem, definePolicyWithType, isCompositePolicy, isIntervalForDayOfMonth, isIntervalForDayOfWeek, isIntervalForDayOfYear, isPermissionItemEqual, maybeInvertPolicyOutcome, mergePermissionItems };
|
|
1016
1233
|
//# sourceMappingURL=index.mjs.map
|