@matter/protocol 0.16.0-alpha.0-20250906-463912bd0 → 0.16.0-alpha.0-20250909-aecad94f3

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.
Files changed (61) hide show
  1. package/dist/cjs/action/Interactable.d.ts +2 -2
  2. package/dist/cjs/action/Interactable.d.ts.map +1 -1
  3. package/dist/cjs/action/server/AccessControl.d.ts +43 -15
  4. package/dist/cjs/action/server/AccessControl.d.ts.map +1 -1
  5. package/dist/cjs/action/server/AccessControl.js +47 -36
  6. package/dist/cjs/action/server/AccessControl.js.map +1 -1
  7. package/dist/cjs/action/server/AttributeReadResponse.d.ts.map +1 -1
  8. package/dist/cjs/action/server/AttributeReadResponse.js +24 -22
  9. package/dist/cjs/action/server/AttributeReadResponse.js.map +1 -1
  10. package/dist/cjs/action/server/AttributeWriteResponse.d.ts.map +1 -1
  11. package/dist/cjs/action/server/AttributeWriteResponse.js +38 -26
  12. package/dist/cjs/action/server/AttributeWriteResponse.js.map +1 -1
  13. package/dist/cjs/action/server/CommandInvokeResponse.d.ts.map +1 -1
  14. package/dist/cjs/action/server/CommandInvokeResponse.js +28 -19
  15. package/dist/cjs/action/server/CommandInvokeResponse.js.map +1 -1
  16. package/dist/cjs/action/server/EventReadResponse.d.ts.map +1 -1
  17. package/dist/cjs/action/server/EventReadResponse.js +22 -20
  18. package/dist/cjs/action/server/EventReadResponse.js.map +1 -1
  19. package/dist/cjs/fabric/Fabric.d.ts +1 -1
  20. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  21. package/dist/cjs/fabric/Fabric.js +4 -4
  22. package/dist/cjs/fabric/Fabric.js.map +1 -1
  23. package/dist/cjs/interaction/FabricAccessControl.d.ts +2 -2
  24. package/dist/cjs/interaction/FabricAccessControl.d.ts.map +1 -1
  25. package/dist/cjs/interaction/FabricAccessControl.js +0 -6
  26. package/dist/cjs/interaction/FabricAccessControl.js.map +1 -1
  27. package/dist/esm/action/Interactable.d.ts +2 -2
  28. package/dist/esm/action/Interactable.d.ts.map +1 -1
  29. package/dist/esm/action/server/AccessControl.d.ts +43 -15
  30. package/dist/esm/action/server/AccessControl.d.ts.map +1 -1
  31. package/dist/esm/action/server/AccessControl.js +48 -37
  32. package/dist/esm/action/server/AccessControl.js.map +1 -1
  33. package/dist/esm/action/server/AttributeReadResponse.d.ts.map +1 -1
  34. package/dist/esm/action/server/AttributeReadResponse.js +25 -23
  35. package/dist/esm/action/server/AttributeReadResponse.js.map +1 -1
  36. package/dist/esm/action/server/AttributeWriteResponse.d.ts.map +1 -1
  37. package/dist/esm/action/server/AttributeWriteResponse.js +39 -27
  38. package/dist/esm/action/server/AttributeWriteResponse.js.map +1 -1
  39. package/dist/esm/action/server/CommandInvokeResponse.d.ts.map +1 -1
  40. package/dist/esm/action/server/CommandInvokeResponse.js +29 -20
  41. package/dist/esm/action/server/CommandInvokeResponse.js.map +1 -1
  42. package/dist/esm/action/server/EventReadResponse.d.ts.map +1 -1
  43. package/dist/esm/action/server/EventReadResponse.js +23 -21
  44. package/dist/esm/action/server/EventReadResponse.js.map +1 -1
  45. package/dist/esm/fabric/Fabric.d.ts +1 -1
  46. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  47. package/dist/esm/fabric/Fabric.js +4 -4
  48. package/dist/esm/fabric/Fabric.js.map +1 -1
  49. package/dist/esm/interaction/FabricAccessControl.d.ts +2 -2
  50. package/dist/esm/interaction/FabricAccessControl.d.ts.map +1 -1
  51. package/dist/esm/interaction/FabricAccessControl.js +0 -6
  52. package/dist/esm/interaction/FabricAccessControl.js.map +1 -1
  53. package/package.json +6 -6
  54. package/src/action/Interactable.ts +2 -2
  55. package/src/action/server/AccessControl.ts +90 -53
  56. package/src/action/server/AttributeReadResponse.ts +35 -29
  57. package/src/action/server/AttributeWriteResponse.ts +50 -38
  58. package/src/action/server/CommandInvokeResponse.ts +33 -24
  59. package/src/action/server/EventReadResponse.ts +25 -21
  60. package/src/fabric/Fabric.ts +4 -4
  61. package/src/interaction/FabricAccessControl.ts +2 -8
@@ -4,13 +4,43 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import { ImplementationError } from "#general";
7
8
  import { Access, AccessLevel, DataModelPath, ElementTag, Schema, ValueModel } from "#model";
8
- import { ClusterId, EndpointNumber, FabricIndex, StatusCode, SubjectId } from "#types";
9
+ import { ClusterId, EndpointNumber, FabricIndex, Status } from "#types";
9
10
  import { InvokeError, ReadError, SchemaImplementationError, WriteError } from "../errors.js";
10
11
  import { Subject } from "./Subject.js";
11
12
 
12
13
  const cache = new WeakMap<Schema, AccessControl>();
13
14
 
15
+ /**
16
+ * Confirm that an access control session (or some variante thereof) is a {@link AccessControl.RemoteActorSession}.
17
+ */
18
+ export function hasRemoteActor<T extends undefined | AccessControl.Session>(
19
+ session: T,
20
+ ): session is Exclude<T, undefined | { subject?: undefined }> {
21
+ return session?.subject !== undefined;
22
+ }
23
+
24
+ /**
25
+ * Throws if a session is not a {@link AccessControl.RemoteActorSession}.
26
+ */
27
+ export function assertRemoteActor<T extends undefined | AccessControl.Session>(
28
+ session: T,
29
+ ): asserts session is Exclude<T, undefined | { subject?: undefined }> {
30
+ if (!hasRemoteActor(session)) {
31
+ throw new ImplementationError("This operation requires an authenticated remote session");
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Confirm that an access control session (or some variante thereof) is a {@link AccessControl.LocalActorSession}.
37
+ */
38
+ export function hasLocalActor<T extends undefined | AccessControl.Session>(
39
+ session: T,
40
+ ): session is Exclude<T, { subject: Subject }> {
41
+ return session?.subject === undefined;
42
+ }
43
+
14
44
  /**
15
45
  * Enforces access control for a specific schema.
16
46
  */
@@ -86,13 +116,17 @@ export namespace AccessControl {
86
116
 
87
117
  /**
88
118
  * A function that asserts access control requirements are met.
119
+ *
120
+ * If {@link session} is undefined the function does not enforce access controls.
89
121
  */
90
- export type Assertion = (session: Session, location: Location) => void;
122
+ export type Assertion = (session: Session | undefined, location: Location) => void;
91
123
 
92
124
  /**
93
125
  * A function that returns true if access control requirements are met.
126
+ *
127
+ * If {@link session} is undefined the function does not enforce access controls.
94
128
  */
95
- export type Verification = (session: Session, location: Location) => boolean;
129
+ export type Verification = (session: Session | undefined, location: Location) => boolean;
96
130
 
97
131
  /**
98
132
  * Metadata that varies with position in the data model.
@@ -121,9 +155,9 @@ export namespace AccessControl {
121
155
  }
122
156
 
123
157
  /**
124
- * Authorization metadata that varies with session.
158
+ * Authorization metadata that varies by remote actor.
125
159
  */
126
- export interface Session {
160
+ export interface RemoteActorSession {
127
161
  /**
128
162
  * Determine whether authorized client has authority at a specific location.
129
163
  */
@@ -131,14 +165,16 @@ export namespace AccessControl {
131
165
 
132
166
  /**
133
167
  * The fabric of the authorized client.
168
+ *
169
+ * For PASE sessions this will be {@link FabricIndex.NO_FABRIC}.
134
170
  */
135
- readonly fabric?: FabricIndex;
171
+ readonly fabric: FabricIndex;
136
172
 
137
173
  /**
138
- * The authenticated {@link SubjectId}s for online sessions. This includes the relevant Node Id, Group ID and
139
- * also potential relevant Case Authenticated Tags.
174
+ * The authenticated remote actor. This includes the relevant Node Id, Group ID and also potential relevant Case
175
+ * Authenticated Tags.
140
176
  */
141
- readonly subject?: Subject;
177
+ readonly subject: Subject;
142
178
 
143
179
  /**
144
180
  * If this is true, fabric-scoped lists are filtered to the accessing fabric.
@@ -155,16 +191,21 @@ export namespace AccessControl {
155
191
  * active.
156
192
  */
157
193
  readonly command?: boolean;
158
-
159
- /**
160
- * If this is true then access levels are not enforced and all values are read/write. Datatypes are still
161
- * enforced.
162
- *
163
- * Tracks "offline" rather than "online" because this makes the safer mode (full enforcement) the default.
164
- */
165
- offline?: boolean;
166
194
  }
167
195
 
196
+ /**
197
+ * A local actor session has no authenticated subject and access controls are bypassed.
198
+ */
199
+ export type LocalActorSession = {
200
+ fabric?: undefined;
201
+ subject?: undefined;
202
+ };
203
+
204
+ /**
205
+ * The accessing session.
206
+ */
207
+ export type Session = LocalActorSession | RemoteActorSession;
208
+
168
209
  /**
169
210
  * Authority status.
170
211
  */
@@ -200,7 +241,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
200
241
  const limits = limitsFor(schema);
201
242
 
202
243
  let mayRead: AccessControl.Verification = (session, location) => {
203
- if (session.offline || session.command) {
244
+ if (hasLocalActor(session) || session.command) {
204
245
  return true;
205
246
  }
206
247
 
@@ -208,7 +249,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
208
249
  };
209
250
 
210
251
  let mayWrite: AccessControl.Verification = (session, location) => {
211
- if (session.offline || session.command) {
252
+ if (hasLocalActor(session) || session.command) {
212
253
  return true;
213
254
  }
214
255
 
@@ -216,7 +257,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
216
257
  };
217
258
 
218
259
  let authorizeRead: AccessControl.Assertion = (session, location) => {
219
- if (session.offline || session.command) {
260
+ if (hasLocalActor(session) || session.command) {
220
261
  return;
221
262
  }
222
263
 
@@ -224,11 +265,11 @@ function dataEnforcerFor(schema: Schema): AccessControl {
224
265
  return;
225
266
  }
226
267
 
227
- throw new ReadError(location, "Permission denied", StatusCode.UnsupportedAccess);
268
+ throw new ReadError(location, "Permission denied", Status.UnsupportedAccess);
228
269
  };
229
270
 
230
271
  let authorizeWrite: AccessControl.Assertion = (session, location) => {
231
- if (session.offline || session.command) {
272
+ if (hasLocalActor(session) || session.command) {
232
273
  return;
233
274
  }
234
275
 
@@ -236,7 +277,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
236
277
  return;
237
278
  }
238
279
 
239
- throw new WriteError(location, "Permission denied", StatusCode.UnsupportedAccess);
280
+ throw new WriteError(location, "Permission denied", Status.UnsupportedAccess);
240
281
  };
241
282
 
242
283
  if (limits.timed) {
@@ -244,18 +285,18 @@ function dataEnforcerFor(schema: Schema): AccessControl {
244
285
  const wrappedMayWrite = mayWrite;
245
286
 
246
287
  authorizeWrite = (session, location) => {
247
- if (!session.offline && !session.timed) {
288
+ if (hasRemoteActor(session) && !session.timed) {
248
289
  throw new WriteError(
249
290
  location,
250
291
  "Permission denied because interaction is not timed",
251
- StatusCode.NeedsTimedInteraction,
292
+ Status.NeedsTimedInteraction,
252
293
  );
253
294
  }
254
295
  wrappedAuthorizeWrite?.(session, location);
255
296
  };
256
297
 
257
298
  mayWrite = (session, location) => {
258
- if (!session.offline && !session.timed) {
299
+ if (hasRemoteActor(session) && !session.timed) {
259
300
  return false;
260
301
  }
261
302
 
@@ -270,24 +311,20 @@ function dataEnforcerFor(schema: Schema): AccessControl {
270
311
  const wrappedMayWrite = mayWrite;
271
312
 
272
313
  authorizeRead = (session, location) => {
273
- if (session.offline || session.command) {
314
+ if (hasLocalActor(session) || session.command) {
274
315
  return;
275
316
  }
276
317
 
277
318
  if (session.fabricFiltered) {
278
- if (session.fabric === undefined) {
279
- throw new ReadError(
280
- location,
281
- "Permission denied: No accessing fabric",
282
- StatusCode.UnsupportedAccess,
283
- );
319
+ if (!session.fabric) {
320
+ throw new ReadError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
284
321
  }
285
322
 
286
323
  if (location?.owningFabric !== undefined && location.owningFabric !== session.fabric) {
287
324
  throw new ReadError(
288
325
  location,
289
326
  "Permission denied: Owning/accessing fabric mismatch",
290
- StatusCode.UnsupportedAccess,
327
+ Status.UnsupportedAccess,
291
328
  );
292
329
  }
293
330
  }
@@ -296,11 +333,11 @@ function dataEnforcerFor(schema: Schema): AccessControl {
296
333
  };
297
334
 
298
335
  mayRead = (session, location) => {
299
- if (session.offline || session.command) {
336
+ if (hasLocalActor(session) || session.command) {
300
337
  return true;
301
338
  }
302
339
 
303
- if (session.fabric === undefined) {
340
+ if (!session.fabric) {
304
341
  return false;
305
342
  }
306
343
 
@@ -312,12 +349,12 @@ function dataEnforcerFor(schema: Schema): AccessControl {
312
349
  };
313
350
 
314
351
  authorizeWrite = (session, location) => {
315
- if (session.offline || session.command) {
352
+ if (hasLocalActor(session) || session.command) {
316
353
  return;
317
354
  }
318
355
 
319
- if (session.fabric === undefined) {
320
- throw new WriteError(location, "Permission denied: No accessing fabric", StatusCode.UnsupportedAccess);
356
+ if (!session.fabric) {
357
+ throw new WriteError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
321
358
  }
322
359
 
323
360
  if (location?.owningFabric !== undefined && location.owningFabric !== session.fabric) {
@@ -328,11 +365,11 @@ function dataEnforcerFor(schema: Schema): AccessControl {
328
365
  };
329
366
 
330
367
  mayWrite = (session, location) => {
331
- if (session.offline || session.command) {
368
+ if (hasLocalActor(session) || session.command) {
332
369
  return true;
333
370
  }
334
371
 
335
- if (session.fabric === undefined) {
372
+ if (!session.fabric) {
336
373
  return false;
337
374
  }
338
375
 
@@ -346,7 +383,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
346
383
 
347
384
  if (!limits.readable) {
348
385
  authorizeRead = (session, location) => {
349
- if (session.offline || session.command) {
386
+ if (hasLocalActor(session) || session.command) {
350
387
  return;
351
388
  }
352
389
 
@@ -354,20 +391,20 @@ function dataEnforcerFor(schema: Schema): AccessControl {
354
391
  };
355
392
 
356
393
  mayRead = session => {
357
- return !!session.offline || !!session.command;
394
+ return hasLocalActor(session) || !!session.command;
358
395
  };
359
396
  }
360
397
 
361
398
  if (!limits.writable) {
362
399
  authorizeWrite = (session, location) => {
363
- if (session.offline || session.command) {
400
+ if (hasLocalActor(session) || session.command) {
364
401
  return;
365
402
  }
366
403
  throw new WriteError(location, "Permission denied: Value is read-only");
367
404
  };
368
405
 
369
406
  mayWrite = session => {
370
- return !!session.offline || !!session.command;
407
+ return hasLocalActor(session) || !!session.command;
371
408
  };
372
409
  }
373
410
 
@@ -378,7 +415,7 @@ function dataEnforcerFor(schema: Schema): AccessControl {
378
415
  authorizeWrite,
379
416
  mayWrite,
380
417
 
381
- authorizeInvoke(_session: AccessControl.Session, location: AccessControl.Location) {
418
+ authorizeInvoke(_session: undefined | AccessControl.Session, location: AccessControl.Location) {
382
419
  throw new SchemaImplementationError(location, "Permission denied: Invoke request but non-command schema");
383
420
  },
384
421
 
@@ -413,7 +450,7 @@ function commandEnforcerFor(schema: Schema): AccessControl {
413
450
  },
414
451
 
415
452
  authorizeInvoke(session, location) {
416
- if (session.offline) {
453
+ if (hasLocalActor(session)) {
417
454
  return;
418
455
  }
419
456
 
@@ -425,23 +462,23 @@ function commandEnforcerFor(schema: Schema): AccessControl {
425
462
  throw new InvokeError(
426
463
  location,
427
464
  "Invoke attempt without required timed context",
428
- StatusCode.TimedRequestMismatch,
465
+ Status.TimedRequestMismatch,
429
466
  );
430
467
  }
431
468
 
432
- if (fabric && session.fabric === undefined) {
433
- throw new WriteError(location, "Permission denied: No accessing fabric", StatusCode.UnsupportedAccess);
469
+ if (fabric && !session.fabric) {
470
+ throw new WriteError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
434
471
  }
435
472
 
436
473
  if (session.authorityAt(limits.writeLevel, location) === AccessControl.Authority.Granted) {
437
474
  return;
438
475
  }
439
476
 
440
- throw new InvokeError(location, "Permission denied", StatusCode.UnsupportedAccess);
477
+ throw new InvokeError(location, "Permission denied", Status.UnsupportedAccess);
441
478
  },
442
479
 
443
480
  mayInvoke(session, location) {
444
- if (session.offline) {
481
+ if (hasLocalActor(session)) {
445
482
  return true;
446
483
  }
447
484
 
@@ -453,7 +490,7 @@ function commandEnforcerFor(schema: Schema): AccessControl {
453
490
  return false;
454
491
  }
455
492
 
456
- if (fabric && session.fabric === undefined) {
493
+ if (fabric && !session.fabric) {
457
494
  return false;
458
495
  }
459
496
 
@@ -8,7 +8,7 @@ import { InteractionSession } from "#action/Interactable.js";
8
8
  import { AttributeTypeProtocol, ClusterProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
9
9
  import { Read } from "#action/request/Read.js";
10
10
  import { ReadResult } from "#action/response/ReadResult.js";
11
- import { AccessControl } from "#action/server/AccessControl.js";
11
+ import { AccessControl, hasLocalActor, hasRemoteActor } from "#action/server/AccessControl.js";
12
12
  import { DataResponse, FallbackLimits, WildcardPathFlagsCodec } from "#action/server/DataResponse.js";
13
13
  import { Val } from "#action/Val.js";
14
14
  import { Diagnostic, InternalError, Logger } from "#general";
@@ -64,7 +64,7 @@ export class AttributeReadResponse<
64
64
  }
65
65
 
66
66
  *process({ dataVersionFilters, attributeRequests }: Read.Attributes): Generator<ReadResult.Chunk, void, void> {
67
- const nodeId = this.session.fabric === undefined ? NodeId.UNSPECIFIED_NODE_ID : this.nodeId;
67
+ const nodeId = hasLocalActor(this.session) ? NodeId.UNSPECIFIED_NODE_ID : this.nodeId;
68
68
 
69
69
  // Index versions
70
70
  if (dataVersionFilters?.length) {
@@ -204,32 +204,37 @@ export class AttributeReadResponse<
204
204
  limits = attribute.limits;
205
205
  }
206
206
 
207
- // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
208
- // We need some fallback location if cluster is not defined
209
- const location = {
210
- ...(cluster?.location ?? {
211
- path: DataModelPath.none,
212
- endpoint: endpointId,
213
- cluster: clusterId,
214
- }),
215
- owningFabric: this.session.fabric,
216
- };
217
- const permission = this.session.authorityAt(limits.readLevel, location);
218
- switch (permission) {
219
- case AccessControl.Authority.Granted:
220
- break;
221
-
222
- case AccessControl.Authority.Unauthorized:
223
- this.addStatus(path, Status.UnsupportedAccess);
224
- return;
225
-
226
- case AccessControl.Authority.Restricted:
227
- this.addStatus(path, Status.AccessRestricted);
228
- return;
229
-
230
- default:
231
- throw new InternalError(`Unsupported authorization state ${permission}`);
207
+ if (hasRemoteActor(this.session)) {
208
+ // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
209
+ // We need some fallback location if cluster is not defined
210
+ const location: AccessControl.Location = {
211
+ ...(cluster?.location ?? {
212
+ path: DataModelPath.none,
213
+ endpoint: endpointId,
214
+ cluster: clusterId,
215
+ }),
216
+ owningFabric: this.session.fabric,
217
+ };
218
+
219
+ const permission = this.session.authorityAt(limits.readLevel, location);
220
+
221
+ switch (permission) {
222
+ case AccessControl.Authority.Granted:
223
+ break;
224
+
225
+ case AccessControl.Authority.Unauthorized:
226
+ this.addStatus(path, Status.UnsupportedAccess);
227
+ return;
228
+
229
+ case AccessControl.Authority.Restricted:
230
+ this.addStatus(path, Status.AccessRestricted);
231
+ return;
232
+
233
+ default:
234
+ throw new InternalError(`Unsupported authorization state ${permission}`);
235
+ }
232
236
  }
237
+
233
238
  if (endpoint === undefined) {
234
239
  this.addStatus(path, Status.UnsupportedEndpoint);
235
240
  return;
@@ -380,8 +385,9 @@ export class AttributeReadResponse<
380
385
 
381
386
  if (
382
387
  !attribute.limits.readable ||
383
- this.session.authorityAt(attribute.limits.readLevel, this.#guardedCurrentCluster.location) !==
384
- AccessControl.Authority.Granted
388
+ (hasRemoteActor(this.session) &&
389
+ this.session.authorityAt(attribute.limits.readLevel, this.#guardedCurrentCluster.location) !==
390
+ AccessControl.Authority.Granted)
385
391
  ) {
386
392
  return;
387
393
  }
@@ -8,7 +8,7 @@ import { InteractionSession } from "#action/Interactable.js";
8
8
  import { AttributeTypeProtocol, ClusterProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
9
9
  import { Write } from "#action/request/Write.js";
10
10
  import { WriteResult } from "#action/response/WriteResult.js";
11
- import { AccessControl } from "#action/server/AccessControl.js";
11
+ import { AccessControl, hasRemoteActor } from "#action/server/AccessControl.js";
12
12
  import { DataResponse, FallbackLimits } from "#action/server/DataResponse.js";
13
13
  import { Diagnostic, InternalError, Logger } from "#general";
14
14
  import { AttributeModel, DataModelPath, ElementTag, FabricIndex as FabricIndexField } from "#model";
@@ -190,30 +190,32 @@ export class AttributeWriteResponse<
190
190
  limits = attribute.limits;
191
191
  }
192
192
 
193
- // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
194
- // We need some fallback location if cluster is not defined
195
- const location = {
196
- ...(cluster?.location ?? {
197
- path: DataModelPath.none,
198
- endpoint: endpointId,
199
- cluster: clusterId,
200
- }),
201
- owningFabric: this.session.fabric,
202
- };
203
-
204
- const permission = this.session.authorityAt(limits.writeLevel, location);
205
- switch (permission) {
206
- case AccessControl.Authority.Granted:
207
- break;
208
-
209
- case AccessControl.Authority.Unauthorized:
210
- return this.#asStatus(path, Status.UnsupportedAccess);
211
-
212
- case AccessControl.Authority.Restricted:
213
- return this.#asStatus(path, Status.AccessRestricted);
214
-
215
- default:
216
- throw new InternalError(`Unsupported authorization state ${permission}`);
193
+ if (hasRemoteActor(this.session)) {
194
+ // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
195
+ // We need some fallback location if cluster is not defined
196
+ const location = {
197
+ ...(cluster?.location ?? {
198
+ path: DataModelPath.none,
199
+ endpoint: endpointId,
200
+ cluster: clusterId,
201
+ }),
202
+ owningFabric: this.session.fabric,
203
+ };
204
+
205
+ const permission = this.session.authorityAt(limits.writeLevel, location);
206
+ switch (permission) {
207
+ case AccessControl.Authority.Granted:
208
+ break;
209
+
210
+ case AccessControl.Authority.Unauthorized:
211
+ return this.#asStatus(path, Status.UnsupportedAccess);
212
+
213
+ case AccessControl.Authority.Restricted:
214
+ return this.#asStatus(path, Status.AccessRestricted);
215
+
216
+ default:
217
+ throw new InternalError(`Unsupported authorization state ${permission}`);
218
+ }
217
219
  }
218
220
 
219
221
  if (endpoint === undefined) {
@@ -235,13 +237,15 @@ export class AttributeWriteResponse<
235
237
  // see https://github.com/project-chip/connectedhomeip/issues/33735
236
238
  // We have patched our tests for now
237
239
 
238
- if (limits.timed && !this.session.timed) {
239
- this.#errorCount++;
240
- return this.#asStatus(path, Status.NeedsTimedInteraction);
241
- }
242
- if (limits.fabricScoped && this.session.fabric === undefined) {
243
- this.#errorCount++;
244
- return this.#asStatus(path, Status.UnsupportedAccess);
240
+ if (hasRemoteActor(this.session)) {
241
+ if (limits.timed && !this.session.timed) {
242
+ this.#errorCount++;
243
+ return this.#asStatus(path, Status.NeedsTimedInteraction);
244
+ }
245
+ if (limits.fabricScoped && !this.session.fabric) {
246
+ this.#errorCount++;
247
+ return this.#asStatus(path, Status.UnsupportedAccess);
248
+ }
245
249
  }
246
250
 
247
251
  if (version !== undefined && version !== cluster.version) {
@@ -321,15 +325,23 @@ export class AttributeWriteResponse<
321
325
  return;
322
326
  }
323
327
 
324
- if (
325
- !attribute.limits.writable ||
326
- this.session.authorityAt(attribute.limits.readLevel, this.#guardedCurrentCluster.location) !==
327
- AccessControl.Authority.Granted ||
328
- (attribute.limits.timed && !this.session.timed)
329
- ) {
328
+ if (!attribute.limits.writable) {
330
329
  return;
331
330
  }
332
331
 
332
+ if (hasRemoteActor(this.session)) {
333
+ if (
334
+ this.session.authorityAt(attribute.limits.readLevel, this.#guardedCurrentCluster.location) !==
335
+ AccessControl.Authority.Granted
336
+ ) {
337
+ return;
338
+ }
339
+
340
+ if (attribute.limits.timed && !this.session.timed) {
341
+ return;
342
+ }
343
+ }
344
+
333
345
  return this.writeValue(
334
346
  attribute,
335
347
  {
@@ -8,7 +8,7 @@ import { InteractionSession } from "#action/Interactable.js";
8
8
  import { CommandInvokeHandler, CommandTypeProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
9
9
  import { Invoke } from "#action/request/Invoke.js";
10
10
  import { InvokeResult } from "#action/response/InvokeResult.js";
11
- import { AccessControl } from "#action/server/AccessControl.js";
11
+ import { AccessControl, hasRemoteActor } from "#action/server/AccessControl.js";
12
12
  import { DataResponse, FallbackLimits } from "#action/server/DataResponse.js";
13
13
  import { Diagnostic, InternalError, Logger } from "#general";
14
14
  import { CommandModel, DataModelPath, ElementTag, FabricIndex as FabricIndexField } from "#model";
@@ -228,19 +228,21 @@ export class CommandInvokeResponse<
228
228
  owningFabric: this.session.fabric,
229
229
  };
230
230
 
231
- const permission = this.session.authorityAt(limits.writeLevel, location);
232
- switch (permission) {
233
- case AccessControl.Authority.Granted:
234
- break;
231
+ if (hasRemoteActor(this.session)) {
232
+ const permission = this.session.authorityAt(limits.writeLevel, location);
233
+ switch (permission) {
234
+ case AccessControl.Authority.Granted:
235
+ break;
235
236
 
236
- case AccessControl.Authority.Unauthorized:
237
- return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
237
+ case AccessControl.Authority.Unauthorized:
238
+ return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
238
239
 
239
- case AccessControl.Authority.Restricted:
240
- return this.#addStatus(path, commandRef, Status.AccessRestricted);
240
+ case AccessControl.Authority.Restricted:
241
+ return this.#addStatus(path, commandRef, Status.AccessRestricted);
241
242
 
242
- default:
243
- throw new InternalError(`Unsupported authorization state ${permission}`);
243
+ default:
244
+ throw new InternalError(`Unsupported authorization state ${permission}`);
245
+ }
244
246
  }
245
247
 
246
248
  if (endpoint === undefined) {
@@ -253,14 +255,16 @@ export class CommandInvokeResponse<
253
255
  return this.#addStatus(path, commandRef, Status.UnsupportedCommand);
254
256
  }
255
257
 
256
- if (limits.fabricScoped && this.session.fabric === undefined) {
257
- this.#errorCount++;
258
- return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
259
- }
258
+ if (hasRemoteActor(this.session)) {
259
+ if (limits.fabricScoped && !this.session.fabric) {
260
+ this.#errorCount++;
261
+ return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
262
+ }
260
263
 
261
- if (limits.timed && !this.session.timed) {
262
- this.#errorCount++;
263
- return this.#addStatus(path, commandRef, Status.NeedsTimedInteraction);
264
+ if (limits.timed && !this.session.timed) {
265
+ this.#errorCount++;
266
+ return this.#addStatus(path, commandRef, Status.NeedsTimedInteraction);
267
+ }
264
268
  }
265
269
 
266
270
  // This path contributes an command value
@@ -308,12 +312,17 @@ export class CommandInvokeResponse<
308
312
 
309
313
  const command = cluster.type.commands[commandId];
310
314
  if (command !== undefined) {
311
- if (
312
- this.session.authorityAt(command.limits.writeLevel, cluster.location) !==
313
- AccessControl.Authority.Granted ||
314
- (command.limits.timed && !this.session.timed)
315
- ) {
316
- return;
315
+ if (hasRemoteActor(this.session)) {
316
+ if (
317
+ this.session.authorityAt(command.limits.writeLevel, cluster.location) !==
318
+ AccessControl.Authority.Granted
319
+ ) {
320
+ return;
321
+ }
322
+
323
+ if (command.limits.timed && !this.session.timed) {
324
+ return;
325
+ }
317
326
  }
318
327
 
319
328
  await this.#invokeCommand(