node-opcua-address-space 2.173.1 → 2.174.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
- "version": "2.173.1",
3
+ "version": "2.174.0",
4
4
  "description": "pure nodejs OPCUA SDK - module address-space",
5
5
  "main": "./dist/src/index_current.js",
6
6
  "types": "./dist/source/index.d.ts",
@@ -21,37 +21,37 @@
21
21
  "@types/semver": "7.7.1",
22
22
  "chalk": "4.1.2",
23
23
  "dequeue": "^1.0.5",
24
- "node-opcua-address-space-base": "2.173.0",
24
+ "node-opcua-address-space-base": "2.174.0",
25
25
  "node-opcua-assert": "2.164.0",
26
- "node-opcua-basic-types": "2.173.0",
26
+ "node-opcua-basic-types": "2.174.0",
27
27
  "node-opcua-binary-stream": "2.173.0",
28
- "node-opcua-client-dynamic-extension-object": "2.173.0",
29
- "node-opcua-constants": "2.157.0",
28
+ "node-opcua-client-dynamic-extension-object": "2.174.0",
29
+ "node-opcua-constants": "2.174.0",
30
30
  "node-opcua-crypto": "5.3.6",
31
- "node-opcua-data-access": "2.173.0",
32
- "node-opcua-data-model": "2.173.0",
33
- "node-opcua-data-value": "2.173.0",
34
- "node-opcua-date-time": "2.173.0",
31
+ "node-opcua-data-access": "2.174.0",
32
+ "node-opcua-data-model": "2.174.0",
33
+ "node-opcua-data-value": "2.174.0",
34
+ "node-opcua-date-time": "2.174.0",
35
35
  "node-opcua-debug": "2.172.0",
36
36
  "node-opcua-enum": "2.173.0",
37
- "node-opcua-extension-object": "2.173.0",
38
- "node-opcua-factory": "2.173.0",
39
- "node-opcua-nodeid": "2.173.0",
40
- "node-opcua-nodeset-ua": "2.173.0",
41
- "node-opcua-numeric-range": "2.173.0",
37
+ "node-opcua-extension-object": "2.174.0",
38
+ "node-opcua-factory": "2.174.0",
39
+ "node-opcua-nodeid": "2.174.0",
40
+ "node-opcua-nodeset-ua": "2.174.0",
41
+ "node-opcua-numeric-range": "2.174.0",
42
42
  "node-opcua-object-registry": "2.172.0",
43
- "node-opcua-pseudo-session": "2.173.0",
44
- "node-opcua-service-browse": "2.173.0",
45
- "node-opcua-service-call": "2.173.0",
46
- "node-opcua-service-history": "2.173.0",
47
- "node-opcua-service-translate-browse-path": "2.173.0",
48
- "node-opcua-service-write": "2.173.0",
43
+ "node-opcua-pseudo-session": "2.174.0",
44
+ "node-opcua-service-browse": "2.174.0",
45
+ "node-opcua-service-call": "2.174.0",
46
+ "node-opcua-service-history": "2.174.0",
47
+ "node-opcua-service-translate-browse-path": "2.174.0",
48
+ "node-opcua-service-write": "2.174.0",
49
49
  "node-opcua-status-code": "2.173.0",
50
- "node-opcua-types": "2.173.0",
50
+ "node-opcua-types": "2.174.0",
51
51
  "node-opcua-utils": "2.173.0",
52
- "node-opcua-variant": "2.173.0",
52
+ "node-opcua-variant": "2.174.0",
53
53
  "node-opcua-xml2json": "2.173.0",
54
- "semver": "^7.8.0",
54
+ "semver": "^7.8.5",
55
55
  "thenify-ex": "4.4.0",
56
56
  "xml-writer": "^1.7.0"
57
57
  },
@@ -59,10 +59,10 @@
59
59
  "humanize": "0.0.9",
60
60
  "node-opcua-benchmarker": "2.173.0",
61
61
  "node-opcua-leak-detector": "2.172.0",
62
- "node-opcua-nodesets": "2.163.1",
63
- "node-opcua-packet-analyzer": "2.173.0",
64
- "node-opcua-schemas": "2.173.0",
65
- "node-opcua-service-filter": "2.173.0",
62
+ "node-opcua-nodesets": "2.174.0",
63
+ "node-opcua-packet-analyzer": "2.174.0",
64
+ "node-opcua-schemas": "2.174.0",
65
+ "node-opcua-service-filter": "2.174.0",
66
66
  "node-opcua-test-fixtures": "2.157.0"
67
67
  },
68
68
  "author": "Etienne Rossignon",
@@ -80,7 +80,7 @@
80
80
  "internet of things"
81
81
  ],
82
82
  "homepage": "http://node-opcua.github.io/",
83
- "gitHead": "f237f07380033d5ee1172c550cc205670d586d86",
83
+ "gitHead": "011657a3a4805ad106df7381c24ed84e2a18d8f1",
84
84
  "files": [
85
85
  "dist",
86
86
  "distHelpers",
@@ -133,6 +133,28 @@ function makeDefaultVariant(
133
133
  }
134
134
  return variant;
135
135
  }
136
+
137
+ /**
138
+ * a Matrix variant is "uninitialized" when it carries no element (value=[]) but declares
139
+ * non-zero dimensions (e.g. ArrayDimensions="256,128"). Such a variant is internally inconsistent
140
+ * (value.length !== product(dimensions)) and, if serialized, produces a spec-violating wire form.
141
+ */
142
+ function isUninitializedMatrix(value: VariantOptions): boolean {
143
+ if (value.arrayType !== VariantArrayType.Matrix) {
144
+ return false;
145
+ }
146
+ const length = Array.isArray(value.value) ? value.value.length : 0;
147
+ if (length !== 0) {
148
+ return false;
149
+ }
150
+ const dimensions = value.dimensions;
151
+ if (!dimensions || dimensions.length === 0) {
152
+ return false;
153
+ }
154
+ const product = dimensions.reduce((n, p) => n * p, 1);
155
+ return product > 0;
156
+ }
157
+
136
158
  export interface NodeSet2ParserEngine {
137
159
  addNodeSet: (xmlData: string) => Promise<void>;
138
160
  terminate: () => Promise<void>;
@@ -731,7 +753,12 @@ function makeNodeSetParserEngine(addressSpace: IAddressSpace, options: NodeSetLo
731
753
  if (false && doDebug) {
732
754
  debugLog("2 setting value to ", cv.nodeId.toString(), value);
733
755
  }
734
- if (value.dataType === DataType.Null) {
756
+ // a Matrix variable declared with fixed ArrayDimensions but no <Value> is built with
757
+ // an empty value (value=[]) yet non-zero declared dimensions. This is an *uninitialized*
758
+ // and internally inconsistent matrix (value.length !== product(dimensions)). Advertising
759
+ // it as Good would put a spec-violating matrix on the wire (ArraySize 0 + non-empty
760
+ // ArrayDimensions), so flag it as BadWaitingForInitialData like any other missing value.
761
+ if (value.dataType === DataType.Null || isUninitializedMatrix(value)) {
735
762
  cv.setValueFromSource(value, StatusCodes.BadWaitingForInitialData);
736
763
  } else {
737
764
  cv.setValueFromSource(value, StatusCodes.Good);
@@ -22,7 +22,7 @@ import type { NamespacePrivate } from "../src/namespace_private";
22
22
 
23
23
  export { PermissionType, RolePermissionType, RolePermissionTypeOptions } from "node-opcua-types";
24
24
 
25
- type AnyUserIdentityToken = UserNameIdentityToken | AnonymousIdentityToken | X509IdentityToken;
25
+ export type AnyUserIdentityToken = UserNameIdentityToken | AnonymousIdentityToken | X509IdentityToken;
26
26
 
27
27
  function getUserName(userIdentityToken: AnyUserIdentityToken): string {
28
28
  if (userIdentityToken instanceof AnonymousIdentityToken) {
@@ -48,29 +48,23 @@ function getUserName(userIdentityToken: AnyUserIdentityToken): string {
48
48
  throw new Error("Invalid user identity token");
49
49
  }
50
50
 
51
- /**
52
- *
53
- */
54
- export enum WellKnownRoles {
55
- Anonymous = ObjectIds.WellKnownRole_Anonymous,
56
- AuthenticatedUser = ObjectIds.WellKnownRole_AuthenticatedUser,
57
- ConfigureAdmin = ObjectIds.WellKnownRole_ConfigureAdmin,
58
- Engineer = ObjectIds.WellKnownRole_Engineer,
59
- Observer = ObjectIds.WellKnownRole_Observer,
60
- Operator = ObjectIds.WellKnownRole_Operator,
61
- SecurityAdmin = ObjectIds.WellKnownRole_SecurityAdmin,
62
- Supervisor = ObjectIds.WellKnownRole_Supervisor
63
- }
64
- export enum WellKnownRolesNodeId {
65
- Anonymous = ObjectIds.WellKnownRole_Anonymous,
66
- AuthenticatedUser = ObjectIds.WellKnownRole_AuthenticatedUser,
67
- ConfigureAdmin = ObjectIds.WellKnownRole_ConfigureAdmin,
68
- Engineer = ObjectIds.WellKnownRole_Engineer,
69
- Observer = ObjectIds.WellKnownRole_Observer,
70
- Operator = ObjectIds.WellKnownRole_Operator,
71
- SecurityAdmin = ObjectIds.WellKnownRole_SecurityAdmin,
72
- Supervisor = ObjectIds.WellKnownRole_Supervisor
73
- }
51
+ // Re-exported from node-opcua-constants for backward compatibility.
52
+ // The canonical definition now lives in node-opcua-constants so that
53
+ // client-side packages can use it without pulling in address-space.
54
+ import { WellKnownRoles } from "node-opcua-constants";
55
+ export { WellKnownRoles } from "node-opcua-constants";
56
+
57
+ /** @deprecated Use WellKnownRoles instead */
58
+ export const WellKnownRolesNodeId = {
59
+ Anonymous: ObjectIds.WellKnownRole_Anonymous,
60
+ AuthenticatedUser: ObjectIds.WellKnownRole_AuthenticatedUser,
61
+ ConfigureAdmin: ObjectIds.WellKnownRole_ConfigureAdmin,
62
+ Engineer: ObjectIds.WellKnownRole_Engineer,
63
+ Observer: ObjectIds.WellKnownRole_Observer,
64
+ Operator: ObjectIds.WellKnownRole_Operator,
65
+ SecurityAdmin: ObjectIds.WellKnownRole_SecurityAdmin,
66
+ Supervisor: ObjectIds.WellKnownRole_Supervisor
67
+ } as const;
74
68
  /**
75
69
  * OPC Unified Architecture, Part 3 13 Release 1.04
76
70
  * 4.8.2 Well Known Roles
@@ -114,9 +108,38 @@ export interface IRolePolicyOverride {
114
108
  getUserRoles(username: string): NodeId[] | null;
115
109
  }
116
110
 
111
+ /**
112
+ * Session attributes a resolver may use to evaluate application/endpoint
113
+ * restrictions on a Role (OPC 10000-18 §4.4.1).
114
+ */
115
+ export interface IRoleResolutionContext {
116
+ /** ApplicationUri from the Client certificate, if any. */
117
+ applicationUri?: string | null;
118
+ /** SecureChannel security mode. */
119
+ securityMode?: MessageSecurityMode;
120
+ /** SecureChannel security policy URI. */
121
+ securityPolicyUri?: string;
122
+ /** Endpoint URL used by the Session, if known. */
123
+ endpointUrl?: string;
124
+ }
125
+
126
+ /**
127
+ * Pluggable role resolver (OPC 10000-18 §4.4).
128
+ *
129
+ * Receives the full UserIdentityToken so implementations can match by
130
+ * Thumbprint, X509Subject, UserName, etc., plus an optional resolution context
131
+ * to enforce application/endpoint restrictions. Registered on
132
+ * IServerBase.roleResolvers by packages like node-opcua-role-set-server.
133
+ */
134
+ export interface IRoleResolver {
135
+ resolveRoles(userIdentityToken: AnyUserIdentityToken, context?: IRoleResolutionContext): NodeId[];
136
+ }
137
+
117
138
  export interface IServerBase {
118
139
  userManager?: IUserManager;
119
140
  rolePolicyOverride?: IRolePolicyOverride | null;
141
+ /** Additional role resolvers (identity stores, LDAP, etc.) */
142
+ roleResolvers?: IRoleResolver[];
120
143
  }
121
144
 
122
145
  export interface SessionContextOptions {
@@ -252,6 +275,11 @@ export class SessionContext implements ISessionContext {
252
275
  }
253
276
  }
254
277
 
278
+ /** The URL of the Endpoint the Session was created on, or `null` if unknown. */
279
+ public get endpointUrl(): string | null {
280
+ return this.session?.getEndpointUrl?.() ?? null;
281
+ }
282
+
255
283
  public toJSON(): Record<string, string | null> {
256
284
  return {
257
285
  userName: this.getUserName(),
@@ -325,7 +353,25 @@ export class SessionContext implements ISessionContext {
325
353
  return anonymous;
326
354
  }
327
355
 
328
- const rolesNodeId = this.server.userManager.getUserRoles(username);
356
+ const rolesNodeId = this.server.userManager.getUserRoles(username) || [];
357
+
358
+ // OPC 10000-18 §4.4: check registered role resolvers
359
+ if (this.server.roleResolvers && this.session?.userIdentityToken) {
360
+ const resolutionContext: IRoleResolutionContext = {
361
+ applicationUri: this.clientApplicationUri,
362
+ securityMode: this.session.channel?.securityMode,
363
+ securityPolicyUri: this.session.channel?.securityPolicy,
364
+ endpointUrl: this.endpointUrl ?? undefined
365
+ };
366
+ for (const resolver of this.server.roleResolvers) {
367
+ const extra = resolver.resolveRoles(this.session.userIdentityToken, resolutionContext);
368
+ for (const r of extra) {
369
+ if (!rolesNodeId.find((e) => sameNodeId(e, r))) {
370
+ rolesNodeId.push(r);
371
+ }
372
+ }
373
+ }
374
+ }
329
375
 
330
376
  if (rolesNodeId.findIndex((r) => r.namespace === 0 && r.value === WellKnownRoles.AuthenticatedUser) < 0) {
331
377
  rolesNodeId.push(resolveNodeId(WellKnownRoles.AuthenticatedUser));
@@ -180,7 +180,25 @@ export class NodeIdManager {
180
180
  }
181
181
  return nodeId;
182
182
  }
183
- return this._constructNodeId(options);
183
+ const nodeId = this._constructNodeId(options);
184
+ // When registerSymbolicNames is true and a nodeId was already
185
+ // provided (e.g. loaded from XML during reverse engineering),
186
+ // also record the BrowseName → NodeId mapping in the symbol
187
+ // cache so that getSymbols() returns the original NodeIds.
188
+ if (options.registerSymbolicNames && nodeId.identifierType === NodeIdType.NUMERIC) {
189
+ const parentInfo = this.findParentNodeId(options);
190
+ let fullParentName = "";
191
+ if (parentInfo) {
192
+ const [parentNodeId, suffix] = parentInfo;
193
+ fullParentName = buildUpName2(parentNodeId, suffix);
194
+ }
195
+ const fullName = compose(fullParentName, prepareName(options.browseName!));
196
+ if (!this._cacheSymbolicName[fullName]) {
197
+ this._cacheSymbolicName[fullName] = [nodeId.value as number, options.nodeClass!];
198
+ this._cacheSymbolicNameRev.add(nodeId.value as number);
199
+ }
200
+ }
201
+ return nodeId;
184
202
  }
185
203
 
186
204
  private _constructNodeId(options: ConstructNodeIdOptions): NodeId {
@@ -14,17 +14,18 @@ import type { UAVariableImpl } from "../ua_variable_impl";
14
14
  const warningLog = make_warningLog(__filename);
15
15
  const _debugLog = make_debugLog(__filename);
16
16
 
17
+ interface RequiredModel {
18
+ requiredNamespaceIndexes: number[];
19
+ nbTypes: number;
20
+ }
17
21
  // eslint-disable-next-line max-statements, complexity
18
- export function _recomputeRequiredModelsFromTypes(
19
- namespace: INamespace,
20
- cache?: Map<number, { requiredNamespaceIndexes: number[]; nbTypes: number }>
21
- ): { requiredNamespaceIndexes: number[]; nbTypes: number } {
22
+ export function _recomputeRequiredModelsFromTypes(namespace: INamespace, cache?: Map<number, RequiredModel>): RequiredModel {
22
23
  if (namespace.index === 0) {
23
24
  return { requiredNamespaceIndexes: [], nbTypes: 1 };
24
25
  }
25
26
  if (cache) {
26
27
  if (cache.has(namespace.index)) {
27
- return cache.get(namespace.index)!;
28
+ return cache.get(namespace.index) as RequiredModel;
28
29
  }
29
30
  }
30
31
  const requiredNamespaceIndexes = [0];
@@ -155,17 +156,14 @@ export function _recomputeRequiredModelsFromTypes(
155
156
  }
156
157
  }
157
158
 
158
- const result = { requiredNamespaceIndexes: requiredNamespaceIndexes, nbTypes: nbTypes };
159
+ const result: RequiredModel = { requiredNamespaceIndexes: requiredNamespaceIndexes, nbTypes: nbTypes };
159
160
  if (cache) {
160
161
  cache.set(namespace.index, result);
161
162
  }
162
163
  return result;
163
164
  }
164
165
 
165
- export function _recomputeRequiredModelsFromTypes2(
166
- namespace: INamespace,
167
- cache?: Map<number, { requiredNamespaceIndexes: number[]; nbTypes: number }>
168
- ): { requiredNamespaceIndexes: number[] } {
166
+ export function _recomputeRequiredModelsFromTypes2(namespace: INamespace, cache?: Map<number, RequiredModel>): RequiredModel {
169
167
  const addressSpace = namespace.addressSpace;
170
168
 
171
169
  const { requiredNamespaceIndexes } = _recomputeRequiredModelsFromTypes(namespace, cache);
@@ -187,13 +185,13 @@ export function _recomputeRequiredModelsFromTypes2(
187
185
  pass2.push(r);
188
186
  }
189
187
 
190
- return { requiredNamespaceIndexes: pass2 };
188
+ return { requiredNamespaceIndexes: pass2, nbTypes: 0 };
191
189
  }
192
190
 
193
191
  export function _getCompleteRequiredModelsFromValuesAndReferences(
194
192
  namespace: INamespace,
195
193
  priorityList: number[],
196
- cache?: Map<number, { requiredNamespaceIndexes: number[]; nbTypes: number }>
194
+ cache?: Map<number, RequiredModel>
197
195
  ): number[] {
198
196
  const namespace_ = namespace as NamespacePrivate;
199
197