@matter/node 0.14.1-alpha.0-20250607-a93593303 → 0.15.0-alpha.0-20250613-a55f991d4
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/cluster/FabricScopedDataHandler.d.ts +16 -0
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.d.ts.map +1 -0
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.js +119 -0
- package/dist/cjs/behavior/cluster/FabricScopedDataHandler.js.map +6 -0
- package/dist/cjs/behavior/cluster/index.d.ts +1 -0
- package/dist/cjs/behavior/cluster/index.d.ts.map +1 -1
- package/dist/cjs/behavior/cluster/index.js +1 -0
- package/dist/cjs/behavior/cluster/index.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/endpoint/properties/Behaviors.d.ts.map +1 -1
- package/dist/cjs/endpoint/properties/Behaviors.js +10 -0
- package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
- package/dist/cjs/node/ServerNode.d.ts +2 -2
- package/dist/cjs/node/ServerNode.d.ts.map +1 -1
- package/dist/cjs/node/ServerNode.js +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/cjs/node/server/ServerEnvironment.d.ts +3 -0
- package/dist/cjs/node/server/ServerEnvironment.d.ts.map +1 -1
- package/dist/cjs/node/server/ServerEnvironment.js +12 -2
- package/dist/cjs/node/server/ServerEnvironment.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/cluster/FabricScopedDataHandler.d.ts +16 -0
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.d.ts.map +1 -0
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.js +99 -0
- package/dist/esm/behavior/cluster/FabricScopedDataHandler.js.map +6 -0
- package/dist/esm/behavior/cluster/index.d.ts +1 -0
- package/dist/esm/behavior/cluster/index.d.ts.map +1 -1
- package/dist/esm/behavior/cluster/index.js +1 -0
- package/dist/esm/behavior/cluster/index.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/endpoint/properties/Behaviors.d.ts.map +1 -1
- package/dist/esm/endpoint/properties/Behaviors.js +10 -0
- package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
- package/dist/esm/node/ServerNode.d.ts +2 -2
- package/dist/esm/node/ServerNode.d.ts.map +1 -1
- package/dist/esm/node/ServerNode.js +3 -3
- package/dist/esm/node/ServerNode.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/dist/esm/node/server/ServerEnvironment.d.ts +3 -0
- package/dist/esm/node/server/ServerEnvironment.d.ts.map +1 -1
- package/dist/esm/node/server/ServerEnvironment.js +12 -2
- package/dist/esm/node/server/ServerEnvironment.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/cluster/FabricScopedDataHandler.ts +142 -0
- package/src/behavior/cluster/index.ts +1 -0
- 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/endpoint/properties/Behaviors.ts +12 -1
- package/src/node/ServerNode.ts +3 -3
- package/src/node/server/InteractionServer.ts +10 -63
- package/src/node/server/ProtocolService.ts +1 -1
- package/src/node/server/ServerEnvironment.ts +16 -2
|
@@ -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 ||
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Behavior } from "#behavior/Behavior.js";
|
|
8
8
|
import type { ClusterBehavior } from "#behavior/cluster/ClusterBehavior.js";
|
|
9
|
+
import { limitEndpointAttributeDataToAllowedFabrics } from "#behavior/cluster/FabricScopedDataHandler.js";
|
|
9
10
|
import { ActionContext } from "#behavior/context/ActionContext.js";
|
|
10
11
|
import { NodeActivity } from "#behavior/context/NodeActivity.js";
|
|
11
12
|
import { OfflineContext } from "#behavior/context/server/OfflineContext.js";
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
} from "#general";
|
|
27
28
|
import { FeatureSet } from "#model";
|
|
28
29
|
import { ProtocolService } from "#node/server/ProtocolService.js";
|
|
29
|
-
import { ClusterTypeProtocol, Val } from "#protocol";
|
|
30
|
+
import { ClusterTypeProtocol, FabricManager, Val } from "#protocol";
|
|
30
31
|
import { ClusterType, VoidSchema } from "#types";
|
|
31
32
|
import { DescriptorServer } from "../../behaviors/descriptor/DescriptorServer.js";
|
|
32
33
|
import type { Agent } from "../Agent.js";
|
|
@@ -221,6 +222,16 @@ export class Behaviors {
|
|
|
221
222
|
promise = endpointInitializer.behaviorsInitialized(agent);
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
if (this.#endpoint.env.has(FabricManager)) {
|
|
226
|
+
const fabricIndices = this.#endpoint.env.get(FabricManager).fabrics.map(fabric => fabric.fabricIndex);
|
|
227
|
+
if (fabricIndices.length > 0) {
|
|
228
|
+
// Make sure the state on startup only includes allowed Fabric scoped data
|
|
229
|
+
return limitEndpointAttributeDataToAllowedFabrics(this.#endpoint, fabricIndices).then(
|
|
230
|
+
() => promise,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
224
235
|
return promise;
|
|
225
236
|
};
|
|
226
237
|
|
package/src/node/ServerNode.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { SessionsBehavior } from "#behavior/system/sessions/SessionsBehavior.js"
|
|
|
15
15
|
import { SubscriptionBehavior } from "#behavior/system/subscription/SubscriptionBehavior.js";
|
|
16
16
|
import { Endpoint } from "#endpoint/Endpoint.js";
|
|
17
17
|
import type { Environment } from "#general";
|
|
18
|
-
import { Construction, DiagnosticSource, Identity, MatterError
|
|
18
|
+
import { asyncNew, Construction, DiagnosticSource, errorOf, Identity, MatterError } from "#general";
|
|
19
19
|
import { FabricManager, Interactable, OccurrenceManager, ServerInteraction, SessionManager } from "#protocol";
|
|
20
20
|
import { RootEndpoint as BaseRootEndpoint } from "../endpoints/root.js";
|
|
21
21
|
import { Node } from "./Node.js";
|
|
@@ -184,9 +184,9 @@ export class ServerNode<T extends ServerNode.RootEndpoint = ServerNode.RootEndpo
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
* By default on factory reset we erase all stored data.
|
|
187
|
+
* By default, on factory reset we erase all stored data.
|
|
188
188
|
*
|
|
189
|
-
* If this is inappropriate for your application you may override to alter the behavior.
|
|
189
|
+
* If this is inappropriate for your application, you may override to alter the behavior. Matter requires that all
|
|
190
190
|
* "security- and privacy-related data and key material" is removed on factory reset.
|
|
191
191
|
*
|
|
192
192
|
* @see {@link MatterSpecification.v12.Core} § 13.4
|