node-opcua-server
Version:
pure nodejs OPCUA SDK - module server
1,004 lines (1,003 loc) • 135 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OPCUAServer = exports.RegisterServerMethod = void 0;
exports.filterDiagnosticInfo = filterDiagnosticInfo;
/* eslint-disable complexity */
/**
* @module node-opcua-server
*/
const crypto_1 = require("crypto");
const util_1 = require("util");
const async_1 = __importDefault(require("async"));
const chalk_1 = __importDefault(require("chalk"));
const node_opcua_hostname_1 = require("node-opcua-hostname");
const node_opcua_assert_1 = require("node-opcua-assert");
const node_opcua_utils_1 = require("node-opcua-utils");
const node_opcua_address_space_1 = require("node-opcua-address-space");
const node_opcua_certificate_manager_1 = require("node-opcua-certificate-manager");
const node_opcua_common_1 = require("node-opcua-common");
const web_1 = require("node-opcua-crypto/web");
const node_opcua_data_model_1 = require("node-opcua-data-model");
const node_opcua_data_value_1 = require("node-opcua-data-value");
const node_opcua_debug_1 = require("node-opcua-debug");
const node_opcua_object_registry_1 = require("node-opcua-object-registry");
const node_opcua_secure_channel_1 = require("node-opcua-secure-channel");
const node_opcua_service_browse_1 = require("node-opcua-service-browse");
const node_opcua_service_call_1 = require("node-opcua-service-call");
const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
const node_opcua_service_history_1 = require("node-opcua-service-history");
const node_opcua_service_node_management_1 = require("node-opcua-service-node-management");
const node_opcua_service_query_1 = require("node-opcua-service-query");
const node_opcua_service_read_1 = require("node-opcua-service-read");
const node_opcua_service_register_node_1 = require("node-opcua-service-register-node");
const node_opcua_service_session_1 = require("node-opcua-service-session");
const node_opcua_service_subscription_1 = require("node-opcua-service-subscription");
const node_opcua_service_translate_browse_path_1 = require("node-opcua-service-translate-browse-path");
const node_opcua_service_write_1 = require("node-opcua-service-write");
const node_opcua_status_code_1 = require("node-opcua-status-code");
const node_opcua_types_1 = require("node-opcua-types");
const node_opcua_variant_1 = require("node-opcua-variant");
const node_opcua_variant_2 = require("node-opcua-variant");
const node_opcua_utils_2 = require("node-opcua-utils");
const base_server_1 = require("./base_server");
const factory_1 = require("./factory");
const monitored_item_1 = require("./monitored_item");
const register_server_manager_1 = require("./register_server_manager");
const register_server_manager_hidden_1 = require("./register_server_manager_hidden");
const register_server_manager_mdns_only_1 = require("./register_server_manager_mdns_only");
const server_end_point_1 = require("./server_end_point");
const server_engine_1 = require("./server_engine");
const user_manager_1 = require("./user_manager");
const user_manager_ua_1 = require("./user_manager_ua");
function isSubscriptionIdInvalid(subscriptionId) {
return subscriptionId < 0 || subscriptionId >= 0xffffffff;
}
// tslint:disable-next-line:no-var-requires
const thenify_ex_1 = require("thenify-ex");
// tslint:disable-next-line:no-var-requires
const package_info = require("../package.json");
const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
const errorLog = (0, node_opcua_debug_1.make_errorLog)(__filename);
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
const default_maxConnectionsPerEndpoint = 10;
function g_sendError(channel, message, ResponseClass, statusCode) {
const response = new node_opcua_types_1.ServiceFault({
responseHeader: { serviceResult: statusCode }
});
return channel.send_response("MSG", response, message);
}
const default_build_info = {
manufacturerName: "NodeOPCUA : MIT Licence ( see http://node-opcua.github.io/)",
productName: "NodeOPCUA-Server",
productUri: null, // << should be same as default_server_info.productUri?
softwareVersion: package_info.version,
buildNumber: "0",
buildDate: new Date(2020, 1, 1)
// xx buildDate: fs.statSync(package_json_file).mtime
};
const minSessionTimeout = 100; // 100 milliseconds
const defaultSessionTimeout = 1000 * 30; // 30 seconds
const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes
let unnamed_session_count = 0;
function _adjust_session_timeout(sessionTimeout) {
let revisedSessionTimeout = sessionTimeout || defaultSessionTimeout;
revisedSessionTimeout = Math.min(revisedSessionTimeout, maxSessionTimeout);
revisedSessionTimeout = Math.max(revisedSessionTimeout, minSessionTimeout);
return revisedSessionTimeout;
}
function channel_has_session(channel, session) {
if (session.channel === channel) {
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(channel.sessionTokens, session.authenticationToken.toString()));
return true;
}
return false;
}
function moveSessionToChannel(session, channel) {
debugLog("moveSessionToChannel sessionId", session.nodeId, " channelId=", channel.channelId);
if (session.publishEngine) {
session.publishEngine.cancelPendingPublishRequestBeforeChannelChange();
}
session._detach_channel();
session._attach_channel(channel);
(0, node_opcua_assert_1.assert)(session.channel.channelId === channel.channelId);
}
async function _attempt_to_close_some_old_unactivated_session(server) {
const session = server.engine.getOldestInactiveSession();
if (session) {
await server.engine.closeSession(session.authenticationToken, false, "Forcing");
}
}
function getRequiredEndpointInfo(endpoint) {
(0, node_opcua_assert_1.assert)(endpoint instanceof node_opcua_types_1.EndpointDescription);
// https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/
// https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/
const e = new node_opcua_types_1.EndpointDescription({
endpointUrl: endpoint.endpointUrl,
securityLevel: endpoint.securityLevel,
securityMode: endpoint.securityMode,
securityPolicyUri: endpoint.securityPolicyUri,
server: {
applicationUri: endpoint.server.applicationUri,
applicationType: endpoint.server.applicationType,
applicationName: endpoint.server.applicationName,
productUri: endpoint.server.productUri
},
transportProfileUri: endpoint.transportProfileUri,
userIdentityTokens: endpoint.userIdentityTokens
});
// reduce even further by explicitly setting unwanted members to null
e.server.applicationName = null;
// xx e.server.applicationType = null as any;
e.server.gatewayServerUri = null;
e.server.discoveryProfileUri = null;
e.server.discoveryUrls = null;
e.serverCertificate = null;
return e;
}
// serverUri String This value is only specified if the EndpointDescription has a gatewayServerUri.
// This value is the applicationUri from the EndpointDescription which is the applicationUri for the
// underlying Server. The type EndpointDescription is defined in 7.10.
function _serverEndpointsForCreateSessionResponse(server, endpointUrl, serverUri) {
serverUri = null; // unused then
// https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/
// https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/
return server
._get_endpoints(endpointUrl)
.filter((e) => !e.restricted) // remove restricted endpoints
.filter((e) => (0, node_opcua_utils_2.matchUri)(e.endpointUrl, endpointUrl))
.map(getRequiredEndpointInfo);
}
function adjustSecurityPolicy(channel, userTokenPolicy_securityPolicyUri) {
// check that userIdentityToken
let securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(userTokenPolicy_securityPolicyUri);
// if the security policy is not specified we use the session security policy
if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.Invalid) {
securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(channel.securityPolicy);
(0, node_opcua_assert_1.assert)(securityPolicy !== node_opcua_secure_channel_1.SecurityPolicy.Invalid);
}
return securityPolicy;
}
function findUserTokenByPolicy(endpoint_description, userTokenType, policyId) {
(0, node_opcua_assert_1.assert)(endpoint_description instanceof node_opcua_types_1.EndpointDescription);
const r = endpoint_description.userIdentityTokens.filter((userIdentity) => userIdentity.tokenType === userTokenType && (!policyId || userIdentity.policyId === policyId));
return r.length === 0 ? null : r[0];
}
function findUserTokenPolicy(endpoint_description, userTokenType) {
(0, node_opcua_assert_1.assert)(endpoint_description instanceof node_opcua_types_1.EndpointDescription);
const r = endpoint_description.userIdentityTokens.filter((userIdentity) => {
(0, node_opcua_assert_1.assert)(userIdentity.tokenType !== undefined);
return userIdentity.tokenType === userTokenType;
});
return r.length === 0 ? null : r[0];
}
function createAnonymousIdentityToken(endpoint_desc) {
(0, node_opcua_assert_1.assert)(endpoint_desc instanceof node_opcua_types_1.EndpointDescription);
const userTokenPolicy = findUserTokenPolicy(endpoint_desc, node_opcua_service_endpoints_1.UserTokenType.Anonymous);
if (!userTokenPolicy) {
throw new Error("Cannot find ANONYMOUS user token policy in end point description");
}
return new node_opcua_service_session_1.AnonymousIdentityToken({ policyId: userTokenPolicy.policyId });
}
function sameIdentityToken(token1, token2) {
if (token1 instanceof node_opcua_service_session_1.UserNameIdentityToken) {
if (!(token2 instanceof node_opcua_service_session_1.UserNameIdentityToken)) {
return false;
}
if (token1.userName !== token2.userName) {
return false;
}
if (token1.password.toString("hex") !== token2.password.toString("hex")) {
// note pasword hash may be different from two request and cannot be verified at this stage
// we assume that we have a valid password
// NOT CALLING return false;
}
return true;
}
else if (token1 instanceof node_opcua_service_session_1.AnonymousIdentityToken) {
if (!(token2 instanceof node_opcua_service_session_1.AnonymousIdentityToken)) {
return false;
}
if (token1.policyId !== token2.policyId) {
return false;
}
return true;
}
(0, node_opcua_assert_1.assert)(false, " Not implemented yet");
return false;
}
function getTokenType(userIdentityToken) {
if (userIdentityToken instanceof node_opcua_service_session_1.AnonymousIdentityToken) {
return node_opcua_service_endpoints_1.UserTokenType.Anonymous;
}
else if (userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken) {
return node_opcua_service_endpoints_1.UserTokenType.UserName;
}
else if (userIdentityToken instanceof node_opcua_types_1.IssuedIdentityToken) {
return node_opcua_service_endpoints_1.UserTokenType.IssuedToken;
}
else if (userIdentityToken instanceof node_opcua_service_session_1.X509IdentityToken) {
return node_opcua_service_endpoints_1.UserTokenType.Certificate;
}
return node_opcua_service_endpoints_1.UserTokenType.Invalid;
}
function thumbprint(certificate) {
return certificate ? certificate.toString("base64") : "";
}
/*=== private
*
* perform the read operation on a given node for a monitored item.
* this method DOES NOT apply to Variable Values attribute
*
* @param self
* @param oldValue
* @param node
* @param itemToMonitor
* @private
*/
function monitoredItem_read_and_record_value(self, context, oldValue, node, itemToMonitor, callback) {
(0, node_opcua_assert_1.assert)(self instanceof monitored_item_1.MonitoredItem);
(0, node_opcua_assert_1.assert)(oldValue instanceof node_opcua_data_value_1.DataValue);
(0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value);
const dataValue = node.readAttribute(context, itemToMonitor.attributeId, itemToMonitor.indexRange, itemToMonitor.dataEncoding);
callback(null, dataValue);
}
/*== private
* this method applies to Variable Values attribute
* @private
*/
function monitoredItem_read_and_record_value_async(self, context, oldValue, node, itemToMonitor, callback) {
(0, node_opcua_assert_1.assert)(context instanceof node_opcua_address_space_1.SessionContext);
(0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value);
(0, node_opcua_assert_1.assert)(self instanceof monitored_item_1.MonitoredItem);
(0, node_opcua_assert_1.assert)(oldValue instanceof node_opcua_data_value_1.DataValue);
// do it asynchronously ( this is only valid for value attributes )
(0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value);
node.readValueAsync(context, (err, dataValue) => {
callback(err, dataValue);
});
}
function build_scanning_node_function(addressSpace, itemToMonitor) {
(0, node_opcua_assert_1.assert)(itemToMonitor instanceof node_opcua_service_read_1.ReadValueId);
const node = addressSpace.findNode(itemToMonitor.nodeId);
/* istanbul ignore next */
if (!node) {
errorLog(" INVALID NODE ID , ", itemToMonitor.nodeId.toString());
(0, node_opcua_debug_1.dump)(itemToMonitor);
return (_sessionContext, _oldData, callback) => {
callback(null, new node_opcua_data_value_1.DataValue({
statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdUnknown,
value: { dataType: node_opcua_variant_1.DataType.Null, value: 0 }
}));
};
}
///// !!monitoredItem.setNode(node);
if (itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value) {
const monitoredItem_read_and_record_value_func = itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value && typeof node.readValueAsync === "function"
? monitoredItem_read_and_record_value_async
: monitoredItem_read_and_record_value;
return function func(sessionContext, oldDataValue, callback) {
(0, node_opcua_assert_1.assert)(this instanceof monitored_item_1.MonitoredItem);
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
(0, node_opcua_assert_1.assert)(typeof callback === "function");
monitoredItem_read_and_record_value_func(this, sessionContext, oldDataValue, node, itemToMonitor, callback);
};
}
else {
// Attributes, other than the Value Attribute, are only monitored for a change in value.
// The filter is not used for these Attributes. Any change in value for these Attributes
// causes a Notification to be generated.
// only record value when it has changed
return function func(sessionContext, oldDataValue, callback) {
(0, node_opcua_assert_1.assert)(this instanceof monitored_item_1.MonitoredItem);
(0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue);
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const newDataValue = node.readAttribute(sessionContext, itemToMonitor.attributeId);
callback(null, newDataValue);
};
}
}
function prepareMonitoredItem(context, addressSpace, monitoredItem) {
const itemToMonitor = monitoredItem.itemToMonitor;
const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor);
monitoredItem.samplingFunc = readNodeFunc;
}
function isMonitoringModeValid(monitoringMode) {
(0, node_opcua_assert_1.assert)(node_opcua_types_1.MonitoringMode.Invalid !== undefined);
return monitoringMode !== node_opcua_types_1.MonitoringMode.Invalid && monitoringMode <= node_opcua_types_1.MonitoringMode.Reporting;
}
function _installRegisterServerManager(self) {
(0, node_opcua_assert_1.assert)(self instanceof OPCUAServer);
(0, node_opcua_assert_1.assert)(!self.registerServerManager);
/* istanbul ignore next */
if (!self.registerServerMethod) {
throw new Error("Internal Error");
}
switch (self.registerServerMethod) {
case RegisterServerMethod.HIDDEN:
self.registerServerManager = new register_server_manager_hidden_1.RegisterServerManagerHidden({
server: self
});
break;
case RegisterServerMethod.MDNS:
self.registerServerManager = new register_server_manager_mdns_only_1.RegisterServerManagerMDNSONLY({
server: self
});
break;
case RegisterServerMethod.LDS:
self.registerServerManager = new register_server_manager_1.RegisterServerManager({
discoveryServerEndpointUrl: self.discoveryServerEndpointUrl,
server: self
});
break;
/* istanbul ignore next */
default:
throw new Error("Invalid switch");
}
self.registerServerManager.on("serverRegistrationPending", () => {
/**
* emitted when the server is trying to registered the LDS
* but when the connection to the lds has failed
* serverRegistrationPending is sent when the backoff signal of the
* connection process is raised
* @event serverRegistrationPending
*/
debugLog("serverRegistrationPending");
self.emit("serverRegistrationPending");
});
self.registerServerManager.on("serverRegistered", () => {
/**
* emitted when the server is successfully registered to the LDS
* @event serverRegistered
*/
debugLog("serverRegistered");
self.emit("serverRegistered");
});
self.registerServerManager.on("serverRegistrationRenewed", () => {
/**
* emitted when the server has successfully renewed its registration to the LDS
* @event serverRegistrationRenewed
*/
debugLog("serverRegistrationRenewed");
self.emit("serverRegistrationRenewed");
});
self.registerServerManager.on("serverUnregistered", () => {
debugLog("serverUnregistered");
/**
* emitted when the server is successfully unregistered to the LDS
* ( for instance during shutdown)
* @event serverUnregistered
*/
self.emit("serverUnregistered");
});
}
function validate_applicationUri(channel, request) {
const applicationUri = request.clientDescription.applicationUri;
const clientCertificate = request.clientCertificate;
// if session is insecure there is no need to check certificate information
if (channel.securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None) {
return true; // assume correct
}
if (!clientCertificate || clientCertificate.length === 0) {
return true; // can't check
}
const e = (0, web_1.exploreCertificate)(clientCertificate);
const uniformResourceIdentifier = e.tbsCertificate.extensions.subjectAltName?.uniformResourceIdentifier ?? null;
const applicationUriFromCert = uniformResourceIdentifier && uniformResourceIdentifier.length > 0 ? uniformResourceIdentifier[0] : null;
/* istanbul ignore next */
if (applicationUriFromCert !== applicationUri) {
errorLog("BadCertificateUriInvalid!");
errorLog("applicationUri = ", applicationUri);
errorLog("applicationUriFromCert = ", applicationUriFromCert);
}
return applicationUriFromCert === applicationUri;
}
function validate_security_endpoint(server, request, channel) {
debugLog("validate_security_endpoint = ", request.endpointUrl);
let endpoints = server._get_endpoints(request.endpointUrl);
// endpointUrl String The network address that the Client used to access the Session Endpoint.
// The HostName portion of the URL should be one of the HostNames for the application that are
// specified in the Server’s ApplicationInstanceCertificate (see 7.2). The Server shall raise an
// AuditUrlMismatchEventType event if the URL does not match the Server’s HostNames.
// AuditUrlMismatchEventType event type is defined in Part 5.
// The Server uses this information for diagnostics and to determine the set of
// EndpointDescriptions to return in the response.
// ToDo: check endpointUrl validity and emit an AuditUrlMismatchEventType event if not
// sometime endpoints have a extra leading "/" that can be ignored
// don't be too harsh.
if (endpoints.length === 0 && request.endpointUrl?.endsWith("/")) {
endpoints = server._get_endpoints(request.endpointUrl.slice(0, -1));
}
if (endpoints.length === 0) {
// we have a UrlMismatch here
const ua_server = server.engine.addressSpace.rootFolder.objects.server;
if (!request.endpointUrl?.match(/localhost/i) || OPCUAServer.requestExactEndpointUrl) {
warningLog("Cannot find suitable endpoints in available endpoints. endpointUri =", request.endpointUrl);
}
ua_server.raiseEvent("AuditUrlMismatchEventType", {
endpointUrl: { dataType: node_opcua_variant_1.DataType.String, value: request.endpointUrl }
});
if (OPCUAServer.requestExactEndpointUrl) {
return { errCode: node_opcua_status_code_1.StatusCodes.BadServiceUnsupported };
}
else {
endpoints = server._get_endpoints(null);
}
}
// ignore restricted endpoints
endpoints = endpoints.filter((e) => !e.restricted);
const endpoints_matching_security_mode = endpoints.filter((e) => {
return e.securityMode === channel.securityMode;
});
if (endpoints_matching_security_mode.length === 0) {
return { errCode: node_opcua_status_code_1.StatusCodes.BadSecurityModeRejected };
}
const endpoints_matching_security_policy = endpoints_matching_security_mode.filter((e) => {
return e.securityPolicyUri === channel.securityPolicy;
});
if (endpoints_matching_security_policy.length === 0) {
return { errCode: node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected };
}
if (endpoints_matching_security_policy.length !== 1) {
debugLog("endpoints_matching_security_policy= ", endpoints_matching_security_policy.length);
}
return { errCode: node_opcua_status_code_1.StatusCodes.Good, endpoint: endpoints_matching_security_policy[0] };
}
function filterDiagnosticInfo(returnDiagnostics, response) {
if (node_opcua_data_model_1.RESPONSE_DIAGNOSTICS_MASK_ALL & returnDiagnostics) {
response.responseHeader.serviceDiagnostics = (0, node_opcua_data_model_1.filterDiagnosticServiceLevel)(returnDiagnostics, response.responseHeader.serviceDiagnostics);
if (response.diagnosticInfos && response.diagnosticInfos.length > 0) {
response.diagnosticInfos = response.diagnosticInfos.map((d) => (0, node_opcua_data_model_1.filterDiagnosticOperationLevel)(returnDiagnostics, d));
}
else {
response.diagnosticInfos = [];
}
if (response.results) {
for (const entry of response.results) {
if (entry.inputArgumentDiagnosticInfos && entry.inputArgumentDiagnosticInfos.length > 0) {
entry.inputArgumentDiagnosticInfos = entry.inputArgumentDiagnosticInfos.map((d) => (0, node_opcua_data_model_1.filterDiagnosticOperationLevel)(returnDiagnostics, d));
}
else {
entry.inputArgumentDiagnosticInfos = [];
}
}
}
}
}
var RegisterServerMethod;
(function (RegisterServerMethod) {
RegisterServerMethod[RegisterServerMethod["HIDDEN"] = 1] = "HIDDEN";
RegisterServerMethod[RegisterServerMethod["MDNS"] = 2] = "MDNS";
RegisterServerMethod[RegisterServerMethod["LDS"] = 3] = "LDS"; // the server registers itself to the LDS or LDS-ME (Local Discovery Server)
})(RegisterServerMethod || (exports.RegisterServerMethod = RegisterServerMethod = {}));
const g_requestExactEndpointUrl = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL;
/**
*
*/
class OPCUAServer extends base_server_1.OPCUABaseServer {
/**
* total number of bytes written by the server since startup
*/
get bytesWritten() {
return this.endpoints.reduce((accumulated, endpoint) => {
return accumulated + endpoint.bytesWritten;
}, 0);
}
/**
* total number of bytes read by the server since startup
*/
get bytesRead() {
return this.endpoints.reduce((accumulated, endpoint) => {
return accumulated + endpoint.bytesRead;
}, 0);
}
/**
* Number of transactions processed by the server since startup
*/
get transactionsCount() {
return this.endpoints.reduce((accumulated, endpoint) => {
return accumulated + endpoint.transactionsCount;
}, 0);
}
/**
* The server build info
*/
get buildInfo() {
return this.engine.buildInfo;
}
/**
* the number of connected channel on all existing end points
*/
get currentChannelCount() {
// TODO : move to base
return this.endpoints.reduce((currentValue, endPoint) => {
return currentValue + endPoint.currentChannelCount;
}, 0);
}
/**
* The number of active subscriptions from all sessions
*/
get currentSubscriptionCount() {
return this.engine ? this.engine.currentSubscriptionCount : 0;
}
/**
* the number of session activation requests that have been rejected
*/
get rejectedSessionCount() {
return this.engine ? this.engine.rejectedSessionCount : 0;
}
/**
* the number of request that have been rejected
*/
get rejectedRequestsCount() {
return this.engine ? this.engine.rejectedRequestsCount : 0;
}
/**
* the number of sessions that have been aborted
*/
get sessionAbortCount() {
return this.engine ? this.engine.sessionAbortCount : 0;
}
/**
* the publishing interval count
*/
get publishingIntervalCount() {
return this.engine ? this.engine.publishingIntervalCount : 0;
}
/**
* the number of sessions currently active
*/
get currentSessionCount() {
return this.engine ? this.engine.currentSessionCount : 0;
}
/**
* true if the server has been initialized
*
*/
get initialized() {
return this.engine && this.engine.addressSpace !== null;
}
/**
* is the server auditing ?
*/
get isAuditing() {
return this.engine ? this.engine.isAuditing : false;
}
/**
* the maximum number of concurrent sessions allowed on the server
*/
get maxAllowedSessionNumber() {
return this.engine.serverCapabilities.maxSessions;
}
constructor(options) {
super(options);
/**
* false if anonymous connection are not allowed
*/
this.allowAnonymous = false;
this.allowAnonymous = false;
options = options || {};
this.options = options;
if (options.maxAllowedSessionNumber !== undefined) {
warningLog("[NODE-OPCUA-W21] maxAllowedSessionNumber property is now deprecated , please use serverCapabilities.maxSessions instead");
options.serverCapabilities = options.serverCapabilities || {};
options.serverCapabilities.maxSessions = options.maxAllowedSessionNumber;
}
// adjust securityPolicies if any
if (options.securityPolicies) {
options.securityPolicies = options.securityPolicies.map(node_opcua_secure_channel_1.coerceSecurityPolicy);
}
/**
* @property maxConnectionsPerEndpoint
*/
this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint;
// build Info
const buildInfo = {
...default_build_info,
...options.buildInfo
};
// repair product name
buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri;
this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri;
this.userManager = (0, user_manager_1.makeUserManager)(options.userManager);
options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous;
/**
* @property allowAnonymous
*/
this.allowAnonymous = options.allowAnonymous;
this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840";
(0, node_opcua_assert_1.assert)(typeof this.discoveryServerEndpointUrl === "string");
this.serverInfo.applicationType =
options.serverInfo?.applicationType === undefined ? node_opcua_service_endpoints_1.ApplicationType.Server : options.serverInfo.applicationType;
this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"];
this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN;
_installRegisterServerManager(this);
if (!options.userCertificateManager) {
this.userCertificateManager = (0, node_opcua_certificate_manager_1.getDefaultCertificateManager)("UserPKI");
}
else {
this.userCertificateManager = options.userCertificateManager;
}
// note: we need to delay initialization of endpoint as certain resources
// such as %FQDN% might not be ready yet at this stage
this._delayInit = async () => {
/* istanbul ignore next */
if (!options) {
throw new Error("Internal Error");
}
// to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName;
// note: applicationUri is handled in a special way
this.engine = new server_engine_1.ServerEngine({
applicationUri: () => this.serverInfo.applicationUri,
buildInfo,
isAuditing: options.isAuditing,
serverCapabilities: options.serverCapabilities,
serverConfiguration: {
serverCapabilities: () => {
return this.capabilitiesForMDNS || ["NA"];
},
supportedPrivateKeyFormat: ["PEM"],
applicationType: () => this.serverInfo.applicationType,
applicationUri: () => this.serverInfo.applicationUri || "",
productUri: () => this.serverInfo.productUri || "",
// hasSecureElement: () => false,
multicastDnsEnabled: () => this.registerServerMethod === RegisterServerMethod.MDNS
}
});
this.objectFactory = new factory_1.Factory(this.engine);
const endpointDefinitions = [
...(options.endpoints || []),
...(options.alternateEndpoints || [])
];
const hostname = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
endpointDefinitions.forEach((endpointDefinition) => {
endpointDefinition.port = endpointDefinition.port === undefined ? 26543 : endpointDefinition.port;
endpointDefinition.hostname = endpointDefinition.hostname || hostname;
});
if (!options.endpoints) {
endpointDefinitions.push({
port: options.port === undefined ? 26543 : options.port,
hostname: options.hostname || hostname,
host: options.host,
allowAnonymous: options.allowAnonymous,
alternateHostname: options.alternateHostname,
disableDiscovery: options.disableDiscovery,
securityModes: options.securityModes,
securityPolicies: options.securityPolicies
});
}
// todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
for (const endpointOptions of endpointDefinitions) {
const endPoint = this.createEndpointDescriptions(options, endpointOptions);
this.endpoints.push(endPoint);
endPoint.on("message", (message, channel) => {
this.on_request(message, channel);
});
// endPoint.on("error", (err: Error) => {
// errorLog("OPCUAServer endpoint error", err);
// // set serverState to ServerState.Failed;
// this.engine.setServerState(ServerState.Failed);
// this.shutdown(() => {
// /* empty */
// });
// });
}
};
}
initialize(...args) {
const done = args[0];
(0, node_opcua_assert_1.assert)(!this.initialized, "server is already initialized"); // already initialized ?
this._preInitTask.push(async () => {
/* istanbul ignore else */
if (this._delayInit) {
await this._delayInit();
this._delayInit = undefined;
}
});
this.performPreInitialization()
.then(() => {
OPCUAServer.registry.register(this);
this.engine.initialize(this.options, () => {
(0, user_manager_ua_1.bindRoleSet)(this.userManager, this.engine.addressSpace);
setImmediate(() => {
this.emit("post_initialize");
done();
});
});
})
.catch((err) => {
done(err);
});
}
start(...args) {
const done = args[0];
const tasks = [];
tasks.push((0, util_1.callbackify)(node_opcua_hostname_1.extractFullyQualifiedDomainName));
if (!this.initialized) {
tasks.push((callback) => {
this.initialize(callback);
});
}
tasks.push((callback) => {
super.start((err) => {
if (err) {
this.shutdown((/*err2*/ err2) => {
callback(err);
});
}
else {
// we start the registration process asynchronously
// as we want to make server immediately available
this.registerServerManager.start(() => {
/* empty */
});
setImmediate(callback);
}
});
});
async_1.default.series(tasks, done);
}
shutdown(...args) {
const timeout = args.length === 1 ? OPCUAServer.defaultShutdownTimeout : args[0];
const callback = (args.length === 1 ? args[0] : args[1]);
(0, node_opcua_assert_1.assert)(typeof callback === "function");
debugLog("OPCUAServer#shutdown (timeout = ", timeout, ")");
/* istanbul ignore next */
if (!this.engine) {
return callback();
}
(0, node_opcua_assert_1.assert)(this.engine);
if (!this.engine.isStarted()) {
// server may have been shot down already , or may have fail to start !!
const err = new Error("OPCUAServer#shutdown failure ! server doesn't seems to be started yet");
return callback(err);
}
this.userCertificateManager.dispose();
this.engine.setServerState(node_opcua_common_1.ServerState.Shutdown);
const shutdownTime = new Date(Date.now() + timeout);
this.engine.setShutdownTime(shutdownTime);
debugLog("OPCUAServer is now un-registering itself from the discovery server " + this.buildInfo);
this.registerServerManager.stop((err) => {
debugLog("OPCUAServer unregistered from discovery server", err);
setTimeout(async () => {
await this.engine.shutdown();
debugLog("OPCUAServer#shutdown: started");
base_server_1.OPCUABaseServer.prototype.shutdown.call(this, (err1) => {
debugLog("OPCUAServer#shutdown: completed");
this.dispose();
callback(err1);
});
}, timeout);
});
}
dispose() {
for (const endpoint of this.endpoints) {
endpoint.dispose();
}
this.endpoints = [];
this.removeAllListeners();
if (this.registerServerManager) {
this.registerServerManager.dispose();
this.registerServerManager = undefined;
}
OPCUAServer.registry.unregister(this);
/* istanbul ignore next */
if (this.engine) {
this.engine.dispose();
}
}
raiseEvent(eventType, options) {
/* istanbul ignore next */
if (!this.engine.addressSpace) {
errorLog("addressSpace missing");
return;
}
const server = this.engine.addressSpace.findNode("Server");
/* istanbul ignore next */
if (!server) {
// xx throw new Error("OPCUAServer#raiseEvent : cannot find Server object");
return;
}
let eventTypeNode = eventType;
if (typeof eventType === "string") {
eventTypeNode = this.engine.addressSpace.findEventType(eventType);
if (eventTypeNode) {
return server.raiseEvent(eventTypeNode, options);
}
else {
console.warn(" cannot find event type ", eventType);
}
}
else {
return server.raiseEvent(eventTypeNode, options);
}
}
/**
* create and register a new session
* @private
*/
createSession(options) {
/* istanbul ignore next */
if (!this.engine) {
throw new Error("Internal Error");
}
return this.engine.createSession(options);
}
/**
* retrieve a session by authentication token
* @private
*/
getSession(authenticationToken, activeOnly) {
return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null;
}
/**
*
* @param channel
* @param clientCertificate
* @param clientNonce
* @private
*/
computeServerSignature(channel, clientCertificate, clientNonce) {
return (0, node_opcua_secure_channel_1.computeSignature)(clientCertificate, clientNonce, this.getPrivateKey(), channel.securityPolicy);
}
/**
*
* @param session
* @param channel
* @param clientSignature
*/
verifyClientSignature(session, channel, clientSignature) {
const clientCertificate = channel.clientCertificate;
const securityPolicy = channel.securityPolicy;
const serverCertificate = this.getCertificate();
const result = (0, node_opcua_secure_channel_1.verifySignature)(serverCertificate, session.nonce, clientSignature, clientCertificate, securityPolicy);
return result;
}
isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback) {
(0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken);
const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
return callback(null, node_opcua_status_code_1.StatusCodes.Good);
}
const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy);
/* istanbul ignore next */
if (!cryptoFactory) {
return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected);
}
/* istanbul ignore next */
if (userIdentityToken.encryptionAlgorithm !== cryptoFactory.asymmetricEncryptionAlgorithm) {
errorLog("invalid encryptionAlgorithm");
errorLog("userTokenPolicy", userTokenPolicy.toString());
errorLog("userTokenPolicy", userIdentityToken.toString());
return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenInvalid);
}
const userName = userIdentityToken.userName;
const password = userIdentityToken.password;
if (!userName || !password) {
return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenInvalid);
}
return callback(null, node_opcua_status_code_1.StatusCodes.Good);
}
isValidX509IdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback) {
(0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.X509IdentityToken);
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy);
/* istanbul ignore next */
if (!cryptoFactory) {
return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected);
}
if (!userTokenSignature || !userTokenSignature.signature) {
this.raiseEvent("AuditCreateSessionEventType", {});
return callback(null, node_opcua_status_code_1.StatusCodes.BadUserSignatureInvalid);
}
if (userIdentityToken.policyId !== userTokenPolicy.policyId) {
errorLog("invalid encryptionAlgorithm");
errorLog("userTokenPolicy", userTokenPolicy.toString());
errorLog("userTokenPolicy", userIdentityToken.toString());
return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected);
}
const certificate = userIdentityToken.certificateData; /* as Certificate*/
const nonce = session.nonce;
const serverCertificate = this.getCertificate();
(0, node_opcua_assert_1.assert)(serverCertificate instanceof Buffer);
(0, node_opcua_assert_1.assert)(certificate instanceof Buffer, "expecting certificate to be a Buffer");
(0, node_opcua_assert_1.assert)(nonce instanceof Buffer, "expecting nonce to be a Buffer");
(0, node_opcua_assert_1.assert)(userTokenSignature.signature instanceof Buffer, "expecting userTokenSignature to be a Buffer");
// verify proof of possession by checking certificate signature & server nonce correctness
if (!(0, node_opcua_secure_channel_1.verifySignature)(serverCertificate, nonce, userTokenSignature, certificate, securityPolicy)) {
return callback(null, node_opcua_status_code_1.StatusCodes.BadUserSignatureInvalid);
}
// verify if certificate is Valid
this.userCertificateManager.checkCertificate(certificate, (err, certificateStatus) => {
/* istanbul ignore next */
if (err) {
return callback(err);
}
if (this.isAuditing) {
switch (certificateStatus) {
case node_opcua_status_code_1.StatusCodes.Good:
break;
case node_opcua_status_code_1.StatusCodes.BadCertificateUntrusted:
this.raiseEvent("AuditCertificateUntrustedEventType", {
certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate },
sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" }
});
break;
case node_opcua_status_code_1.StatusCodes.BadCertificateTimeInvalid:
case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerTimeInvalid:
this.raiseEvent("AuditCertificateExpiredEventType", {
certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate },
sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" }
});
break;
case node_opcua_status_code_1.StatusCodes.BadCertificateRevoked:
case node_opcua_status_code_1.StatusCodes.BadCertificateRevocationUnknown:
case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerRevocationUnknown:
this.raiseEvent("AuditCertificateRevokedEventType", {
certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate },
sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" }
});
break;
case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerUseNotAllowed:
case node_opcua_status_code_1.StatusCodes.BadCertificateUseNotAllowed:
case node_opcua_status_code_1.StatusCodes.BadSecurityChecksFailed:
this.raiseEvent("AuditCertificateMismatchEventType", {
certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate },
sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" }
});
break;
}
}
if (certificateStatus &&
(node_opcua_status_code_1.StatusCodes.BadCertificateUntrusted.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateTimeInvalid.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerTimeInvalid.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerUseNotAllowed.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateIssuerRevocationUnknown.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateRevocationUnknown.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateRevoked.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadCertificateUseNotAllowed.equals(certificateStatus) ||
node_opcua_status_code_1.StatusCodes.BadSecurityChecksFailed.equals(certificateStatus) ||
!node_opcua_status_code_1.StatusCodes.Good.equals(certificateStatus))) {
debugLog("isValidX509IdentityToken => certificateStatus = ", certificateStatus?.toString());
return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenRejected);
}
if (node_opcua_status_code_1.StatusCodes.Good !== certificateStatus) {
(0, node_opcua_assert_1.assert)(certificateStatus instanceof node_opcua_status_code_1.StatusCode);
return callback(null, certificateStatus);
// return callback(null, StatusCodes.BadIdentityTokenInvalid);
}
// verify if certificate is truster or rejected
// todo: StatusCodes.BadCertificateUntrusted
// store untrusted certificate to rejected folder
// todo:
return callback(null, node_opcua_status_code_1.StatusCodes.Good);
});
}
/**
* @internal
*/
userNameIdentityTokenAuthenticateUser(channel, session, userTokenPolicy, userIdentityToken, callback) {
(0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken);
// assert(this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken));
const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
const userName = userIdentityToken.userName;
let password = userIdentityToken.password;
// decrypt password if necessary
if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
// not good, password was sent in clear text ...
password = password.toString();
}
else {
const serverPrivateKey = this.getPrivateKey();
const serverNonce = session.nonce;
(0, node_opcua_assert_1.assert)(serverNonce instanceof Buffer);
const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy);
/* istanbul ignore next */
if (!cryptoFactory) {
return callback(new Error(" Unsupported security Policy"));
}
const buff = cryptoFactory.asymmetricDecrypt(password, serverPrivateKey);