@matter/node 0.14.1-alpha.0-20250606-a9bcd03f9 → 0.15.0-alpha.0-20250612-ddd428561
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/cjs/behavior/Events.d.ts +8 -3
- package/dist/cjs/behavior/Events.d.ts.map +1 -1
- package/dist/cjs/behavior/Events.js +5 -1
- package/dist/cjs/behavior/Events.js.map +1 -1
- package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js +3 -3
- package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts +2 -1
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.js +22 -7
- package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/cjs/behavior/state/managed/Datasource.d.ts +6 -5
- package/dist/cjs/behavior/state/managed/Datasource.d.ts.map +1 -1
- package/dist/cjs/behavior/state/managed/Datasource.js +25 -14
- package/dist/cjs/behavior/state/managed/Datasource.js.map +1 -1
- package/dist/cjs/behavior/supervision/ValueSupervisor.d.ts +7 -3
- package/dist/cjs/behavior/supervision/ValueSupervisor.d.ts.map +1 -1
- package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts +20 -36
- package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/access-control/AccessControlServer.js +153 -87
- package/dist/cjs/behaviors/access-control/AccessControlServer.js.map +1 -1
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js +8 -19
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js.map +2 -2
- package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/node/server/InteractionServer.js +10 -44
- package/dist/cjs/node/server/InteractionServer.js.map +2 -2
- package/dist/cjs/node/server/ProtocolService.js +1 -1
- package/dist/cjs/node/server/ProtocolService.js.map +1 -1
- package/dist/esm/behavior/Events.d.ts +8 -3
- package/dist/esm/behavior/Events.d.ts.map +1 -1
- package/dist/esm/behavior/Events.js +5 -2
- package/dist/esm/behavior/Events.js.map +1 -1
- package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js +4 -4
- package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.d.ts +2 -1
- package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.js +29 -9
- package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/esm/behavior/state/managed/Datasource.d.ts +6 -5
- package/dist/esm/behavior/state/managed/Datasource.d.ts.map +1 -1
- package/dist/esm/behavior/state/managed/Datasource.js +25 -14
- package/dist/esm/behavior/state/managed/Datasource.js.map +1 -1
- package/dist/esm/behavior/supervision/ValueSupervisor.d.ts +7 -3
- package/dist/esm/behavior/supervision/ValueSupervisor.d.ts.map +1 -1
- package/dist/esm/behaviors/access-control/AccessControlServer.d.ts +20 -36
- package/dist/esm/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
- package/dist/esm/behaviors/access-control/AccessControlServer.js +153 -88
- package/dist/esm/behaviors/access-control/AccessControlServer.js.map +1 -1
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js +8 -19
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
- package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/esm/node/server/InteractionServer.js +10 -44
- package/dist/esm/node/server/InteractionServer.js.map +1 -1
- package/dist/esm/node/server/ProtocolService.js +1 -1
- package/dist/esm/node/server/ProtocolService.js.map +1 -1
- package/package.json +7 -7
- package/src/behavior/Events.ts +8 -3
- package/src/behavior/cluster/ClusterBehaviorUtil.ts +4 -4
- package/src/behavior/context/server/OnlineContext.ts +39 -9
- package/src/behavior/state/managed/Datasource.ts +37 -20
- package/src/behavior/supervision/ValueSupervisor.ts +8 -3
- package/src/behaviors/access-control/AccessControlServer.ts +210 -102
- package/src/behaviors/operational-credentials/OperationalCredentialsServer.ts +10 -18
- package/src/node/server/InteractionServer.ts +10 -63
- package/src/node/server/ProtocolService.ts +1 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import { AsyncObservable } from "#general";
|
|
7
|
+
import { ActionContext } from "#behavior/context/ActionContext.js";
|
|
8
|
+
import type { AsyncObservable, Transaction } from "#general";
|
|
9
9
|
import { DataModelPath, Schema } from "#model";
|
|
10
10
|
import type { AccessControl, Val } from "#protocol";
|
|
11
11
|
import type { ValidationLocation } from "../state/validation/location.js";
|
|
@@ -84,7 +84,12 @@ export namespace ValueSupervisor {
|
|
|
84
84
|
/**
|
|
85
85
|
* If present the session is associated with an online interaction. Emits when the interaction ends.
|
|
86
86
|
*/
|
|
87
|
-
interactionComplete?: AsyncObservable<[]>;
|
|
87
|
+
interactionComplete?: AsyncObservable<[session?: ActionContext]>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set to true when the interaction has started and the interactionBegin event was emitted for this session
|
|
91
|
+
*/
|
|
92
|
+
interactionStarted?: boolean;
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
95
|
* If true, structs initialize without named properties which are more expensive to install. This is useful
|
|
@@ -7,14 +7,16 @@
|
|
|
7
7
|
import { ActionContext } from "#behavior/context/ActionContext.js";
|
|
8
8
|
import { AccessControl as AccessControlTypes } from "#clusters/access-control";
|
|
9
9
|
import { deepCopy, InternalError, Logger, MaybePromise } from "#general";
|
|
10
|
-
import { AccessLevel } from "#model";
|
|
11
10
|
import { NodeLifecycle } from "#node/NodeLifecycle.js";
|
|
12
11
|
import {
|
|
13
12
|
AccessControl,
|
|
14
|
-
AccessControlManager,
|
|
15
13
|
AclEndpointContext,
|
|
14
|
+
AclEntry,
|
|
15
|
+
AclList,
|
|
16
|
+
Fabric,
|
|
16
17
|
FabricManager,
|
|
17
18
|
IncomingSubjectDescriptor,
|
|
19
|
+
MessageExchange,
|
|
18
20
|
NodeSession,
|
|
19
21
|
SecureSession,
|
|
20
22
|
} from "#protocol";
|
|
@@ -23,10 +25,12 @@ import {
|
|
|
23
25
|
ClusterId,
|
|
24
26
|
DeviceTypeId,
|
|
25
27
|
EndpointNumber,
|
|
28
|
+
FabricIndex,
|
|
26
29
|
GroupId,
|
|
27
30
|
NodeId,
|
|
28
31
|
StatusCode,
|
|
29
32
|
StatusResponseError,
|
|
33
|
+
SubjectId,
|
|
30
34
|
TlvTaggedList,
|
|
31
35
|
TlvType,
|
|
32
36
|
} from "#types";
|
|
@@ -36,6 +40,9 @@ const logger = Logger.get("AccessControlServer");
|
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* This is the default server implementation of AccessControlBehavior.
|
|
43
|
+
*
|
|
44
|
+
* When custom extensions are used, the `extensionEntryValidator` and `extensionEntryAccessCheck` methods can be
|
|
45
|
+
* overridden to implement custom validation and access checks for the extension entries.
|
|
39
46
|
*/
|
|
40
47
|
export class AccessControlServer extends AccessControlBehavior.with("Extension") {
|
|
41
48
|
declare internal: AccessControlServer.Internal;
|
|
@@ -57,20 +64,25 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
#online() {
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const aclsForFabric = this.#mapFabricAcls();
|
|
68
|
+
|
|
69
|
+
// Initialize the ACL managers for each fabric
|
|
62
70
|
const fabrics = this.env.get(FabricManager);
|
|
63
|
-
const acl = deepCopy(this.state.acl);
|
|
64
|
-
const originalAclLength = acl.length;
|
|
65
71
|
for (const fabric of fabrics) {
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
const fabricAcls = aclsForFabric.get(fabric.fabricIndex) ?? [];
|
|
73
|
+
|
|
74
|
+
if (!fabricAcls.length) {
|
|
75
|
+
// Handle Backward compatibility to Matter.js before 0.9.1 and add the missing ACL entry if no entry was set
|
|
76
|
+
// so far by the controller
|
|
77
|
+
const fallbackAcl: AccessControlTypes.AccessControlEntry = {
|
|
68
78
|
fabricIndex: fabric.fabricIndex,
|
|
69
79
|
privilege: AccessControlTypes.AccessControlEntryPrivilege.Administer,
|
|
70
80
|
authMode: AccessControlTypes.AccessControlEntryAuthMode.Case,
|
|
71
81
|
subjects: [fabric.rootNodeId],
|
|
72
82
|
targets: null, // entire node
|
|
73
|
-
}
|
|
83
|
+
};
|
|
84
|
+
this.state.acl.push(fallbackAcl);
|
|
85
|
+
fabricAcls.push(fallbackAcl);
|
|
74
86
|
logger.warn(
|
|
75
87
|
"Added missing ACL entry for fabric",
|
|
76
88
|
fabric.fabricIndex,
|
|
@@ -79,28 +91,51 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
79
91
|
". This should only happen once after upgrading to matter.js 0.9.1",
|
|
80
92
|
);
|
|
81
93
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.state.acl = acl;
|
|
94
|
+
fabric.acl.aclList = fabricAcls;
|
|
95
|
+
fabric.acl.extensionEntryAccessCheck = this.extensionEntryAccessCheck.bind(this);
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
aclEntry as unknown as AccessControlTypes.AccessControlEntry,
|
|
94
|
-
subjectDesc,
|
|
95
|
-
endpoint,
|
|
96
|
-
clusterId,
|
|
97
|
-
),
|
|
98
|
-
);
|
|
98
|
+
// TODO handle delete fabric more generically later to remove fabric scoped data
|
|
99
|
+
this.reactTo(fabrics.events.updated, this.#updateFabricAcls);
|
|
100
|
+
this.reactTo(fabrics.events.added, this.#updateFabricAcls);
|
|
101
|
+
|
|
102
|
+
this.reactTo(this.events.interactionBegin, this.#handleInteractionBegin);
|
|
103
|
+
this.reactTo(this.events.interactionEnd, this.#handleInteractionEnd);
|
|
99
104
|
|
|
100
105
|
this.reactTo(this.events.acl$Changed, this.#updateAccessControlList);
|
|
106
|
+
|
|
107
|
+
this.internal.initialized = true;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
|
|
110
|
+
addDefaultCaseAcl(fabric: Fabric, subjects: SubjectId[]) {
|
|
111
|
+
const entry = {
|
|
112
|
+
fabricIndex: fabric.fabricIndex,
|
|
113
|
+
privilege: AccessControlTypes.AccessControlEntryPrivilege.Administer,
|
|
114
|
+
authMode: AccessControlTypes.AccessControlEntryAuthMode.Case,
|
|
115
|
+
subjects: subjects as NodeId[],
|
|
116
|
+
targets: null, // entire node
|
|
117
|
+
};
|
|
118
|
+
this.state.acl.push(entry);
|
|
119
|
+
this.#updateFabricAcls(fabric);
|
|
120
|
+
// The update of the ACL is not validated because it has no assigned sess/fabric (and even then would use
|
|
121
|
+
// the wrong one), so we trigger the event ourself.
|
|
122
|
+
this.events.accessControlEntryChanged?.emit(
|
|
123
|
+
{
|
|
124
|
+
changeType: AccessControlTypes.ChangeType.Added,
|
|
125
|
+
adminNodeId: null, // When we add it, it is always from a PASE session
|
|
126
|
+
adminPasscodeId: 0, // When we add it, it is always from a PASE session
|
|
127
|
+
latestValue: entry,
|
|
128
|
+
fabricIndex: fabric.fabricIndex,
|
|
129
|
+
},
|
|
130
|
+
this.context,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#validateAccessControlListChanges(
|
|
135
|
+
value: AccessControlTypes.AccessControlEntry[],
|
|
136
|
+
_oldValue: AccessControlTypes.AccessControlEntry[],
|
|
137
|
+
context?: ActionContext,
|
|
138
|
+
) {
|
|
104
139
|
// TODO: This might be not really correct for local ACL changes because there the session fabric could be
|
|
105
140
|
// different which would lead to missing validation of the relevant entries
|
|
106
141
|
const relevantFabricIndex = this.context.session?.associatedFabric.fabricIndex;
|
|
@@ -108,6 +143,21 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
108
143
|
if (relevantFabricIndex === undefined) {
|
|
109
144
|
return;
|
|
110
145
|
}
|
|
146
|
+
if (context !== undefined && context.exchange !== undefined) {
|
|
147
|
+
const delayedChangeExchange = this.internal.aclUpdateDelayed.get(relevantFabricIndex);
|
|
148
|
+
if (delayedChangeExchange !== undefined && delayedChangeExchange !== context.exchange) {
|
|
149
|
+
// We are in a delayed ACL update with another exchange, so we do not process this one with Busy error
|
|
150
|
+
// This is formally not in specification, but chip also does this that way
|
|
151
|
+
logger.warn(
|
|
152
|
+
"Decline parallel ACL changes from multiple exchanges",
|
|
153
|
+
context.exchange.id,
|
|
154
|
+
"vs.",
|
|
155
|
+
delayedChangeExchange.id,
|
|
156
|
+
);
|
|
157
|
+
throw new StatusResponseError("Parallel ACL change from multiple exchanges", StatusCode.Busy);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
111
161
|
const fabricAcls = value.filter(entry => entry.fabricIndex === relevantFabricIndex);
|
|
112
162
|
if (fabricAcls.length > this.state.accessControlEntriesPerFabric) {
|
|
113
163
|
throw new StatusResponseError("AccessControlEntriesPerFabric exceeded", StatusCode.ResourceExhausted);
|
|
@@ -223,7 +273,7 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
223
273
|
value: AccessControlTypes.AccessControlEntry[],
|
|
224
274
|
oldValue: AccessControlTypes.AccessControlEntry[],
|
|
225
275
|
) {
|
|
226
|
-
if (this.internal.
|
|
276
|
+
if (!this.internal.initialized) {
|
|
227
277
|
return; // Too early to send events
|
|
228
278
|
}
|
|
229
279
|
const { session } = this.context;
|
|
@@ -302,7 +352,7 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
302
352
|
value: AccessControlTypes.AccessControlExtension[],
|
|
303
353
|
oldValue: AccessControlTypes.AccessControlExtension[],
|
|
304
354
|
) {
|
|
305
|
-
if (this.internal.
|
|
355
|
+
if (!this.internal.initialized) {
|
|
306
356
|
return; // Too early to send events
|
|
307
357
|
}
|
|
308
358
|
const { session } = this.context;
|
|
@@ -341,35 +391,6 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
341
391
|
);
|
|
342
392
|
}
|
|
343
393
|
|
|
344
|
-
/**
|
|
345
|
-
* Implements the access control check for the given context, location and endpoint and is called by the
|
|
346
|
-
* InteractionServer. The method returns the list of granted Access privileges for the given context, location and
|
|
347
|
-
* endpoint.
|
|
348
|
-
*/
|
|
349
|
-
accessLevelsFor(
|
|
350
|
-
context: ActionContext,
|
|
351
|
-
location: AccessControl.Location,
|
|
352
|
-
endpoint?: AclEndpointContext,
|
|
353
|
-
): AccessLevel[] {
|
|
354
|
-
if (location.cluster === undefined) {
|
|
355
|
-
// Without a cluster, internal behaviors are only accessible internally so this is an irrelevant placeholder
|
|
356
|
-
logger.warn("Access control check without cluster, returning View access level");
|
|
357
|
-
return [AccessLevel.View];
|
|
358
|
-
}
|
|
359
|
-
if (context.session === undefined) {
|
|
360
|
-
// Without a session, we can't determine access levels
|
|
361
|
-
logger.warn("Access control check without session, returning View access level");
|
|
362
|
-
return [AccessLevel.View];
|
|
363
|
-
}
|
|
364
|
-
if (endpoint === undefined) {
|
|
365
|
-
// Without an endpoint, we can't determine access levels, ???
|
|
366
|
-
logger.warn("Access control check without endpoint, returning View access level");
|
|
367
|
-
return [AccessLevel.View];
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return this.aclManager.getGrantedPrivileges(context, endpoint, location.cluster);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
394
|
/**
|
|
374
395
|
* This method allows to implement the validation of manufacturer specific ACL extensions when an extension entry is
|
|
375
396
|
* added or changed. The default implementation checks whether the extension is a valid TLV and possible to decode.
|
|
@@ -399,8 +420,8 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
399
420
|
* validation.
|
|
400
421
|
*/
|
|
401
422
|
protected extensionEntryAccessCheck(
|
|
402
|
-
_aclList:
|
|
403
|
-
_aclEntry:
|
|
423
|
+
_aclList: AclList,
|
|
424
|
+
_aclEntry: AclEntry,
|
|
404
425
|
_subjectDesc: IncomingSubjectDescriptor,
|
|
405
426
|
_endpoint: AclEndpointContext,
|
|
406
427
|
_clusterId: ClusterId,
|
|
@@ -408,77 +429,164 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
|
|
|
408
429
|
return true;
|
|
409
430
|
}
|
|
410
431
|
|
|
432
|
+
/** A fabric was added or updated, so we need to initialize the ACL for this fabric */
|
|
433
|
+
#updateFabricAcls(fabric: Fabric) {
|
|
434
|
+
const fabricIndex = fabric.fabricIndex;
|
|
435
|
+
fabric.acl.aclList = deepCopy(this.state.acl).filter(entry => entry.fabricIndex === fabricIndex);
|
|
436
|
+
}
|
|
437
|
+
|
|
411
438
|
/**
|
|
412
|
-
*
|
|
439
|
+
* When beginning an interaction for an online session, we register the potential ACL change for the associated
|
|
440
|
+
* fabric index. If ACL data are really changed later, the exchange gets added then.
|
|
413
441
|
*/
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
442
|
+
#handleInteractionBegin(session?: AccessControl.Session) {
|
|
443
|
+
if (session !== undefined && !session.offline && session.fabric !== undefined) {
|
|
444
|
+
this.#prepareAclUpdateFor(session.fabric);
|
|
417
445
|
}
|
|
418
|
-
return this.internal.aclManager;
|
|
419
446
|
}
|
|
420
447
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
448
|
+
/**
|
|
449
|
+
* When an interaction is finished, we check if there was a delayed ACL update for the associated fabric and apply
|
|
450
|
+
* it to the manager. For this we check if we have an exchange stored because otherwise the interaction was in fact
|
|
451
|
+
* not changing the ACL.
|
|
452
|
+
*/
|
|
453
|
+
#handleInteractionEnd(session?: AccessControl.Session) {
|
|
454
|
+
if (session !== undefined && !session.offline && session.fabric !== undefined) {
|
|
455
|
+
if (this.internal.aclUpdateDelayed.get(session.fabric) !== undefined) {
|
|
456
|
+
this.#applyDelayedAclUpdateFor(session.fabric);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** The ACL list was changed, so we need to determine if and when to apply the update to the ACL manager */
|
|
462
|
+
#updateAccessControlList(
|
|
463
|
+
acl: AccessControlTypes.AccessControlEntry[],
|
|
464
|
+
_oldAcl: AccessControlTypes.AccessControlEntry[],
|
|
465
|
+
context?: ActionContext,
|
|
466
|
+
) {
|
|
467
|
+
if (context === undefined || context.offline) {
|
|
468
|
+
// local or offline ACL change, so we update all fabrics because we do not know better
|
|
469
|
+
this.#updateAllFabricsAcls();
|
|
470
|
+
} else {
|
|
471
|
+
const fabric = context.session?.associatedFabric;
|
|
472
|
+
if (fabric === undefined || fabric.fabricIndex === undefined || context.exchange === undefined) {
|
|
473
|
+
throw new InternalError("We require a fabric bound online session to write ACL changes");
|
|
474
|
+
}
|
|
475
|
+
this.#handleFabricAclUpdate(fabric, acl, context.exchange);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Handles the ACL update for a specific fabric. If an exchange is present, we delay the update until the
|
|
481
|
+
* interaction is finished.
|
|
482
|
+
*/
|
|
483
|
+
#handleFabricAclUpdate(fabric: Fabric, acl: AccessControlTypes.AccessControlEntry[], exchange: MessageExchange) {
|
|
484
|
+
const fabricIndex = fabric.fabricIndex;
|
|
485
|
+
if (this.internal.aclUpdateDelayed.has(fabricIndex)) {
|
|
486
|
+
// We have registered an interaction for this fabric, so we delay the update
|
|
487
|
+
logger.debug(
|
|
488
|
+
"ACL attribute updated, but interaction still in progress, delaying update of ACL manager for FabricIndex",
|
|
489
|
+
fabricIndex,
|
|
490
|
+
);
|
|
491
|
+
this.#delayAclUpdateFor(fabricIndex, exchange, acl);
|
|
425
492
|
} else {
|
|
426
|
-
|
|
427
|
-
|
|
493
|
+
// No interaction registered, so we apply directly because local/offline change
|
|
494
|
+
logger.debug("ACL attribute updated, applying update to ACL manager", fabricIndex);
|
|
495
|
+
|
|
496
|
+
fabric.acl.aclList = deepCopy(acl).filter(entry => entry.fabricIndex === fabricIndex);
|
|
428
497
|
}
|
|
429
498
|
}
|
|
430
499
|
|
|
431
|
-
|
|
432
|
-
this.
|
|
433
|
-
|
|
500
|
+
#mapFabricAcls() {
|
|
501
|
+
const acl = deepCopy(this.state.acl);
|
|
502
|
+
const aclsForFabric = new Map<FabricIndex, AccessControlTypes.AccessControlEntry[]>();
|
|
503
|
+
// Collect ACLs for each fabric
|
|
504
|
+
for (const entry of acl) {
|
|
505
|
+
const { fabricIndex } = entry;
|
|
506
|
+
const acls = aclsForFabric.get(fabricIndex) ?? [];
|
|
507
|
+
acls.push(entry);
|
|
508
|
+
aclsForFabric.set(fabricIndex, acls);
|
|
509
|
+
}
|
|
510
|
+
return aclsForFabric;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Update all fabrics with the current ACL list */
|
|
514
|
+
#updateAllFabricsAcls() {
|
|
515
|
+
const aclsForFabric = this.#mapFabricAcls();
|
|
516
|
+
|
|
517
|
+
const fabrics = this.env.get(FabricManager);
|
|
518
|
+
for (const fabric of fabrics) {
|
|
519
|
+
// Update all Fabrics and set the ACL list for each fabric, empty ACLs when none are present
|
|
520
|
+
fabric.acl.aclList = aclsForFabric.get(fabric.fabricIndex) ?? [];
|
|
521
|
+
}
|
|
434
522
|
}
|
|
435
523
|
|
|
436
524
|
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* This is a hack to prevent the ACL from updating while we are in the middle of a write transaction and will be
|
|
440
|
-
* removed again once we somehow handle relevant sub transactions.
|
|
525
|
+
* Register a potential change of ACL for a specific fabric index. if changes happened is checked when interaction
|
|
526
|
+
* ends.
|
|
441
527
|
*/
|
|
442
|
-
|
|
443
|
-
|
|
528
|
+
#prepareAclUpdateFor(fabricIndex: FabricIndex) {
|
|
529
|
+
if (!this.internal.aclUpdateDelayed.has(fabricIndex)) {
|
|
530
|
+
logger.info("Register ACL update to be delayed for fabricIndex", fabricIndex);
|
|
531
|
+
this.internal.aclUpdateDelayed.set(fabricIndex, undefined);
|
|
532
|
+
}
|
|
444
533
|
}
|
|
445
534
|
|
|
446
535
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* This is a hack to prevent the ACL from updating while we are in the middle of a write transaction and will be
|
|
450
|
-
* removed again once we somehow handle relevant sub transactions.
|
|
536
|
+
* Register a concrete change of ACL for a specific fabric index. The exchange allows to also limit ACL changes to
|
|
537
|
+
* that exchange until interaction is finished.
|
|
451
538
|
*/
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
539
|
+
#delayAclUpdateFor(
|
|
540
|
+
fabricIndex: FabricIndex,
|
|
541
|
+
exchange: MessageExchange,
|
|
542
|
+
acl: AccessControlTypes.AccessControlEntry[],
|
|
543
|
+
) {
|
|
544
|
+
if (!this.internal.aclUpdateDelayed.has(fabricIndex)) {
|
|
545
|
+
logger.info("Register ACL update to be delayed for fabricIndex", fabricIndex);
|
|
458
546
|
}
|
|
459
|
-
this.internal.aclUpdateDelayed
|
|
547
|
+
this.internal.aclUpdateDelayed.set(fabricIndex, exchange);
|
|
548
|
+
this.internal.delayedAclData.set(
|
|
549
|
+
fabricIndex,
|
|
550
|
+
deepCopy(acl).filter(entry => entry.fabricIndex === fabricIndex),
|
|
551
|
+
);
|
|
460
552
|
}
|
|
461
553
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
554
|
+
/** Applies the delayed ACL update for a specific fabric index, if existing */
|
|
555
|
+
#applyDelayedAclUpdateFor(fabricIndex: FabricIndex) {
|
|
556
|
+
const updateDelayed = !!this.internal.aclUpdateDelayed.get(fabricIndex);
|
|
557
|
+
const delayedData = this.internal.delayedAclData.get(fabricIndex);
|
|
558
|
+
|
|
559
|
+
this.internal.delayedAclData.delete(fabricIndex);
|
|
560
|
+
this.internal.aclUpdateDelayed.delete(fabricIndex);
|
|
561
|
+
if (updateDelayed && delayedData !== undefined) {
|
|
562
|
+
this.env.get(FabricManager).for(fabricIndex).acl.aclList = delayedData;
|
|
465
563
|
}
|
|
466
|
-
const delayedData = deepCopy(this.internal.delayedAclData);
|
|
467
|
-
this.internal.delayedAclData = undefined;
|
|
468
|
-
logger.info("Updating ACL manager with ACL", delayedData);
|
|
469
|
-
this.aclManager.updateAccessControlList(delayedData);
|
|
470
564
|
}
|
|
471
565
|
}
|
|
472
566
|
|
|
473
567
|
export namespace AccessControlServer {
|
|
474
568
|
export class Internal {
|
|
475
|
-
/**
|
|
476
|
-
|
|
569
|
+
/** Is the cluster logic initialized? Used to block events before full initialization. */
|
|
570
|
+
initialized = false;
|
|
477
571
|
|
|
478
|
-
/**
|
|
479
|
-
|
|
572
|
+
/**
|
|
573
|
+
* When an online and potentially chunked ACL writing happens, we will delay the update and store the exchange
|
|
574
|
+
* used for the writing. With this we also verify that concurrent writes are blocked and will not mix the data.
|
|
575
|
+
*/
|
|
576
|
+
aclUpdateDelayed = new Map<FabricIndex, MessageExchange | undefined>();
|
|
480
577
|
|
|
481
|
-
/** Latest delayed data of acl */
|
|
482
|
-
delayedAclData
|
|
578
|
+
/** Latest delayed data of acl attribute */
|
|
579
|
+
delayedAclData = new Map<FabricIndex, AccessControlTypes.AccessControlEntry[]>();
|
|
483
580
|
}
|
|
581
|
+
|
|
582
|
+
export declare const ExtensionInterface: {
|
|
583
|
+
extensionEntryValidator: (extension: AccessControlTypes.AccessControlExtension) => void;
|
|
584
|
+
extensionEntryAccessCheck: (
|
|
585
|
+
aclList: AclList,
|
|
586
|
+
aclEntry: AclEntry,
|
|
587
|
+
subjectDesc: IncomingSubjectDescriptor,
|
|
588
|
+
endpoint: AclEndpointContext,
|
|
589
|
+
clusterId: ClusterId,
|
|
590
|
+
) => boolean;
|
|
591
|
+
};
|
|
484
592
|
}
|
|
@@ -8,7 +8,6 @@ import { ValueSupervisor } from "#behavior/supervision/ValueSupervisor.js";
|
|
|
8
8
|
import { CommissioningServer } from "#behavior/system/commissioning/CommissioningServer.js";
|
|
9
9
|
import { ProductDescriptionServer } from "#behavior/system/product-description/ProductDescriptionServer.js";
|
|
10
10
|
import { AccessControlServer } from "#behaviors/access-control";
|
|
11
|
-
import { AccessControl } from "#clusters/access-control";
|
|
12
11
|
import { OperationalCredentials } from "#clusters/operational-credentials";
|
|
13
12
|
import { Endpoint } from "#endpoint/Endpoint.js";
|
|
14
13
|
import { CryptoVerifyError, Logger, MatterFlowError, MaybePromise, UnexpectedDataError } from "#general";
|
|
@@ -104,15 +103,15 @@ export class OperationalCredentialsServer extends OperationalCredentialsBehavior
|
|
|
104
103
|
const session = this.session;
|
|
105
104
|
NodeSession.assert(session);
|
|
106
105
|
|
|
107
|
-
const
|
|
106
|
+
const attestationElements = TlvAttestation.encode({
|
|
108
107
|
declaration: certification.declaration,
|
|
109
108
|
attestationNonce: attestationNonce,
|
|
110
109
|
timestamp: 0,
|
|
111
110
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
111
|
+
|
|
112
|
+
const attestationSignature = await certification.sign(session, attestationElements);
|
|
113
|
+
|
|
114
|
+
return { attestationElements, attestationSignature };
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
override async csrRequest({ csrNonce, isForUpdateNoc }: OperationalCredentials.CsrRequest) {
|
|
@@ -260,14 +259,7 @@ export class OperationalCredentialsServer extends OperationalCredentialsBehavior
|
|
|
260
259
|
|
|
261
260
|
// The receiver SHALL create and add a new Access Control Entry using the CaseAdminSubject field to grant
|
|
262
261
|
// subsequent Administer access to an Administrator member of the new Fabric.
|
|
263
|
-
|
|
264
|
-
aclCluster.state.acl.push({
|
|
265
|
-
fabricIndex: fabric.fabricIndex,
|
|
266
|
-
privilege: AccessControl.AccessControlEntryPrivilege.Administer,
|
|
267
|
-
authMode: AccessControl.AccessControlEntryAuthMode.Case,
|
|
268
|
-
subjects: [caseAdminSubject],
|
|
269
|
-
targets: null, // entire node
|
|
270
|
-
});
|
|
262
|
+
await this.endpoint.act(agent => agent.get(AccessControlServer).addDefaultCaseAcl(fabric, [caseAdminSubject]));
|
|
271
263
|
|
|
272
264
|
const session = this.session;
|
|
273
265
|
NodeSession.assert(session);
|
|
@@ -355,14 +347,14 @@ export class OperationalCredentialsServer extends OperationalCredentialsBehavior
|
|
|
355
347
|
|
|
356
348
|
// Build a new Fabric with the updated NOC and ICAC
|
|
357
349
|
try {
|
|
358
|
-
const
|
|
350
|
+
const updatedFabric = await timedOp.buildUpdatedFabric(nocValue, icacValue);
|
|
359
351
|
|
|
360
352
|
// update FabricManager and Resumption records but leave current session intact
|
|
361
|
-
await timedOp.updateFabric(
|
|
353
|
+
await timedOp.updateFabric(updatedFabric);
|
|
362
354
|
|
|
363
355
|
return {
|
|
364
356
|
statusCode: OperationalCredentials.NodeOperationalCertStatus.Ok,
|
|
365
|
-
fabricIndex:
|
|
357
|
+
fabricIndex: updatedFabric.fabricIndex,
|
|
366
358
|
};
|
|
367
359
|
} catch (error) {
|
|
368
360
|
logger.info("Building fabric for updateNoc failed", error);
|
|
@@ -432,7 +424,7 @@ export class OperationalCredentialsServer extends OperationalCredentialsBehavior
|
|
|
432
424
|
try {
|
|
433
425
|
await failsafeContext.setRootCert(rootCaCertificate);
|
|
434
426
|
} catch (error) {
|
|
435
|
-
logger.info("
|
|
427
|
+
logger.info("Error installing root certificate:", error);
|
|
436
428
|
if (
|
|
437
429
|
error instanceof CryptoVerifyError ||
|
|
438
430
|
error instanceof CertificateError ||
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { NodeActivity } from "#behavior/context/NodeActivity.js";
|
|
8
8
|
import { OnlineContext } from "#behavior/context/server/OnlineContext.js";
|
|
9
9
|
import { AccessControlServer } from "#behaviors/access-control";
|
|
10
|
-
import { AccessControl as AccessControlClusterType } from "#clusters/access-control";
|
|
11
10
|
import {
|
|
12
11
|
Crypto,
|
|
13
12
|
Diagnostic,
|
|
@@ -39,7 +38,6 @@ import {
|
|
|
39
38
|
TimedRequest,
|
|
40
39
|
WriteRequest,
|
|
41
40
|
WriteResponse,
|
|
42
|
-
WriteResult,
|
|
43
41
|
} from "#protocol";
|
|
44
42
|
import {
|
|
45
43
|
DEFAULT_MAX_PATHS_PER_INVOKE,
|
|
@@ -68,10 +66,6 @@ export interface WithActivity {
|
|
|
68
66
|
[activityKey]?: NodeActivity.Activity;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
const AclClusterId = AccessControlClusterType.Complete.id;
|
|
72
|
-
const AclAttributeId = AccessControlClusterType.Complete.attributes.acl.id;
|
|
73
|
-
const AclExtensionAttributeId = AccessControlClusterType.Complete.attributes.extension.id;
|
|
74
|
-
|
|
75
69
|
export interface PeerSubscription {
|
|
76
70
|
subscriptionId: number;
|
|
77
71
|
peerAddress: PeerAddress;
|
|
@@ -139,7 +133,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
139
133
|
#activity: NodeActivity;
|
|
140
134
|
#newActivityBlocked = false;
|
|
141
135
|
#aclServer?: AccessControlServer;
|
|
142
|
-
#aclUpdateIsDelayedInExchange = new Set<MessageExchange>();
|
|
143
136
|
#serverInteraction: OnlineServerInteraction;
|
|
144
137
|
|
|
145
138
|
constructor(node: ServerNode, sessions: SessionManager) {
|
|
@@ -372,63 +365,17 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
372
365
|
);
|
|
373
366
|
}
|
|
374
367
|
|
|
375
|
-
//
|
|
376
|
-
// and is needed because Acl should not become effective during writing of the ACL itself.
|
|
377
|
-
// We check if any ACL path is written and assume that acl write will happen, so we delay the update.
|
|
378
|
-
// If nothing changed in the end this does not matter
|
|
379
|
-
for (const {
|
|
380
|
-
path: { endpointId, clusterId, attributeId },
|
|
381
|
-
} of writeRequest.writeRequests) {
|
|
382
|
-
if (
|
|
383
|
-
(endpointId === undefined || endpointId === 0) &&
|
|
384
|
-
clusterId === AclClusterId &&
|
|
385
|
-
(attributeId === AclAttributeId || attributeId === AclExtensionAttributeId)
|
|
386
|
-
) {
|
|
387
|
-
this.aclServer.aclUpdateDelayed = true;
|
|
388
|
-
this.#aclUpdateIsDelayedInExchange.add(exchange);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
let result: Awaited<WriteResult<any>>;
|
|
393
|
-
try {
|
|
394
|
-
// TODO: We still need to add multi message writes!
|
|
395
|
-
|
|
396
|
-
result = await this.#serverInteraction.write(
|
|
397
|
-
writeRequest,
|
|
398
|
-
this.#prepareOnlineContext(
|
|
399
|
-
exchange,
|
|
400
|
-
message,
|
|
401
|
-
true, // always fabric filtered
|
|
402
|
-
receivedWithinTimedInteraction,
|
|
403
|
-
),
|
|
404
|
-
);
|
|
405
|
-
} catch (error) {
|
|
406
|
-
if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
|
|
407
|
-
// Unlikely to get there at all, but make sure we handle it best we can for now
|
|
408
|
-
this.#aclUpdateIsDelayedInExchange.delete(exchange);
|
|
409
|
-
|
|
410
|
-
if (this.#aclUpdateIsDelayedInExchange.size === 0) {
|
|
411
|
-
// only that one ACl change in flight, so we can reset the delayed ACL
|
|
412
|
-
this.aclServer.resetDelayedAccessControlList();
|
|
413
|
-
} else {
|
|
414
|
-
// TODO: we should restore the delayed data just for this errored fabric?
|
|
415
|
-
logger.error("One of multiple concurrent ACL writes failed, unhandled case for now.");
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
throw error;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// We delayed the ACL update during this write transaction, so we need to update it now that anything is written
|
|
422
|
-
if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
|
|
423
|
-
this.#aclUpdateIsDelayedInExchange.delete(exchange);
|
|
368
|
+
// TODO: We still need to add multi message writes!
|
|
424
369
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
370
|
+
const result = await this.#serverInteraction.write(
|
|
371
|
+
writeRequest,
|
|
372
|
+
this.#prepareOnlineContext(
|
|
373
|
+
exchange,
|
|
374
|
+
message,
|
|
375
|
+
true, // always fabric filtered
|
|
376
|
+
receivedWithinTimedInteraction,
|
|
377
|
+
),
|
|
378
|
+
);
|
|
432
379
|
|
|
433
380
|
return {
|
|
434
381
|
writeResponses: result?.map(({ path, status, clusterStatus }) => ({
|
|
@@ -316,7 +316,7 @@ class ClusterState implements DisposableClusterProtocol {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// Emit all attributes as changed that are not omitted or quieter
|
|
319
|
-
this.#quieterObservers.on(this.#datasource.
|
|
319
|
+
this.#quieterObservers.on(this.#datasource.changed, (changes: string[], version: number) => {
|
|
320
320
|
const data = changes
|
|
321
321
|
.map(name => attributeNameToIdMap.get(name))
|
|
322
322
|
.filter(
|