@twin.org/rights-management-plugins 0.0.3-next.13 → 0.0.3-next.14
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/es/index.js +5 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IDefaultPolicyArbiterConfig.js +4 -0
- package/dist/es/models/IDefaultPolicyArbiterConfig.js.map +1 -0
- package/dist/es/models/IDefaultPolicyArbiterConstructorOptions.js +4 -0
- package/dist/es/models/IDefaultPolicyArbiterConstructorOptions.js.map +1 -0
- package/dist/es/models/IPassThroughPolicyObligationEnforcerConstructorOptions.js +4 -0
- package/dist/es/models/IPassThroughPolicyObligationEnforcerConstructorOptions.js.map +1 -0
- package/dist/es/policyArbiters/defaultPolicyArbiter.js +824 -0
- package/dist/es/policyArbiters/defaultPolicyArbiter.js.map +1 -0
- package/dist/es/policyArbiters/passThroughPolicyArbiter.js +5 -4
- package/dist/es/policyArbiters/passThroughPolicyArbiter.js.map +1 -1
- package/dist/es/policyEnforcementProcessor/defaultPolicyEnforcementProcessor.js +5 -4
- package/dist/es/policyEnforcementProcessor/defaultPolicyEnforcementProcessor.js.map +1 -1
- package/dist/es/policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.js +5 -4
- package/dist/es/policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.js.map +1 -1
- package/dist/es/policyExecutionActions/loggingPolicyExecutionAction.js +4 -1
- package/dist/es/policyExecutionActions/loggingPolicyExecutionAction.js.map +1 -1
- package/dist/es/policyInformationSources/identityPolicyInformationSource.js +2 -1
- package/dist/es/policyInformationSources/identityPolicyInformationSource.js.map +1 -1
- package/dist/es/policyInformationSources/staticPolicyInformationSource.js +2 -1
- package/dist/es/policyInformationSources/staticPolicyInformationSource.js.map +1 -1
- package/dist/es/policyObligationEnforcers/passThroughPolicyObligationEnforcer.js +55 -0
- package/dist/es/policyObligationEnforcers/passThroughPolicyObligationEnforcer.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/models/IDefaultPolicyArbiterConfig.d.ts +10 -0
- package/dist/types/models/IDefaultPolicyArbiterConstructorOptions.d.ts +20 -0
- package/dist/types/models/IPassThroughPolicyObligationEnforcerConstructorOptions.d.ts +10 -0
- package/dist/types/policyArbiters/defaultPolicyArbiter.d.ts +34 -0
- package/dist/types/policyArbiters/passThroughPolicyArbiter.d.ts +5 -4
- package/dist/types/policyEnforcementProcessor/defaultPolicyEnforcementProcessor.d.ts +4 -3
- package/dist/types/policyEnforcementProcessor/passThroughPolicyEnforcementProcessor.d.ts +4 -3
- package/dist/types/policyExecutionActions/loggingPolicyExecutionAction.d.ts +3 -2
- package/dist/types/policyInformationSources/identityPolicyInformationSource.d.ts +3 -2
- package/dist/types/policyInformationSources/staticPolicyInformationSource.d.ts +3 -2
- package/dist/types/policyObligationEnforcers/passThroughPolicyObligationEnforcer.d.ts +35 -0
- package/docs/changelog.md +23 -0
- package/docs/reference/classes/DefaultPolicyArbiter.md +101 -0
- package/docs/reference/classes/DefaultPolicyEnforcementProcessor.md +10 -4
- package/docs/reference/classes/IdentityPolicyInformationSource.md +7 -1
- package/docs/reference/classes/LoggingPolicyExecutionAction.md +7 -1
- package/docs/reference/classes/PassThroughPolicyArbiter.md +10 -4
- package/docs/reference/classes/PassThroughPolicyEnforcementProcessor.md +10 -4
- package/docs/reference/classes/PassThroughPolicyObligationEnforcer.md +107 -0
- package/docs/reference/classes/StaticPolicyInformationSource.md +7 -1
- package/docs/reference/index.md +5 -0
- package/docs/reference/interfaces/IDefaultPolicyArbiterConfig.md +17 -0
- package/docs/reference/interfaces/IDefaultPolicyArbiterConstructorOptions.md +39 -0
- package/docs/reference/interfaces/IPassThroughPolicyObligationEnforcerConstructorOptions.md +17 -0
- package/locales/en.json +16 -0
- package/package.json +2 -2
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
// Copyright 2025 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { ArrayHelper, Coerce, ComponentFactory, GeneralError, Guards, Is, ObjectHelper } from "@twin.org/core";
|
|
4
|
+
import { JsonPathHelper } from "@twin.org/data-json-path";
|
|
5
|
+
import { OdrlPolicyHelper, PolicyDecision, PolicyObligationEnforcerFactory } from "@twin.org/rights-management-models";
|
|
6
|
+
import { ConflictStrategyType, LogicalConstraintType, OperatorType } from "@twin.org/standards-w3c-odrl";
|
|
7
|
+
/**
|
|
8
|
+
* Default Policy Arbiter.
|
|
9
|
+
*/
|
|
10
|
+
export class DefaultPolicyArbiter {
|
|
11
|
+
/**
|
|
12
|
+
* The class name of the Default Policy Arbiter.
|
|
13
|
+
*/
|
|
14
|
+
static CLASS_NAME = "DefaultPolicyArbiter";
|
|
15
|
+
/**
|
|
16
|
+
* Type indicating the operand type is JSONPath.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
static _JSON_PATH_TYPE = "jsonpath";
|
|
20
|
+
/**
|
|
21
|
+
* Prefix indicating the operand encodes a JSONPath.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
static _JSON_PATH_OPERAND_PREFIX = `twin:${DefaultPolicyArbiter._JSON_PATH_TYPE}`;
|
|
25
|
+
/**
|
|
26
|
+
* Prefix indicating the permission target references an item in the information map.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
static _INFORMATION_TARGET_PREFIX = "twin:information:";
|
|
30
|
+
/**
|
|
31
|
+
* Default maximum inheritance depth.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
static _DEFAULT_MAX_INHERITANCE_DEPTH = 10;
|
|
35
|
+
/**
|
|
36
|
+
* The logging component.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
_logging;
|
|
40
|
+
/**
|
|
41
|
+
* The policy administration point component.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
_policyAdministrationPoint;
|
|
45
|
+
/**
|
|
46
|
+
* The maximum depth to traverse when resolving inherited policies.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
_maxInheritanceDepth;
|
|
50
|
+
/**
|
|
51
|
+
* Create a new instance of DefaultPolicyArbiter.
|
|
52
|
+
* @param options The options for the default policy arbiter.
|
|
53
|
+
*/
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this._logging = ComponentFactory.get(options?.loggingComponentType ?? "logging");
|
|
56
|
+
this._policyAdministrationPoint = ComponentFactory.get(options?.policyAdministrationPointComponentType ?? "policy-administration-point");
|
|
57
|
+
this._maxInheritanceDepth =
|
|
58
|
+
Coerce.integer(options?.config?.maxInheritanceDepth) ??
|
|
59
|
+
DefaultPolicyArbiter._DEFAULT_MAX_INHERITANCE_DEPTH;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns the class name of the component.
|
|
63
|
+
* @returns The class name of the component.
|
|
64
|
+
*/
|
|
65
|
+
className() {
|
|
66
|
+
return DefaultPolicyArbiter.CLASS_NAME;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Makes decisions regarding policy access to data.
|
|
70
|
+
* @param agreement The agreement to evaluate.
|
|
71
|
+
* @param information Information provided by the requester to determine if a policy can be created.
|
|
72
|
+
* @param data The data to make a decision on.
|
|
73
|
+
* @param action Optional action to make a decision on, if not provided, the arbiter will evaluate all actions in the agreement.
|
|
74
|
+
* @returns The decisions about access to the data.
|
|
75
|
+
*/
|
|
76
|
+
async decide(agreement, information, data, action) {
|
|
77
|
+
Guards.object(DefaultPolicyArbiter.CLASS_NAME, "agreement", agreement);
|
|
78
|
+
await this._logging.log({
|
|
79
|
+
level: "info",
|
|
80
|
+
source: DefaultPolicyArbiter.CLASS_NAME,
|
|
81
|
+
ts: Date.now(),
|
|
82
|
+
message: "decidingPolicy",
|
|
83
|
+
data: {
|
|
84
|
+
policyId: agreement.uid
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// Resolve and merge inherited policies.
|
|
88
|
+
const mergedPolicy = await this.mergeInheritedPolicies(agreement);
|
|
89
|
+
// Extract agreement parties once for use in rule evaluation
|
|
90
|
+
const agreementAssigner = OdrlPolicyHelper.getPartyIds(agreement.assigner);
|
|
91
|
+
const agreementAssignee = OdrlPolicyHelper.getPartyIds(agreement.assignee);
|
|
92
|
+
// ODRL-style rule evaluation:
|
|
93
|
+
// - Permission rules authorize if ANY applicable permission matches (OR across permissions).
|
|
94
|
+
// - Default to denied when there are no permissions (closed-world for access control).
|
|
95
|
+
// - Conflict strategy controls how applicable permissions/prohibitions are resolved.
|
|
96
|
+
const permissions = ArrayHelper.fromObjectOrArray(mergedPolicy.permission ?? []);
|
|
97
|
+
let permissionApplies = false;
|
|
98
|
+
if (permissions.length > 0) {
|
|
99
|
+
for (const permission of permissions) {
|
|
100
|
+
if (await this.evaluatePermission(agreementAssigner, agreementAssignee, mergedPolicy, permission, information, data, action)) {
|
|
101
|
+
permissionApplies = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const prohibitions = ArrayHelper.fromObjectOrArray(mergedPolicy.prohibition ?? []);
|
|
107
|
+
let prohibitionApplies = false;
|
|
108
|
+
for (const prohibition of prohibitions) {
|
|
109
|
+
if (this.evaluateProhibition(agreementAssigner, agreementAssignee, prohibition, data, action)) {
|
|
110
|
+
prohibitionApplies = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const conflictStrategy = mergedPolicy.conflict ?? ConflictStrategyType.Invalid;
|
|
115
|
+
let decision;
|
|
116
|
+
if (permissionApplies && prohibitionApplies) {
|
|
117
|
+
switch (conflictStrategy) {
|
|
118
|
+
case ConflictStrategyType.Perm:
|
|
119
|
+
decision = PolicyDecision.Granted;
|
|
120
|
+
break;
|
|
121
|
+
case ConflictStrategyType.Prohibit:
|
|
122
|
+
case ConflictStrategyType.Invalid:
|
|
123
|
+
default:
|
|
124
|
+
decision = PolicyDecision.Denied;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
decision = permissionApplies ? PolicyDecision.Granted : PolicyDecision.Denied;
|
|
130
|
+
}
|
|
131
|
+
return [{ target: "$", decision }];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Evaluate whether a prohibition applies.
|
|
135
|
+
* @param agreementAssigner The assigner ID from the agreement.
|
|
136
|
+
* @param agreementAssignee The assignee ID from the agreement.
|
|
137
|
+
* @param prohibition The prohibition to evaluate.
|
|
138
|
+
* @param data The request data/context.
|
|
139
|
+
* @param action Optional action to check against the prohibition's applicable actions.
|
|
140
|
+
* @returns True if the prohibition applies.
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
evaluateProhibition(agreementAssigner, agreementAssignee, prohibition, data, action) {
|
|
144
|
+
if (!this.isRuleApplicableToParties(prohibition, agreementAssigner, agreementAssignee)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
// Check if the prohibition's action(s) match the requested action
|
|
148
|
+
if (!this.isActionApplicable(prohibition.action, action)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
// ODRL semantics: a rule without constraints is unconditional.
|
|
152
|
+
const constraints = ArrayHelper.fromObjectOrArray(prohibition.constraint ?? []);
|
|
153
|
+
if (constraints.length === 0) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return constraints.every(c => this.evaluateConstraint(c, data));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Merge inherited policies into the current policy.
|
|
160
|
+
* Inherited policies' permissions, prohibitions, and obligations are combined with the current policy's rules.
|
|
161
|
+
* @param policy The policy to evaluate.
|
|
162
|
+
* @returns A new policy with merged rules from all ancestors.
|
|
163
|
+
* @internal
|
|
164
|
+
*/
|
|
165
|
+
async mergeInheritedPolicies(policy) {
|
|
166
|
+
const visitedPolicyIds = [];
|
|
167
|
+
visitedPolicyIds.push(policy.uid);
|
|
168
|
+
const inheritedPolicies = await this.resolveInheritedPolicies(policy, visitedPolicyIds, 0);
|
|
169
|
+
const conflictStrategies = new Set();
|
|
170
|
+
if (Is.stringValue(policy.conflict)) {
|
|
171
|
+
conflictStrategies.add(policy.conflict);
|
|
172
|
+
}
|
|
173
|
+
// Start with copies of the current policy's rules
|
|
174
|
+
const mergedPermissions = ArrayHelper.fromObjectOrArray(policy.permission ?? []).map(permission => this.applyPolicyPartiesToRule(policy, permission));
|
|
175
|
+
const mergedProhibitions = ArrayHelper.fromObjectOrArray(policy.prohibition ?? []).map(prohibition => this.applyPolicyPartiesToRule(policy, prohibition));
|
|
176
|
+
const mergedObligations = ArrayHelper.fromObjectOrArray(policy.obligation ?? []).map(obligation => this.applyPolicyPartiesToRule(policy, obligation));
|
|
177
|
+
// Merge rules from each inherited policy
|
|
178
|
+
for (const inheritedPolicy of inheritedPolicies) {
|
|
179
|
+
if (Is.stringValue(inheritedPolicy.conflict)) {
|
|
180
|
+
conflictStrategies.add(inheritedPolicy.conflict);
|
|
181
|
+
}
|
|
182
|
+
const inheritedPermissions = ArrayHelper.fromObjectOrArray(inheritedPolicy.permission ?? []);
|
|
183
|
+
if (inheritedPermissions.length > 0) {
|
|
184
|
+
mergedPermissions.push(...inheritedPermissions.map(permission => this.applyPolicyPartiesToRule(inheritedPolicy, permission)));
|
|
185
|
+
}
|
|
186
|
+
const inheritedProhibitions = ArrayHelper.fromObjectOrArray(inheritedPolicy.prohibition ?? []);
|
|
187
|
+
if (inheritedProhibitions.length > 0) {
|
|
188
|
+
mergedProhibitions.push(...inheritedProhibitions.map(prohibition => this.applyPolicyPartiesToRule(inheritedPolicy, prohibition)));
|
|
189
|
+
}
|
|
190
|
+
const inheritedObligations = ArrayHelper.fromObjectOrArray(inheritedPolicy.obligation ?? []);
|
|
191
|
+
if (inheritedObligations.length > 0) {
|
|
192
|
+
mergedObligations.push(...inheritedObligations.map(obligation => this.applyPolicyPartiesToRule(inheritedPolicy, obligation)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let mergedConflict;
|
|
196
|
+
if (conflictStrategies.size === 1) {
|
|
197
|
+
mergedConflict = Array.from(conflictStrategies)[0];
|
|
198
|
+
}
|
|
199
|
+
else if (conflictStrategies.size > 1) {
|
|
200
|
+
mergedConflict = ConflictStrategyType.Invalid;
|
|
201
|
+
}
|
|
202
|
+
// Return a new policy with merged rules
|
|
203
|
+
return {
|
|
204
|
+
...policy,
|
|
205
|
+
conflict: mergedConflict,
|
|
206
|
+
permission: mergedPermissions.length > 0 ? mergedPermissions : undefined,
|
|
207
|
+
prohibition: mergedProhibitions.length > 0 ? mergedProhibitions : undefined,
|
|
208
|
+
obligation: mergedObligations.length > 0 ? mergedObligations : undefined
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Resolve inherited policies by their UIDs from the Policy Administration Point.
|
|
213
|
+
* Policies can inherit from other policies via the inheritFrom property.
|
|
214
|
+
* Detects circular inheritance and throws an exception if detected.
|
|
215
|
+
* @param policy The policy that may have inheritFrom references.
|
|
216
|
+
* @param visitedPolicyIds Array of policy UIDs already visited in this inheritance chain.
|
|
217
|
+
* @returns Array of inherited policies fetched from the PAP.
|
|
218
|
+
* @throws GeneralError if a parent policy cannot be found or if circular inheritance is detected.
|
|
219
|
+
* @internal
|
|
220
|
+
*/
|
|
221
|
+
async resolveInheritedPolicies(policy, visitedPolicyIds, currentDepth) {
|
|
222
|
+
const inheritedPolicies = [];
|
|
223
|
+
// If policy has no inheritFrom, return empty array
|
|
224
|
+
if (Is.empty(policy.inheritFrom)) {
|
|
225
|
+
return inheritedPolicies;
|
|
226
|
+
}
|
|
227
|
+
const inheritFromIds = ArrayHelper.fromObjectOrArray(policy.inheritFrom) ?? [];
|
|
228
|
+
for (const inheritFromId of inheritFromIds) {
|
|
229
|
+
const nextDepth = currentDepth + 1;
|
|
230
|
+
if (nextDepth > this._maxInheritanceDepth) {
|
|
231
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "maxInheritanceDepthExceeded", {
|
|
232
|
+
policyId: policy.uid,
|
|
233
|
+
inheritFromId,
|
|
234
|
+
maxInheritanceDepth: this._maxInheritanceDepth
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// Check for circular inheritance
|
|
238
|
+
if (visitedPolicyIds.includes(inheritFromId)) {
|
|
239
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "circularInheritanceDetected", {
|
|
240
|
+
policyId: policy.uid,
|
|
241
|
+
inheritFromId
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Fetch the inherited policy from the PAP
|
|
245
|
+
const inheritedPolicy = await this._policyAdministrationPoint.get(inheritFromId);
|
|
246
|
+
if (Is.empty(inheritedPolicy)) {
|
|
247
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "inheritedPolicyNotFound", {
|
|
248
|
+
inheritFromId
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// Mark this policy as visited
|
|
252
|
+
visitedPolicyIds.push(inheritFromId);
|
|
253
|
+
inheritedPolicies.push(inheritedPolicy);
|
|
254
|
+
// Recursively resolve inherited policies of the parent
|
|
255
|
+
const grandparentPolicies = await this.resolveInheritedPolicies(inheritedPolicy, visitedPolicyIds, nextDepth);
|
|
256
|
+
inheritedPolicies.push(...grandparentPolicies);
|
|
257
|
+
}
|
|
258
|
+
return inheritedPolicies;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Apply policy-level assigner/assignee values to rules that don't override them.
|
|
262
|
+
* @param policy The policy providing defaults.
|
|
263
|
+
* @param rule The rule to apply defaults to.
|
|
264
|
+
* @returns The rule with policy-level assigner/assignee applied.
|
|
265
|
+
* @internal
|
|
266
|
+
*/
|
|
267
|
+
applyPolicyPartiesToRule(policy, rule) {
|
|
268
|
+
const assigner = Is.empty(rule.assigner) ? policy.assigner : rule.assigner;
|
|
269
|
+
const assignee = Is.empty(rule.assignee) ? policy.assignee : rule.assignee;
|
|
270
|
+
const assignerIds = OdrlPolicyHelper.getPartyIds(assigner);
|
|
271
|
+
const ruleAssignerIds = OdrlPolicyHelper.getPartyIds(rule.assigner);
|
|
272
|
+
const assigneeIds = OdrlPolicyHelper.getPartyIds(assignee);
|
|
273
|
+
const ruleAssigneeIds = OdrlPolicyHelper.getPartyIds(rule.assignee);
|
|
274
|
+
let assignerEqual = false;
|
|
275
|
+
if (Is.empty(assignerIds) && Is.empty(ruleAssignerIds)) {
|
|
276
|
+
assignerEqual = true;
|
|
277
|
+
}
|
|
278
|
+
else if (!Is.empty(assignerIds) && !Is.empty(ruleAssignerIds)) {
|
|
279
|
+
assignerEqual =
|
|
280
|
+
assignerIds.length === ruleAssignerIds.length &&
|
|
281
|
+
assignerIds.every(id => ruleAssignerIds.includes(id));
|
|
282
|
+
}
|
|
283
|
+
let assigneeEqual = false;
|
|
284
|
+
if (Is.empty(assigneeIds) && Is.empty(ruleAssigneeIds)) {
|
|
285
|
+
assigneeEqual = true;
|
|
286
|
+
}
|
|
287
|
+
else if (!Is.empty(assigneeIds) && !Is.empty(ruleAssigneeIds)) {
|
|
288
|
+
assigneeEqual =
|
|
289
|
+
assigneeIds.length === ruleAssigneeIds.length &&
|
|
290
|
+
assigneeIds.every(id => ruleAssigneeIds.includes(id));
|
|
291
|
+
}
|
|
292
|
+
if (assignerEqual && assigneeEqual) {
|
|
293
|
+
return rule;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
...rule,
|
|
297
|
+
assigner,
|
|
298
|
+
assignee
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Determine whether a rule applies to the agreement parties based on assigner/assignee.
|
|
303
|
+
* @param rule The rule to evaluate.
|
|
304
|
+
* @param agreementAssigner The assigner ID from the agreement.
|
|
305
|
+
* @param agreementAssignee The assignee ID from the agreement.
|
|
306
|
+
* @returns True if the rule is applicable to the agreement parties.
|
|
307
|
+
* @internal
|
|
308
|
+
*/
|
|
309
|
+
isRuleApplicableToParties(rule, agreementAssigner, agreementAssignee) {
|
|
310
|
+
const ruleAssigners = OdrlPolicyHelper.getPartyIds(rule.assigner);
|
|
311
|
+
const ruleAssignees = OdrlPolicyHelper.getPartyIds(rule.assignee);
|
|
312
|
+
if (!this.isPartyApplicable(ruleAssigners, agreementAssigner)) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (!this.isPartyApplicable(ruleAssignees, agreementAssignee)) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Determine whether a rule party constraint applies to the agreement parties.
|
|
322
|
+
* Rule party is treated as a constraint: if specified, it must match at least one agreement party.
|
|
323
|
+
* @param rulePartyIds The party ids specified on the rule (if any).
|
|
324
|
+
* @param agreementPartyIds The party ids extracted from the agreement.
|
|
325
|
+
* @returns True if the rule party constraint is satisfied.
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
isPartyApplicable(rulePartyIds, agreementPartyIds) {
|
|
329
|
+
// No party specified on rule means it applies to any agreement party.
|
|
330
|
+
if (Is.empty(rulePartyIds)) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
// Rule specifies party/parties, but agreement doesn't provide any.
|
|
334
|
+
if (Is.empty(agreementPartyIds)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
for (const rulePartyId of rulePartyIds) {
|
|
338
|
+
if (agreementPartyIds.includes(rulePartyId)) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Determine whether a rule's action(s) match the requested action.
|
|
346
|
+
* If no action is specified in the rule, it applies to all actions (action-agnostic).
|
|
347
|
+
* If an action is specified in the rule and a specific action is requested, they must match.
|
|
348
|
+
* If an action is specified in the rule but no specific action is requested, the rule applies (general evaluation).
|
|
349
|
+
* @param ruleActions The actions defined in the rule (can be string, object, or array).
|
|
350
|
+
* @param requestedAction The action being requested (optional).
|
|
351
|
+
* @returns True if the rule's action(s) apply.
|
|
352
|
+
* @internal
|
|
353
|
+
*/
|
|
354
|
+
isActionApplicable(ruleActions, requestedAction) {
|
|
355
|
+
// If the rule has no action specified, it applies to all actions
|
|
356
|
+
if (Is.empty(ruleActions)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
// If the rule has actions but no specific action is requested,
|
|
360
|
+
// the rule applies (we're evaluating permissions in general)
|
|
361
|
+
if (Is.empty(requestedAction)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
// Both rule actions and requested action are specified - check for match
|
|
365
|
+
const ruleActionArray = ArrayHelper.fromObjectOrArray(ruleActions) ?? [];
|
|
366
|
+
// Check if the requested action matches any of the rule's actions
|
|
367
|
+
for (const ruleAction of ruleActionArray) {
|
|
368
|
+
const ruleActionId = Is.string(ruleAction)
|
|
369
|
+
? ruleAction
|
|
370
|
+
: (ruleAction.uid ?? ruleAction["@id"]);
|
|
371
|
+
const requestedActionId = Is.string(requestedAction)
|
|
372
|
+
? requestedAction
|
|
373
|
+
: (requestedAction?.uid ??
|
|
374
|
+
requestedAction?.["@id"]);
|
|
375
|
+
if (Is.stringValue(ruleActionId) && Is.stringValue(requestedActionId)) {
|
|
376
|
+
if (ruleActionId === requestedActionId) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Apply a permission and create decisions based on that information.
|
|
385
|
+
* @param agreementAssigner The assigner ID from the agreement.
|
|
386
|
+
* @param agreementAssignee The assignee ID from the agreement.
|
|
387
|
+
* @param policy The policy containing the permission.
|
|
388
|
+
* @param permission The permission to apply.
|
|
389
|
+
* @param information Additional facts provided by the PIP.
|
|
390
|
+
* @param data The request data/context.
|
|
391
|
+
* @returns True if the permission applies.
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
async evaluatePermission(agreementAssigner, agreementAssignee, policy, permission, information, data, action) {
|
|
395
|
+
if (!this.isRuleApplicableToParties(permission, agreementAssigner, agreementAssignee)) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
// Check if the permission's action(s) match the requested action
|
|
399
|
+
if (!this.isActionApplicable(permission.action, action)) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
// ODRL semantics: a Permission without constraints is unconditional.
|
|
403
|
+
const constraints = ArrayHelper.fromObjectOrArray(permission.constraint ?? []);
|
|
404
|
+
if (constraints.length === 0) {
|
|
405
|
+
return this.enforcePermissionDuties(policy, permission, information, data);
|
|
406
|
+
}
|
|
407
|
+
// Resolve the data context for evaluating this permission.
|
|
408
|
+
// If the target starts with twin:information:<key>, the key references an entry in the information map.
|
|
409
|
+
const ruleDataContext = this.resolveRuleDataContext(permission, information, data);
|
|
410
|
+
// All constraints must be satisfied for the permission to apply.
|
|
411
|
+
const constraintsSatisfied = constraints.every(c => this.evaluateConstraint(c, ruleDataContext));
|
|
412
|
+
if (!constraintsSatisfied) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
return this.enforcePermissionDuties(policy, permission, information, ruleDataContext);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Enforce duties attached to a permission.
|
|
419
|
+
* @param policy The policy being evaluated.
|
|
420
|
+
* @param permission The permission being evaluated.
|
|
421
|
+
* @param information Additional facts provided by the PIP.
|
|
422
|
+
* @param data The request data/context.
|
|
423
|
+
* @returns True if all duties are enforced or none are present.
|
|
424
|
+
* @internal
|
|
425
|
+
*/
|
|
426
|
+
async enforcePermissionDuties(policy, permission, information, data) {
|
|
427
|
+
const duties = ArrayHelper.fromObjectOrArray(permission.duty ?? []);
|
|
428
|
+
if (duties.length === 0) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
for (const duty of duties) {
|
|
432
|
+
const enforced = await this.enforceDuty(policy, duty, information, data);
|
|
433
|
+
if (!enforced) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Enforce a single duty using registered obligation enforcers.
|
|
441
|
+
* @param policy The policy being evaluated.
|
|
442
|
+
* @param duty The duty to enforce.
|
|
443
|
+
* @param information Additional facts provided by the PIP.
|
|
444
|
+
* @param data The request data/context.
|
|
445
|
+
* @returns True if any enforcer succeeds.
|
|
446
|
+
* @internal
|
|
447
|
+
*/
|
|
448
|
+
async enforceDuty(policy, duty, information, data) {
|
|
449
|
+
const enforcerNames = PolicyObligationEnforcerFactory.names();
|
|
450
|
+
if (enforcerNames.length === 0) {
|
|
451
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "noObligationEnforcersRegistered");
|
|
452
|
+
}
|
|
453
|
+
for (const enforcerName of enforcerNames) {
|
|
454
|
+
const enforcer = PolicyObligationEnforcerFactory.get(enforcerName);
|
|
455
|
+
if (await enforcer.enforce(policy, duty, information, data)) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Resolve the data context used when evaluating a permission's constraints.
|
|
463
|
+
* If the permission target is `twin:information:<key>`, the key references an entry in the information map.
|
|
464
|
+
* @param rule The permission being evaluated.
|
|
465
|
+
* @param information Additional facts provided by the PIP.
|
|
466
|
+
* @param data The request data/context.
|
|
467
|
+
* @returns The data context to use for JSONPath resolution.
|
|
468
|
+
* @throws GeneralError When the information target key is missing.
|
|
469
|
+
* @internal
|
|
470
|
+
*/
|
|
471
|
+
resolveRuleDataContext(rule, information, data) {
|
|
472
|
+
const targetId = this.getTargetId(rule.target);
|
|
473
|
+
if (targetId?.startsWith(DefaultPolicyArbiter._INFORMATION_TARGET_PREFIX)) {
|
|
474
|
+
const key = targetId.slice(DefaultPolicyArbiter._INFORMATION_TARGET_PREFIX.length);
|
|
475
|
+
if (Is.empty(key) || Is.empty(information?.[key])) {
|
|
476
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "informationTargetMissing", {
|
|
477
|
+
key
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return information[key];
|
|
481
|
+
}
|
|
482
|
+
return data;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Extract the target id from the permission.
|
|
486
|
+
* @param target The permission target.
|
|
487
|
+
* @returns The information key, or undefined when the target is not an information reference.
|
|
488
|
+
* @internal
|
|
489
|
+
*/
|
|
490
|
+
getTargetId(target) {
|
|
491
|
+
if (Is.undefined(target)) {
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
if (Is.array(target) && target.length > 1) {
|
|
495
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "multipleTargetsNotSupported");
|
|
496
|
+
}
|
|
497
|
+
const arr = ArrayHelper.fromObjectOrArray(target ?? []);
|
|
498
|
+
if (arr.length === 0) {
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
return Is.string(arr[0]) ? arr[0] : arr[0].uid;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Evaluate a single ODRL constraint against the available context.
|
|
505
|
+
* Supports logical constraint composition through nested refinements.
|
|
506
|
+
* @param constraint The constraint to evaluate.
|
|
507
|
+
* @param ruleDataContext The request data/context.
|
|
508
|
+
* @returns True if the constraint is satisfied.
|
|
509
|
+
* @internal
|
|
510
|
+
*/
|
|
511
|
+
evaluateConstraint(constraint, ruleDataContext) {
|
|
512
|
+
const logicalConstraint = this.getLogicalConstraintOperands(constraint);
|
|
513
|
+
if (logicalConstraint) {
|
|
514
|
+
return this.evaluateLogicalConstraint(logicalConstraint, ruleDataContext);
|
|
515
|
+
}
|
|
516
|
+
// Must be a regular constraint beyond this point
|
|
517
|
+
const regularConstraint = constraint;
|
|
518
|
+
// Evaluate the main constraint condition
|
|
519
|
+
const leftValue = this.calculateOperandValue(regularConstraint.leftOperand, ruleDataContext);
|
|
520
|
+
const rightValue = this.calculateOperandValue(regularConstraint.rightOperand, ruleDataContext);
|
|
521
|
+
const mainSatisfied = this.evaluateOperator(regularConstraint.operator, leftValue, rightValue);
|
|
522
|
+
// If main constraint is not satisfied, the overall constraint fails
|
|
523
|
+
return mainSatisfied;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Extract logical constraint operands when present.
|
|
527
|
+
* @param constraint The constraint to inspect.
|
|
528
|
+
* @returns The logical operator and its operands, or undefined when not logical.
|
|
529
|
+
* @throws GeneralError if logical constraint operands are not unique.
|
|
530
|
+
* @internal
|
|
531
|
+
*/
|
|
532
|
+
getLogicalConstraintOperands(constraint) {
|
|
533
|
+
const logicalConstraint = constraint;
|
|
534
|
+
const operators = Object.values(LogicalConstraintType);
|
|
535
|
+
for (const operator of operators) {
|
|
536
|
+
const value = logicalConstraint[operator];
|
|
537
|
+
if (!Is.undefined(value)) {
|
|
538
|
+
const constraints = this.normalizeLogicalConstraintOperands(value);
|
|
539
|
+
this.validateLogicalConstraintOperandUniqueness(constraints, operator);
|
|
540
|
+
return {
|
|
541
|
+
operator,
|
|
542
|
+
constraints
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Normalize logical constraint operands into a constraint array.
|
|
550
|
+
* Handles both IOdrlConstraint and IOdrlLogicalConstraint types.
|
|
551
|
+
* @param raw The raw operand value.
|
|
552
|
+
* @returns The constraint array.
|
|
553
|
+
* @internal
|
|
554
|
+
*/
|
|
555
|
+
normalizeLogicalConstraintOperands(raw) {
|
|
556
|
+
let normalized = raw;
|
|
557
|
+
if (Is.object(normalized) &&
|
|
558
|
+
!Is.undefined(normalized["@list"])) {
|
|
559
|
+
normalized = normalized["@list"];
|
|
560
|
+
}
|
|
561
|
+
return (ArrayHelper.fromObjectOrArray(normalized) ?? [])
|
|
562
|
+
.filter(item => Is.object(item))
|
|
563
|
+
.map(item => item);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Validate that all operands in a logical constraint are unique.
|
|
567
|
+
* ODRL spec 2.5.2 requires that all operand values MUST be unique Constraint instances.
|
|
568
|
+
* Uniqueness is checked by uid property and id property.
|
|
569
|
+
* @param constraints The constraint operands to validate.
|
|
570
|
+
* @param operator The logical operator type (for error messaging).
|
|
571
|
+
* @throws GeneralError if duplicate constraints are found.
|
|
572
|
+
* @internal
|
|
573
|
+
*/
|
|
574
|
+
validateLogicalConstraintOperandUniqueness(constraints, operator) {
|
|
575
|
+
const seenIdentifiers = new Set();
|
|
576
|
+
for (let i = 0; i < constraints.length; i++) {
|
|
577
|
+
const constraint = constraints[i];
|
|
578
|
+
let identifier;
|
|
579
|
+
// Try to get identifier from uid property
|
|
580
|
+
if (Is.stringValue(constraint.uid)) {
|
|
581
|
+
identifier = constraint.uid;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// Try to get identifier from @id property (JSON-LD)
|
|
585
|
+
const jsonLdId = constraint["@id"];
|
|
586
|
+
if (Is.stringValue(jsonLdId)) {
|
|
587
|
+
identifier = jsonLdId;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// If we have an identifier, check for duplicates
|
|
591
|
+
if (Is.stringValue(identifier)) {
|
|
592
|
+
if (seenIdentifiers.has(identifier)) {
|
|
593
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "logicalConstraintOperandNotUnique", {
|
|
594
|
+
operator,
|
|
595
|
+
identifier,
|
|
596
|
+
index: i
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
seenIdentifiers.add(identifier);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Evaluate a logical constraint operator against its operands.
|
|
605
|
+
* @param logicalConstraint The operator and operand list.
|
|
606
|
+
* @param ruleDataContext The request data/context.
|
|
607
|
+
* @returns True if the logical constraint is satisfied.
|
|
608
|
+
* @internal
|
|
609
|
+
*/
|
|
610
|
+
evaluateLogicalConstraint(logicalConstraint, ruleDataContext) {
|
|
611
|
+
const { operator, constraints } = logicalConstraint;
|
|
612
|
+
if (constraints.length === 0) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
switch (operator) {
|
|
616
|
+
case LogicalConstraintType.And:
|
|
617
|
+
return constraints.every(item => this.evaluateConstraint(item, ruleDataContext));
|
|
618
|
+
case LogicalConstraintType.AndSequence: {
|
|
619
|
+
for (const item of constraints) {
|
|
620
|
+
if (!this.evaluateConstraint(item, ruleDataContext)) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
case LogicalConstraintType.Or:
|
|
627
|
+
return constraints.some(item => this.evaluateConstraint(item, ruleDataContext));
|
|
628
|
+
case LogicalConstraintType.Xone: {
|
|
629
|
+
let satisfied = 0;
|
|
630
|
+
for (const item of constraints) {
|
|
631
|
+
if (this.evaluateConstraint(item, ruleDataContext)) {
|
|
632
|
+
satisfied += 1;
|
|
633
|
+
if (satisfied > 1) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return satisfied === 1;
|
|
639
|
+
}
|
|
640
|
+
default:
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Calculate an operand value.
|
|
646
|
+
* @param operand The operand.
|
|
647
|
+
* @param ruleDataContext The request data/context.
|
|
648
|
+
* @returns The resolved operand value.
|
|
649
|
+
* @internal
|
|
650
|
+
*/
|
|
651
|
+
calculateOperandValue(operand, ruleDataContext) {
|
|
652
|
+
// Treat JSONPath operands as selectors against the current rule data context.
|
|
653
|
+
// Will be in the format twin:jsonpath:<jsonPath>
|
|
654
|
+
let jsonPath;
|
|
655
|
+
if (Is.stringValue(operand)) {
|
|
656
|
+
if (operand.startsWith(`${DefaultPolicyArbiter._JSON_PATH_OPERAND_PREFIX}:`)) {
|
|
657
|
+
jsonPath = operand.slice(DefaultPolicyArbiter._JSON_PATH_OPERAND_PREFIX.length + 1);
|
|
658
|
+
if (jsonPath.length === 0) {
|
|
659
|
+
throw new GeneralError(DefaultPolicyArbiter.CLASS_NAME, "jsonPathOperandMissingTarget", {
|
|
660
|
+
operand
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
else if (Is.object(operand)) {
|
|
666
|
+
// Is this an object { "@value": "18", "@type": "xsd:integer" } ?
|
|
667
|
+
const value = operand["@value"];
|
|
668
|
+
const type = operand["@type"];
|
|
669
|
+
if (Is.stringValue(type)) {
|
|
670
|
+
// If the type is set we can try and extract the value
|
|
671
|
+
// is it twin:jsonpath ?
|
|
672
|
+
if (type === DefaultPolicyArbiter._JSON_PATH_OPERAND_PREFIX) {
|
|
673
|
+
if (Is.stringValue(value)) {
|
|
674
|
+
jsonPath = value;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const xsdValue = this.coerceXsdType(value, type);
|
|
679
|
+
if (!Is.undefined(xsdValue)) {
|
|
680
|
+
return xsdValue;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// We have a JSON Path to resolve
|
|
686
|
+
if (Is.stringValue(jsonPath)) {
|
|
687
|
+
const jsonPaths = JsonPathHelper.query(jsonPath, ruleDataContext);
|
|
688
|
+
if (jsonPaths.length === 0) {
|
|
689
|
+
// No matches
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
else if (jsonPaths.length === 1) {
|
|
693
|
+
// Single match - return the value directly
|
|
694
|
+
return jsonPaths[0].value;
|
|
695
|
+
}
|
|
696
|
+
// Multiple matches - return array of values
|
|
697
|
+
return jsonPaths.map(p => p.value);
|
|
698
|
+
}
|
|
699
|
+
// Not JSON Path or object value so return as is
|
|
700
|
+
return operand;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Evaluate an ODRL operator against resolved operands.
|
|
704
|
+
* @param operator The operator.
|
|
705
|
+
* @param left The resolved left operand.
|
|
706
|
+
* @param right The resolved right operand.
|
|
707
|
+
* @returns True if the comparison is satisfied.
|
|
708
|
+
* @internal
|
|
709
|
+
*/
|
|
710
|
+
evaluateOperator(operator, left, right) {
|
|
711
|
+
// Handle array/collection left values (e.g. JSONPath returning multiple matches).
|
|
712
|
+
const leftValues = ArrayHelper.fromObjectOrArray(left ?? []);
|
|
713
|
+
switch (operator) {
|
|
714
|
+
case OperatorType.Eq:
|
|
715
|
+
return leftValues.some(v => ObjectHelper.equal(v, right, false));
|
|
716
|
+
case OperatorType.Neq:
|
|
717
|
+
return leftValues.every(v => !ObjectHelper.equal(v, right, false));
|
|
718
|
+
case OperatorType.Gt:
|
|
719
|
+
return leftValues.some(v => this.compareOrdered(v, right, (a, b) => a > b));
|
|
720
|
+
case OperatorType.Gteq:
|
|
721
|
+
return leftValues.some(v => this.compareOrdered(v, right, (a, b) => a >= b));
|
|
722
|
+
case OperatorType.Lt:
|
|
723
|
+
return leftValues.some(v => this.compareOrdered(v, right, (a, b) => a < b));
|
|
724
|
+
case OperatorType.Lteq:
|
|
725
|
+
return leftValues.some(v => this.compareOrdered(v, right, (a, b) => a <= b));
|
|
726
|
+
case OperatorType.IsAnyOf: {
|
|
727
|
+
return leftValues.some(v => {
|
|
728
|
+
const stringValue = typeof v === "string" || typeof v === "number" || typeof v === "boolean"
|
|
729
|
+
? String(v)
|
|
730
|
+
: JSON.stringify(v);
|
|
731
|
+
return (ArrayHelper.fromObjectOrArray(right) ?? []).includes(stringValue);
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
case OperatorType.IsAllOf: {
|
|
735
|
+
return ObjectHelper.equal(leftValues, ArrayHelper.fromObjectOrArray(right) ?? [], false);
|
|
736
|
+
}
|
|
737
|
+
case OperatorType.IsNoneOf: {
|
|
738
|
+
return leftValues.every(v => !(ArrayHelper.fromObjectOrArray(right) ?? []).includes(v));
|
|
739
|
+
}
|
|
740
|
+
case OperatorType.LocTimeEq:
|
|
741
|
+
return leftValues.some(v => ObjectHelper.equal(v, right, false));
|
|
742
|
+
case OperatorType.LocTimeGteq:
|
|
743
|
+
return leftValues.some(v => this.compareOrdered(v, right, (a, b) => a >= b));
|
|
744
|
+
case OperatorType.IsA:
|
|
745
|
+
case OperatorType.HasPart:
|
|
746
|
+
case OperatorType.IsPartOf:
|
|
747
|
+
// For now, treat these as simple equality/ordering semantics where meaningful.
|
|
748
|
+
// Profiles can introduce richer semantics via additional arbiters.
|
|
749
|
+
return leftValues.some(v => ObjectHelper.equal(v, right, false));
|
|
750
|
+
default:
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Compare values with numeric/date/string coercion.
|
|
756
|
+
* @param left The left value.
|
|
757
|
+
* @param right The right value.
|
|
758
|
+
* @param compare Comparison operator.
|
|
759
|
+
* @returns True if ordered comparison passes.
|
|
760
|
+
* @internal
|
|
761
|
+
*/
|
|
762
|
+
compareOrdered(left, right, compare) {
|
|
763
|
+
const leftNum = Coerce.number(left);
|
|
764
|
+
const rightNum = Coerce.number(right);
|
|
765
|
+
if (!Is.undefined(leftNum) && !Is.undefined(rightNum)) {
|
|
766
|
+
return compare(leftNum, rightNum);
|
|
767
|
+
}
|
|
768
|
+
const leftDate = Coerce.dateTime(left);
|
|
769
|
+
const rightDate = Coerce.dateTime(right);
|
|
770
|
+
if (!Is.undefined(leftDate) && !Is.undefined(rightDate)) {
|
|
771
|
+
return compare(leftDate.getTime(), rightDate.getTime());
|
|
772
|
+
}
|
|
773
|
+
// Only use string ordering when both operands are actual strings.
|
|
774
|
+
// Avoid coercing other types into strings, as that can cause
|
|
775
|
+
// unintended comparisons like 18 >= "$.minAge" evaluating to true.
|
|
776
|
+
if (Is.string(left) && Is.string(right)) {
|
|
777
|
+
return compare(left.localeCompare(right), 0);
|
|
778
|
+
}
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Coerce a value to a specific XSD type.
|
|
783
|
+
* @param value The value to coerce.
|
|
784
|
+
* @param type The XSD type.
|
|
785
|
+
* @returns The coerced value, or undefined when coercion is not possible.
|
|
786
|
+
* @internal
|
|
787
|
+
*/
|
|
788
|
+
coerceXsdType(value, type) {
|
|
789
|
+
if ([
|
|
790
|
+
"xsd:string",
|
|
791
|
+
"xsd:normalizedString",
|
|
792
|
+
"xsd:token",
|
|
793
|
+
"xsd:anyURI",
|
|
794
|
+
"xsd:QName",
|
|
795
|
+
"xsd:NOTATION"
|
|
796
|
+
].includes(type)) {
|
|
797
|
+
// Handle standard xsd types
|
|
798
|
+
return Coerce.string(value);
|
|
799
|
+
}
|
|
800
|
+
else if ([
|
|
801
|
+
"xsd:integer",
|
|
802
|
+
"xsd:decimal",
|
|
803
|
+
"xsd:float",
|
|
804
|
+
"xsd:double",
|
|
805
|
+
"xsd:long",
|
|
806
|
+
"xsd:int",
|
|
807
|
+
"xsd:short",
|
|
808
|
+
"xsd:byte"
|
|
809
|
+
].includes(type)) {
|
|
810
|
+
// Handle standard xsd types
|
|
811
|
+
return Coerce.number(value);
|
|
812
|
+
}
|
|
813
|
+
else if (type === "xsd:boolean") {
|
|
814
|
+
// Handle standard xsd types
|
|
815
|
+
return Coerce.boolean(value);
|
|
816
|
+
}
|
|
817
|
+
else if (["xsd:date", "xsd:dateTime", "xsd:time"].includes(type)) {
|
|
818
|
+
// Handle standard xsd types
|
|
819
|
+
return Coerce.dateTime(value);
|
|
820
|
+
}
|
|
821
|
+
return undefined;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
//# sourceMappingURL=defaultPolicyArbiter.js.map
|