node-opcua-address-space 2.164.2 → 2.165.0

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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { assert } from "node-opcua-assert";
6
- import { CertificateInternals, exploreCertificate } from "node-opcua-crypto/web";
6
+ import { Certificate, CertificateInternals, exploreCertificate } from "node-opcua-crypto/web";
7
7
  import { AccessRestrictionsFlag, allPermissions, AttributeIds, PermissionFlag } from "node-opcua-data-model";
8
8
  import { PreciseClock } from "node-opcua-date-time";
9
9
  import { NodeId, NodeIdLike, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
@@ -99,8 +99,22 @@ export interface IUserManager {
99
99
  */
100
100
  getUserRoles?: (user: string) => NodeId[];
101
101
  }
102
+
103
+ /**
104
+ * A temporary override for role resolution.
105
+ *
106
+ * When set on the server, `getUserRoles` is called
107
+ * **before** the default `userManager`. Returning
108
+ * a `NodeId[]` overrides the roles; returning `null`
109
+ * falls through to the default resolution.
110
+ */
111
+ export interface IRolePolicyOverride {
112
+ getUserRoles(username: string): NodeId[] | null;
113
+ }
114
+
102
115
  export interface IServerBase {
103
116
  userManager?: IUserManager;
117
+ rolePolicyOverride?: IRolePolicyOverride | null;
104
118
  }
105
119
 
106
120
  export interface SessionContextOptions {
@@ -206,6 +220,33 @@ export class SessionContext implements ISessionContext {
206
220
  this.currentTime = undefined;
207
221
  }
208
222
 
223
+ /**
224
+ * The client's application-instance certificate,
225
+ * or `null` if no secure channel is available.
226
+ */
227
+ public get clientCertificate(): Certificate | null {
228
+ return this.session?.channel?.clientCertificate ?? null;
229
+ }
230
+
231
+ /**
232
+ * The application URI extracted from the client
233
+ * certificate's SubjectAltName, or `null` if
234
+ * no certificate is available.
235
+ */
236
+ public get clientApplicationUri(): string | null {
237
+ const cert = this.clientCertificate;
238
+ if (!cert) {
239
+ return null;
240
+ }
241
+ try {
242
+ const info = exploreCertificate(cert);
243
+ const san = info.tbsCertificate.extensions?.subjectAltName;
244
+ return san?.uniformResourceIdentifier?.[0] ?? null;
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+
209
250
  /**
210
251
  * getCurrentUserRoles
211
252
  *
@@ -228,6 +269,15 @@ export class SessionContext implements ISessionContext {
228
269
 
229
270
  const username = getUserName(userIdentityToken);
230
271
 
272
+ // --- US-028: check role policy override first ---
273
+ if (this.server?.rolePolicyOverride) {
274
+ const overriddenRoles = this.server.rolePolicyOverride.getUserRoles(username);
275
+ if (overriddenRoles !== null) {
276
+ return overriddenRoles;
277
+ }
278
+ // null => fall through to default resolution
279
+ }
280
+
231
281
  if (username === "anonymous") {
232
282
  return anonymous;
233
283
  }
@@ -305,7 +355,7 @@ export class SessionContext implements ISessionContext {
305
355
  return true;
306
356
  }
307
357
  }
308
- if (!this.session ) { return false; }
358
+ if (!this.session) { return false; }
309
359
  const securityMode = this.session?.channel?.securityMode;
310
360
  if (accessRestrictions & AccessRestrictionsFlag.SigningRequired) {
311
361
  if (securityMode !== MessageSecurityMode.Sign && securityMode !== MessageSecurityMode.SignAndEncrypt) {
@@ -55,7 +55,8 @@ import {
55
55
  IAddressSpace,
56
56
  ShutdownTask,
57
57
  RaiseEventData,
58
- UAVariableT
58
+ UAVariableT,
59
+ MethodCallInterceptor
59
60
  } from "node-opcua-address-space-base";
60
61
  import { make_debugLog, make_warningLog, make_errorLog } from "node-opcua-debug";
61
62
 
@@ -191,6 +192,7 @@ export class AddressSpace implements AddressSpacePrivate {
191
192
  private _shutdownTask?: ShutdownTask[];
192
193
  private _modelChangeTransactionCounter = 0;
193
194
  private _modelChanges: ModelChangeStructureDataType[] = [];
195
+ public _methodCallInterceptors: MethodCallInterceptor[] = [];
194
196
 
195
197
  constructor() {
196
198
  this._private_namespaceIndex = 1;
@@ -1082,6 +1084,27 @@ export class AddressSpace implements AddressSpacePrivate {
1082
1084
  this._shutdownTask.push(task);
1083
1085
  }
1084
1086
 
1087
+ /**
1088
+ * Register a method call interceptor.
1089
+ * Interceptors are called sequentially before each UAMethod.execute().
1090
+ * If any interceptor returns a non-Good StatusCode, the method call
1091
+ * is rejected and subsequent interceptors are skipped.
1092
+ */
1093
+ public addMethodCallInterceptor(interceptor: MethodCallInterceptor): void {
1094
+ assert(typeof interceptor === "function");
1095
+ this._methodCallInterceptors.push(interceptor);
1096
+ }
1097
+
1098
+ /**
1099
+ * Remove a previously registered method call interceptor.
1100
+ */
1101
+ public removeMethodCallInterceptor(interceptor: MethodCallInterceptor): void {
1102
+ const index = this._methodCallInterceptors.indexOf(interceptor);
1103
+ if (index !== -1) {
1104
+ this._methodCallInterceptors.splice(index, 1);
1105
+ }
1106
+ }
1107
+
1085
1108
  public async shutdown(): Promise<void> {
1086
1109
  const performTasks = async (tasks: ShutdownTask[]) => {
1087
1110
  // perform registerShutdownTask
@@ -20,7 +20,8 @@ import {
20
20
  UAObjectType,
21
21
  UAReference,
22
22
  UAVariable,
23
- ContinuationData
23
+ ContinuationData,
24
+ MethodCallInterceptor
24
25
  } from "node-opcua-address-space-base";
25
26
  import { UARootFolder } from "../source/ua_root_folder";
26
27
  import { ExtensionObjectConstructorFuncWithSchema } from "../source/interfaces/extension_object_constructor";
@@ -89,4 +90,6 @@ export interface AddressSpacePrivate extends IAddressSpace {
89
90
  ) => void;
90
91
 
91
92
  isEnumeration(dataType: NodeId): boolean;
93
+
94
+ _methodCallInterceptors: MethodCallInterceptor[];
92
95
  }