diff --git a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h index 8af26ab..670d178 100644 --- a/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h +++ b/shared/libraries/opcuatms/opcuatms_client/include/opcuatms_client/objects/tms_client_device_impl.h @@ -66,6 +66,7 @@ class TmsClientDeviceImpl : public TmsClientComponentBaseImpl& functionPropValues); PropertyPtr addVariableBlockProperty(const StringPtr& propName, const OpcUaNodeId& propNodeId); void browseRawProperties(); + void setLocksForAttributes(); bool isIgnoredMethodProperty(const std::string& browseName); PropertyObjectPtr cloneChildPropertyObject(const PropertyPtr& prop) override; diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp index 941de19..db537c8 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_device_impl.cpp @@ -156,30 +156,40 @@ ErrCode TmsClientDeviceImpl::getAvailableOperationModes(IList** availableOpModes ErrCode TmsClientDeviceImpl::setOperationMode(OperationModeType modeType) { - if (!this->hasReference("OperationMode")) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOT_SUPPORTED, "OperationModes are not supported by the server"); - - const auto nodeId = getNodeId("OperationMode"); - const auto modeTypeStr = OperationModeTypeToString(modeType); - - const auto variant = VariantConverter::ToVariant(String(modeTypeStr), nullptr, daqContext); - client->writeValue(nodeId, variant); - - return OPENDAQ_SUCCESS; + return setOperationModeImpl(modeType, false); } ErrCode TmsClientDeviceImpl::setOperationModeRecursive(OperationModeType modeType) { - if (!this->hasReference("OperationMode")) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOT_SUPPORTED, "OperationModes are not supported by the server"); - - const auto nodeId = getNodeId("OperationMode"); - const auto modeTypeStr = "Recursive" + OperationModeTypeToString(modeType); + return setOperationModeImpl(modeType, true); +} - const auto variant = VariantConverter::ToVariant(String(modeTypeStr), nullptr, daqContext); - client->writeValue(nodeId, variant); +ErrCode TmsClientDeviceImpl::setOperationModeImpl(OperationModeType modeType, bool recursiveCall) +{ + ErrCode errCode = OPENDAQ_SUCCESS; + if (this->hasReference("OperationMode")) + { + const auto nodeId = getNodeId("OperationMode"); + const auto modeTypeStr = ((recursiveCall) ? "Recursive" : "") + OperationModeTypeToString(modeType); - return OPENDAQ_SUCCESS; + const auto variant = VariantConverter::ToVariant(String(modeTypeStr), nullptr, daqContext); + try + { + client->writeValue(nodeId, variant); + } + catch (OpcUaException& e) + { + if (e.getStatusCode() == UA_STATUSCODE_BADUSERACCESSDENIED) + errCode = DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED, "Access denied when setting OperationModes"); + else + errCode = DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_OPCUA_GENERAL, "Failed to set OperationMode on server"); + } + } + else + { + errCode = DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_NOT_SUPPORTED, "OperationModes are not supported by the server"); + } + return errCode; } ErrCode TmsClientDeviceImpl::getOperationMode(OperationModeType* modeType) @@ -735,6 +745,9 @@ DictPtr TmsClientDeviceImpl::onGetAvailableFunction auto browser = clientContext->getReferenceBrowser(); auto types = Dict(); + if (getAttributeWritePermission(this->nodeId) == false) + return types; + const auto fbFolderNodeId = browser->getChildNodeId(nodeId, "FB"); if (!browser->hasReference(fbFolderNodeId, "AvailableTypes")) diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_function_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_function_impl.cpp index a1a5eb2..91305b7 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_function_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_function_impl.cpp @@ -47,21 +47,18 @@ ErrCode TmsClientFunctionImpl::call(IBaseObject* args, IBaseObject** result) lastProccessDescription = "Calling function"; OpcUaObject callResult = ctx->getClient()->callMethod(callRequest); if (OPCUA_STATUSCODE_FAILED(callResult->statusCode) || (callResult->outputArgumentsSize != 1)) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + { + if (callResult->statusCode == UA_STATUSCODE_BADUSERACCESSDENIED) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED); + else + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + } lastProccessDescription = "Getting call result"; *result = VariantConverter::ToDaqObject(OpcUaVariant(callResult->outputArguments[0]), daqContext).detach(); return OPENDAQ_SUCCESS; }); - if (OPENDAQ_FAILED(errCode)) - { - daqClearErrorInfo(); - if (this->daqContext.getLogger().assigned()) - { - auto loggerComponent = this->daqContext.getLogger().getOrAddComponent("OpcUaClientProcedure"); - LOG_W("Failed to call function on OpcUA client. Error in \"{}\"", lastProccessDescription); - } - } + OPENDAQ_RETURN_IF_FAILED(errCode, fmt::format("Failed to call function on OpcUA client. Error in \"{}\"", lastProccessDescription)); return OPENDAQ_SUCCESS; } diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_input_port_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_input_port_impl.cpp index 36ccd85..35539eb 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_input_port_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_input_port_impl.cpp @@ -37,8 +37,7 @@ ErrCode TmsClientInputPortImpl::setRequiresSignal(Bool value) ErrCode TmsClientInputPortImpl::acceptsSignal(ISignal* signal, Bool* accepts) { - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_OPCUA_CLIENT_CALL_NOT_AVAILABLE); - + return getAttributeWritePermission(nodeId); //const ErrCode errCode = daqTry([&]() //{ // OpcUaNodeId methodId(NAMESPACE_DAQBSP, UA_DAQBSPID_INPUTPORTTYPE_ACCEPTSSIGNAL); diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp index 6177801..86f4dbf 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_object_impl.cpp @@ -1,6 +1,7 @@ -#include #include #include +#include +#include "opendaq/custom_log.h" BEGIN_NAMESPACE_OPENDAQ_OPCUA_TMS @@ -105,4 +106,51 @@ CachedReferences TmsClientObjectImpl::getChildReferencesOfType(const opcua::OpcU return clientContext->getReferenceBrowser()->browseFiltered(nodeId, filter); } +bool TmsClientObjectImpl::getAttributeWritePermission(const opcua::OpcUaNodeId& nodeId) +{ + // Note: This method is used to determine if a node attributes is changeable. + // For variable nodes you should check UA_ATTRIBUTEID_ACCESSLEVEL and UA_ATTRIBUTEID_USERACCESSLEVEL! + // In common case a user can have write permission to a node but not to its attributes. + bool commonWritePerm = true; + try + { + const auto reader = clientContext->getAttributeReader(); + const int64_t userWriteMask = reader->getValue(nodeId, UA_ATTRIBUTEID_USERWRITEMASK).toInteger(); + const int64_t writeMask = reader->getValue(nodeId, UA_ATTRIBUTEID_WRITEMASK).toInteger(); + commonWritePerm = ((userWriteMask & writeMask) != 0); + } + catch (...) + { + if (this->daqContext.getLogger().assigned()) + { + auto loggerComponent = this->daqContext.getLogger().getOrAddComponent("OpcUaClientObject"); + LOG_W("Cannot read write mask attributes for OpcUA node"); + } + } + return commonWritePerm; +} + +bool TmsClientObjectImpl::getExecutePermission(const opcua::OpcUaNodeId& nodeId) +{ + // Note: This method is used to determine if a method node is executable. + // Other nodes don't have executable attributes and this method will return true for them. + bool commonExecutable = true; + try + { + const auto reader = clientContext->getAttributeReader(); + const bool userExecutable = reader->getValue(nodeId, UA_ATTRIBUTEID_USEREXECUTABLE).toBool(); + const bool executable = reader->getValue(nodeId, UA_ATTRIBUTEID_EXECUTABLE).toBool(); + commonExecutable = userExecutable & executable; + } + catch (...) + { + if (this->daqContext.getLogger().assigned()) + { + auto loggerComponent = this->daqContext.getLogger().getOrAddComponent("OpcUaClientObject"); + LOG_W("Cannot read executable mask attributes for OpcUA node"); + } + } + return commonExecutable; +} + END_NAMESPACE_OPENDAQ_OPCUA_TMS diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_procedure_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_procedure_impl.cpp index 5615a46..d9f523c 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_procedure_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_procedure_impl.cpp @@ -48,19 +48,16 @@ ErrCode TmsClientProcedureImpl::dispatch(IBaseObject* args) lastProccessDescription = "Calling procedure"; OpcUaObject callResult = ctx->getClient()->callMethod(callRequest); if (OPCUA_STATUSCODE_FAILED(callResult->statusCode) || (callResult->outputArgumentsSize != 0)) - return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + { + if (callResult->statusCode == UA_STATUSCODE_BADUSERACCESSDENIED) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED); + else + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + } return OPENDAQ_SUCCESS; }); - if (OPENDAQ_FAILED(errCode)) - { - daqClearErrorInfo(); - if (this->daqContext.getLogger().assigned()) - { - auto loggerComponent = this->daqContext.getLogger().getOrAddComponent("OpcUaClientProcudure"); - LOG_W("Failed to call procedure on OpcUA client. Error: \"{}\"", lastProccessDescription); - } - } + OPENDAQ_RETURN_IF_FAILED(errCode, fmt::format("Failed to call procedure on OpcUA client. Error: \"{}\"", lastProccessDescription)); return OPENDAQ_SUCCESS; } diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp index bb69199..e83064d 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_impl.cpp @@ -85,7 +85,21 @@ void TmsClientPropertyImpl::configurePropertyFields() const auto& references = clientContext->getReferenceBrowser()->browse(nodeId); const auto reader = clientContext->getAttributeReader(); - for (auto [browseName, ref] : references.byBrowseName) + int64_t userAccessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_USERACCESSLEVEL).toInteger(); + int64_t accessLevel = reader->getValue(nodeId, UA_ATTRIBUTEID_ACCESSLEVEL).toInteger(); + int64_t commonAccessLevel = userAccessLevel & accessLevel; + + this->readOnly = ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) == 0); + + bool isExecutableProperty = (valueType == CoreType::ctFunc || valueType == CoreType::ctProc); + bool commonExecutable = true; + if (isExecutableProperty) + { + commonExecutable = getExecutePermission(nodeId); + this->visible = commonExecutable; + } + + for (const auto& [browseName, ref] : references.byBrowseName) { const auto childNodeId = OpcUaNodeId(ref->nodeId.nodeId); @@ -120,11 +134,17 @@ void TmsClientPropertyImpl::configurePropertyFields() break; case details::PropertyField::IsReadOnly: - this->readOnly = EvalValue(evalStr).asPtr(); + if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) + this->readOnly = EvalValue(evalStr).asPtr(); + else + this->readOnly = true; break; case details::PropertyField::IsVisible: - this->visible = EvalValue(evalStr).asPtr(); + if (!isExecutableProperty || commonExecutable) + this->visible = EvalValue(evalStr).asPtr(); + else + this->visible = false; break; case details::PropertyField::Unit: @@ -207,10 +227,16 @@ void TmsClientPropertyImpl::configurePropertyFields() break; } case details::PropertyField::IsReadOnly: - this->readOnly = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + if ((commonAccessLevel & UA_ACCESSLEVELMASK_WRITE) != 0) + this->readOnly = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + else + this->readOnly = true; break; case details::PropertyField::IsVisible: - this->visible = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + if (!isExecutableProperty || commonExecutable) + this->visible = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); + else + this->visible = false; break; case details::PropertyField::Unit: this->unit = VariantConverter::ToDaqObject(reader->getValue(childNodeId, UA_ATTRIBUTEID_VALUE)); diff --git a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_object_impl.cpp b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_object_impl.cpp index 66efbd2..017cf94 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_object_impl.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/objects/tms_client_property_object_impl.cpp @@ -129,6 +129,7 @@ void TmsClientPropertyObjectBaseImpl::init() } clientContext->readObjectAttributes(nodeId); browseRawProperties(); + setLocksForAttributes(); } template @@ -287,7 +288,14 @@ ErrCode INTERFACE_FUNC TmsClientPropertyObjectBaseImpl::beginUpdate() request->inputArgumentsSize = 0; request->objectId = nodeId.copyAndGetDetachedValue(); request->methodId = beginUpdateId.copyAndGetDetachedValue(); - client->callMethod(request); + OpcUaObject callResult = client->callMethod(request); + if (callResult->statusCode != UA_STATUSCODE_GOOD) + { + if (callResult->statusCode == UA_STATUSCODE_BADUSERACCESSDENIED) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED); + else + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + } return OPENDAQ_SUCCESS; } @@ -302,7 +310,14 @@ ErrCode INTERFACE_FUNC TmsClientPropertyObjectBaseImpl::endUpdate() request->inputArgumentsSize = 0; request->objectId = nodeId.copyAndGetDetachedValue(); request->methodId = endUpdateId.copyAndGetDetachedValue(); - client->callMethod(request); + OpcUaObject callResult = client->callMethod(request); + if (callResult->statusCode != UA_STATUSCODE_GOOD) + { + if (callResult->statusCode == UA_STATUSCODE_BADUSERACCESSDENIED) + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_ACCESSDENIED); + else + return DAQ_MAKE_ERROR_INFO(OPENDAQ_ERR_CALLFAILED); + } return OPENDAQ_SUCCESS; } @@ -430,6 +445,7 @@ void TmsClientPropertyObjectBaseImpl::addMethodProperties(const OpcUaNodeI ListPtr inputArgs; ListPtr outputArgs; uint32_t numberInList = std::numeric_limits::max(); + bool commonExecutable = true; try { @@ -450,6 +466,7 @@ void TmsClientPropertyObjectBaseImpl::addMethodProperties(const OpcUaNodeI const auto numberInListId = browser->getChildNodeId(childNodeId, "NumberInList"); numberInList = VariantConverter::ToDaqObject(reader->getValue(numberInListId, UA_ATTRIBUTEID_VALUE)); } + commonExecutable = getExecutePermission(childNodeId); } catch(const std::exception& e) { @@ -462,13 +479,13 @@ void TmsClientPropertyObjectBaseImpl::addMethodProperties(const OpcUaNodeI if (outputArgs.assigned() && outputArgs.getCount() == 1) { auto callableInfo = FunctionInfo(outputArgs[0].getType(), inputArgs); - prop = FunctionPropertyBuilder(propName, callableInfo).setReadOnly(true).build(); + prop = FunctionPropertyBuilder(propName, callableInfo).setReadOnly(true).setVisible(commonExecutable).build(); func = TmsClientFunction(clientContext, daqContext, parentNodeId, childNodeId); } else { auto callableInfo = ProcedureInfo(inputArgs); - prop = FunctionPropertyBuilder(propName, callableInfo).setReadOnly(true).build(); + prop = FunctionPropertyBuilder(propName, callableInfo).setReadOnly(true).setVisible(commonExecutable).build(); func = TmsClientProcedure(clientContext, daqContext, parentNodeId, childNodeId); } @@ -564,6 +581,22 @@ void TmsClientPropertyObjectBaseImpl::browseRawProperties() } +template +void TmsClientPropertyObjectBaseImpl::setLocksForAttributes() +{ + if (!getAttributeWritePermission(nodeId)) + { + if (this->objPtr.template supportsInterface()) + { + this->objPtr.template asPtrOrNull(true).lockAllAttributes(); + } + else + { + LOG_W("Object does not support IComponentPrivate, cannot lock attributes for write protection"); + } + } +} + template bool TmsClientPropertyObjectBaseImpl::isIgnoredMethodProperty(const std::string& browseName) { diff --git a/shared/libraries/opcuatms/opcuatms_client/src/tms_attribute_collector.cpp b/shared/libraries/opcuatms/opcuatms_client/src/tms_attribute_collector.cpp index 8b670a7..f9e74e6 100644 --- a/shared/libraries/opcuatms/opcuatms_client/src/tms_attribute_collector.cpp +++ b/shared/libraries/opcuatms/opcuatms_client/src/tms_attribute_collector.cpp @@ -113,6 +113,13 @@ void TmsAttributeCollector::collectFunctionBlockAttributes(const OpcUaNodeId& no void TmsAttributeCollector::collectInputPortAttributes(const OpcUaNodeId& nodeId) { collectBaseObjectAttributes(nodeId); + + const auto& references = browser->browse(nodeId); + for (const auto& [refNodeId, ref] : references.byNodeId) + { + if (ref->nodeClass == UA_NODECLASS_METHOD) + collectMethodAttributes(refNodeId); + } } void TmsAttributeCollector::collectSignalAttributes(const OpcUaNodeId& nodeId) @@ -165,6 +172,10 @@ void TmsAttributeCollector::collectPropertyAttributes(const OpcUaNodeId& nodeId) attributes.insert({nodeId, UA_ATTRIBUTEID_DISPLAYNAME}); attributes.insert({nodeId, UA_ATTRIBUTEID_DESCRIPTION}); attributes.insert({nodeId, UA_ATTRIBUTEID_DATATYPE}); + attributes.insert({nodeId, UA_ATTRIBUTEID_ACCESSLEVEL}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERACCESSLEVEL}); + attributes.insert({nodeId, UA_ATTRIBUTEID_EXECUTABLE}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USEREXECUTABLE}); if (browser->hasReference(nodeId, "ValidationExpression")) attributes.insert({browser->getChildNodeId(nodeId, "ValidationExpression"), UA_ATTRIBUTEID_VALUE}); @@ -193,6 +204,8 @@ void TmsAttributeCollector::collectEvaluationPropertyAttributes(const OpcUaNodeI void TmsAttributeCollector::collectBaseObjectAttributes(const OpcUaNodeId& nodeId) { attributes.insert({nodeId, UA_ATTRIBUTEID_NODECLASS}); + attributes.insert({nodeId, UA_ATTRIBUTEID_WRITEMASK}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERWRITEMASK}); if (browser->hasReference(nodeId, "NumberInList")) attributes.insert({browser->getChildNodeId(nodeId, "NumberInList"), UA_ATTRIBUTEID_VALUE}); @@ -200,6 +213,9 @@ void TmsAttributeCollector::collectBaseObjectAttributes(const OpcUaNodeId& nodeI void TmsAttributeCollector::collectMethodAttributes(const OpcUaNodeId& nodeId) { + attributes.insert({nodeId, UA_ATTRIBUTEID_EXECUTABLE}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USEREXECUTABLE}); + if (browser->hasReference(nodeId, "InputArguments")) attributes.insert({browser->getChildNodeId(nodeId, "InputArguments"), UA_ATTRIBUTEID_VALUE}); if (browser->hasReference(nodeId, "OutputArguments")) @@ -215,6 +231,9 @@ void TmsAttributeCollector::collectVariableBlockAttributes(const OpcUaNodeId& no void TmsAttributeCollector::collectIoNode(const OpcUaNodeId& nodeId) { + attributes.insert({nodeId, UA_ATTRIBUTEID_WRITEMASK}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERWRITEMASK}); + const auto& references = browser->browse(nodeId); for (const auto& [refNodeId, ref] : references.byNodeId) @@ -228,6 +247,9 @@ void TmsAttributeCollector::collectIoNode(const OpcUaNodeId& nodeId) void TmsAttributeCollector::collectInputPortNode(const OpcUaNodeId& nodeId) { + attributes.insert({nodeId, UA_ATTRIBUTEID_WRITEMASK}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERWRITEMASK}); + const auto& references = browser->browse(nodeId); for (const auto& [refNodeId, ref] : references.byNodeId) @@ -239,6 +261,9 @@ void TmsAttributeCollector::collectInputPortNode(const OpcUaNodeId& nodeId) void TmsAttributeCollector::collectFunctionBlockNode(const OpcUaNodeId& nodeId) { + attributes.insert({nodeId, UA_ATTRIBUTEID_WRITEMASK}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERWRITEMASK}); + const auto& references = browser->browse(nodeId); for (const auto& [refNodeId, ref] : references.byNodeId) @@ -250,6 +275,9 @@ void TmsAttributeCollector::collectFunctionBlockNode(const OpcUaNodeId& nodeId) void TmsAttributeCollector::collectSignalsNode(const OpcUaNodeId& nodeId) { + attributes.insert({nodeId, UA_ATTRIBUTEID_WRITEMASK}); + attributes.insert({nodeId, UA_ATTRIBUTEID_USERWRITEMASK}); + const auto& signalReferences = browser->browse(nodeId); for (const auto& [refNodeId, ref] : signalReferences.byNodeId) diff --git a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_function_property.cpp b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_function_property.cpp index 6ac1b9b..fa537bc 100644 --- a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_function_property.cpp +++ b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_function_property.cpp @@ -8,6 +8,7 @@ #include "tms_object_integration_test.h" #include #include +#include using namespace daq; using namespace opcua::tms; @@ -211,8 +212,7 @@ TEST_F(TmsFunctionTest, InvalidArgTypes) auto [serverObj, clientObj] = registerPropertyObject(obj); ProcedurePtr clientProc = clientObj.getPropertyValue("Proc"); - ASSERT_NO_THROW(clientProc("foo")); - ASSERT_EQ(getLastMessage(), "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); + ASSERT_THROW_MSG(clientProc(), CallFailedException, "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); } // NOTE: Should this throw an error? @@ -240,11 +240,8 @@ TEST_F(TmsFunctionTest, InvalidArgCount) auto [serverObj, clientObj] = registerPropertyObject(obj); ProcedurePtr clientProc = clientObj.getPropertyValue("Proc"); - ASSERT_NO_THROW(clientProc()); - ASSERT_EQ(getLastMessage(), "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); - - ASSERT_NO_THROW(clientProc(1, 2)); - ASSERT_EQ(getLastMessage(), "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); + ASSERT_THROW_MSG(clientProc(), CallFailedException, "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); + ASSERT_THROW_MSG(clientProc(1, 2), CallFailedException, "Failed to call procedure on OpcUA client. Error: \"Calling procedure\""); } TEST_F(TmsFunctionTest, ProcedureArgumentInfo) @@ -322,6 +319,6 @@ TEST_F(TmsFunctionTest, ServerThrow) auto [serverObj, clientObj] = registerPropertyObject(obj); FunctionPtr clientFunc = clientObj.getPropertyValue("Func"); - ASSERT_NO_THROW(clientFunc()); - ASSERT_EQ(getLastMessage(), "Failed to call function on OpcUA client. Error in \"Calling function\""); + ASSERT_ANY_THROW(clientFunc()); + ASSERT_THROW_MSG(clientFunc(), CallFailedException, "Failed to call function on OpcUA client. Error in \"Calling function\""); } diff --git a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_input_port.cpp b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_input_port.cpp index 6c7245c..bd16bd8 100644 --- a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_input_port.cpp +++ b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_input_port.cpp @@ -122,7 +122,9 @@ TEST_F(TmsInputPortTest, MethodAcceptsSignal) InputPortPtr clientInputPort = TmsClientInputPort(NullContext(), nullptr, "inputPort", clientContext, nodeId); //TODO: More testing when the server in fact really checks the signal if the signal is ok - EXPECT_THROW(clientInputPort.acceptsSignal(signal), daq::opcua::OpcUaClientCallNotAvailableException); + bool ok = false; + ASSERT_NO_THROW(ok = clientInputPort.acceptsSignal(signal)); + EXPECT_TRUE(ok); } TEST_F(TmsInputPortTest, ConnectedToReference) diff --git a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_property_object.cpp b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_property_object.cpp index 01ef577..ddc01f5 100644 --- a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_property_object.cpp +++ b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_property_object.cpp @@ -665,7 +665,6 @@ TEST_F(TmsNestedPropertyObjectTest, TestNestedObjectClientFunctionCall) ASSERT_EQ(func2(5), 5); } -// NOTE: OPC UA does not propagate error codes. TEST_F(TmsNestedPropertyObjectTest, TestNestedObjectClientProcedureCall) { const PropertyObjectPtr child = clientObj.getPropertyValue("child1.child1_2.child1_2_1"); @@ -673,7 +672,7 @@ TEST_F(TmsNestedPropertyObjectTest, TestNestedObjectClientProcedureCall) ProcedurePtr proc2 = child.getPropertyValue("Procedure"); ASSERT_NO_THROW(proc1(5)); - ASSERT_NO_THROW(proc1(0)); // Outputs warning + ASSERT_ANY_THROW(proc1(0)); ASSERT_NO_THROW(proc2(5)); - ASSERT_NO_THROW(proc2(0)); // Outputs warning + ASSERT_ANY_THROW(proc2(0)); } diff --git a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_user_access.cpp b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_user_access.cpp index c29b519..ee07a63 100644 --- a/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_user_access.cpp +++ b/shared/libraries/opcuatms/tests/opcuatms_integration/test_tms_user_access.cpp @@ -25,37 +25,14 @@ #include #include "test_user_helper.h" #include "tms_object_integration_test.h" +#include using namespace daq; using namespace opcua::tms; -// using namespace opcua; - -#define DISABLE_EXPECTED_FAILURE -#ifdef DISABLE_EXPECTED_FAILURE -#define DISABLE_EXPECT_ANY_THROW(statement) EXPECT_NO_THROW(statement) -#else -#define DISABLE_EXPECT_ANY_THROW(statement) EXPECT_ANY_THROW(statement) -#endif -#ifdef DISABLE_EXPECTED_FAILURE -#define DISABLE_EXPECT_EQ(val1, val2) EXPECT_NE(val1, val2) -#else -#define DISABLE_EXPECT_EQ(val1, val2) EXPECT_EQ(val1, val2) -#endif - -class TmsUserAccessTest : public TmsObjectIntegrationTest, public testing::Test + +class TmsUserAccess : public TmsObjectIntegrationTest { public: - void SetUp() override - { - TmsObjectIntegrationTest::Init(); - createDevice(); - } - - void TearDown() override - { - TmsObjectIntegrationTest::Clear(); - } - InstancePtr createDevice() { const auto moduleManager = ModuleManager("[[none]]"); @@ -131,11 +108,139 @@ class TmsUserAccessTest : public TmsObjectIntegrationTest, public testing::Test std::cout << "---end---" << std::endl; } + bool ListContains(daq::ListPtr list, const std::string& value) + { + for (const auto& item : list) + { + if (item == value) + return true; + } + return false; + }; + InstancePtr instance; DevicePtr device; FunctionBlockPtr fb; }; +class TmsUserAccessTest : public TmsUserAccess, public testing::Test +{ +public: + void SetUp() override + { + TmsObjectIntegrationTest::Init(); + createDevice(); + } + + void TearDown() override + { + TmsObjectIntegrationTest::Clear(); + } +}; + +class UserPermission +{ +public: + enum PermissionType : uint32_t + { + Read = 0b001, + Write = 0b010, + Execute = 0b100 + }; + + UserPermission(PermissionType permissionType) : permissionType(permissionType) {}; + + bool hasPermission(PermissionType permissionType) const + { + return ((static_cast(this->permissionType) & static_cast(permissionType)) == permissionType); + } +protected: + PermissionType permissionType; +}; + +namespace { + std::string ParamNameGenerator(const testing::TestParamInfo& info) + { + using UP = UserPermission::PermissionType; + UserPermission perms(info.param); + if (perms.hasPermission(UP::Write) && perms.hasPermission(UP::Execute)) + { + return "adminRights"; + } + else if (perms.hasPermission(UP::Write)) + { + return "writerRights"; + } + else if (perms.hasPermission(UP::Execute)) + { + return "executorRights"; + } + else if (perms.hasPermission(UP::Read)) + { + return "readerRights"; + } + else + { + throw std::runtime_error("Invalid permissions for test user"); + } + return ""; + } +} + +class TmsUserAccessPTest : public TmsUserAccess, public ::testing::TestWithParam +{ +public: + void SetUp() override + { + TmsObjectIntegrationTest::Init(); + createDevice(); + serverContext = std::make_shared(ctx, instance.getRootDevice()); + } + + void TearDown() override + { + TmsObjectIntegrationTest::Clear(); + } + + void CreateClient(UserPermission perms) + { + using UP = UserPermission::PermissionType; + if (perms.hasPermission(UP::Write) && perms.hasPermission(UP::Execute)) + { + client = CreateAndConnectTestClient("adminUser", "adminUserPass"); + } + else if (perms.hasPermission(UP::Write)) + { + client = CreateAndConnectTestClient("writerUser", "writerUserPass"); + } + else if (perms.hasPermission(UP::Execute)) + { + client = CreateAndConnectTestClient("executorUser", "executorUserPass"); + } + else if (perms.hasPermission(UP::Read)) + { + client = CreateAndConnectTestClient("readerUser", "readerUserPass"); + } + else + { + throw std::runtime_error("Invalid permissions for test user"); + } + + ctxClient = daq::NullContext(logger); + clientContext = std::make_shared(client, ctxClient); + + clientContext->addEnumerationTypesToTypeManager(); + } + + const std::vector& attributesToCheck() + { + return attributes; + } + +protected: + std::vector attributes{"Name", "Description", "Active"}; +}; + TEST_F(TmsUserAccessTest, CreateClientDevice) { auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); @@ -189,8 +294,10 @@ TEST_F(TmsUserAccessTest, CommonUserForRootDevice) ASSERT_ANY_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); } -TEST_F(TmsUserAccessTest, ReaderUser) +TEST_P(TmsUserAccessPTest, CommonChecks) { + const UserPermission userPerm(GetParam()); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); @@ -198,7 +305,7 @@ TEST_F(TmsUserAccessTest, ReaderUser) auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); auto nodeId = tmsPropertyObject.registerOpcUaNode(); auto ctx = NullContext(); - CreateClient("readerUser", "readerUserPass"); + CreateClient(userPerm); DevicePtr clientDevice; ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); @@ -212,56 +319,74 @@ TEST_F(TmsUserAccessTest, ReaderUser) EXPECT_EQ(device.getAllProperties().getCount() - 1, mockDevice.getAllProperties().getCount()); EXPECT_EQ(fb.getAllProperties().getCount(), mockFb.getAllProperties().getCount()); - // struct property in a mock device EXPECT_EQ(device.getSignals().getCount() - 1, mockDevice.getSignals().getCount()); EXPECT_EQ(fb.getSignals().getCount() - 1, mockFb.getSignals().getCount()); +} - // an exception has not been implemented on opc ua client side - // it catchs opcua-wrapper exeption (via daqTry) and print error message to log but return seccess - { - DISABLE_EXPECT_ANY_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); - EXPECT_NE(mockDevice.getPropertyValue("TestProperty"), "NewValue"); - - DISABLE_EXPECT_ANY_THROW(mockDevice.setName("NewDevName")); - EXPECT_NE(mockDevice.getName(), "NewDevName"); +TEST_P(TmsUserAccessPTest, Properties) +{ + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); - DISABLE_EXPECT_ANY_THROW(mockDevice.setDescription("NewDeviceDescription")); - EXPECT_NE(mockDevice.getDescription(), "NewDeviceDescription"); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); - ProcedurePtr proc; - EXPECT_NO_THROW(proc = mockDevice.getPropertyValue("stop")); - // an exception has not been implemented on opc ua client side - // it checks errors and print error message to log but return success - DISABLE_EXPECT_ANY_THROW(proc()); + auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); + auto nodeId = tmsPropertyObject.registerOpcUaNode(); + auto ctx = NullContext(); + CreateClient(userPerm); + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); - DISABLE_EXPECT_ANY_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); - EXPECT_NE(mockFb.getPropertyValue("TestConfigString"), "NewValue"); + ASSERT_EQ(clientDevice.getDevices().getCount(), 2); + ASSERT_EQ(clientDevice.getFunctionBlocks().getCount(), 1); - DISABLE_EXPECT_ANY_THROW(mockFb.setName("NewFbName")); - EXPECT_NE(mockFb.getName(), "NewFbName"); + DevicePtr mockDevice = clientDevice.getDevices()[1]; + FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - DISABLE_EXPECT_ANY_THROW(mockFb.setDescription("NewFbDescription")); - EXPECT_NE(mockFb.getDescription(), "NewFbDescription"); + EXPECT_EQ(mockDevice.getProperty("TestProperty").getReadOnly(), userPerm.hasPermission(UP::Write) ? False : True); + if (userPerm.hasPermission(UP::Write)) + { + EXPECT_NO_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); + EXPECT_EQ(mockDevice.getPropertyValue("TestProperty"), "NewValue"); + } + else + { + EXPECT_ANY_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); + EXPECT_NE(mockDevice.getPropertyValue("TestProperty"), "NewValue"); } + ProcedurePtr proc; + EXPECT_EQ(mockDevice.getProperty("stop").getVisible(), userPerm.hasPermission(UP::Execute) ? True : False); + EXPECT_NO_THROW(proc = mockDevice.getPropertyValue("stop")); + if (userPerm.hasPermission(UP::Execute)) + { + EXPECT_NO_THROW(proc()); + } + else { - // due to lack of access rights (the reader does not have write and execute permissions), these calls should fail - // an exception has not been implemented on opc ua client side - // it calls execution without checking of user access rights and when even doesn't check a call result - DISABLE_EXPECT_ANY_THROW(mockDevice->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockDevice->endUpdate()); + EXPECT_ANY_THROW(proc()); + } + - DISABLE_EXPECT_ANY_THROW(mockFb->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockFb->endUpdate()); + EXPECT_EQ(mockFb.getProperty("TestConfigString").getReadOnly(), userPerm.hasPermission(UP::Write) ? False : True); + if (userPerm.hasPermission(UP::Write)) + { + EXPECT_NO_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); + EXPECT_EQ(mockFb.getPropertyValue("TestConfigString"), "NewValue"); } + else { - EXPECT_ANY_THROW(clientDevice.addFunctionBlock("mock_fb_uid")); - EXPECT_ANY_THROW(clientDevice.removeFunctionBlock(mockFb)); + EXPECT_ANY_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); + EXPECT_NE(mockFb.getPropertyValue("TestConfigString"), "NewValue"); } } -TEST_F(TmsUserAccessTest, WriterUser) +TEST_P(TmsUserAccessPTest, Attributes) { + const UserPermission userPerm(GetParam()); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); @@ -269,7 +394,7 @@ TEST_F(TmsUserAccessTest, WriterUser) auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); auto nodeId = tmsPropertyObject.registerOpcUaNode(); auto ctx = NullContext(); - CreateClient("writerUser", "writerUserPass"); + CreateClient(userPerm); DevicePtr clientDevice; ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); @@ -279,58 +404,55 @@ TEST_F(TmsUserAccessTest, WriterUser) DevicePtr mockDevice = clientDevice.getDevices()[1]; FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - // one private signal in MockFunctionBlockImpl. and one in MockPhysicalDeviceImpl - EXPECT_EQ(device.getAllProperties().getCount() - 1, mockDevice.getAllProperties().getCount()); - EXPECT_EQ(fb.getAllProperties().getCount(), mockFb.getAllProperties().getCount()); + daq::ListPtr list = List(); + list.pushBack(mockDevice); + list.pushBack(mockFb); - // struct property in a mock device - EXPECT_EQ(device.getSignals().getCount() - 1, mockDevice.getSignals().getCount()); - EXPECT_EQ(fb.getSignals().getCount() - 1, mockFb.getSignals().getCount()); + for (const auto& sig : mockDevice.getSignals()) + list.pushBack(sig); - { - EXPECT_NO_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); - EXPECT_EQ(mockDevice.getPropertyValue("TestProperty"), "NewValue"); - - EXPECT_NO_THROW(mockDevice.setName("NewDevName")); - EXPECT_EQ(mockDevice.getName(), "NewDevName"); + for (const auto& channel : mockDevice.getChannels()) + list.pushBack(channel); - EXPECT_NO_THROW(mockDevice.setDescription("NewDeviceDescription")); - EXPECT_EQ(mockDevice.getDescription(), "NewDeviceDescription"); + for (const auto& sig : mockFb.getSignals()) + list.pushBack(sig); - ProcedurePtr proc; - EXPECT_NO_THROW(proc = mockDevice.getPropertyValue("stop")); - // an exception has not been implemented on opc ua client side - // it checks errors and print error message to log but return success - DISABLE_EXPECT_ANY_THROW(proc()); + for (const auto& ip : mockFb.getInputPorts()) + list.pushBack(ip); - EXPECT_NO_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); - EXPECT_EQ(mockFb.getPropertyValue("TestConfigString"), "NewValue"); + { + for (const auto& comp : list) + { + const auto listLockedAttr = comp.getLockedAttributes(); + for (const auto& attr : attributesToCheck()) + { + EXPECT_EQ(ListContains(listLockedAttr, attr), !userPerm.hasPermission(UserPermission::PermissionType::Write)); + } - EXPECT_NO_THROW(mockFb.setName("NewFbName")); - EXPECT_EQ(mockFb.getName(), "NewFbName"); + EXPECT_NO_THROW(comp.setName("NewCompName")); + EXPECT_NO_THROW(comp.setDescription("NewCompDescription")); - EXPECT_NO_THROW(mockFb.setDescription("NewFbDescription")); - EXPECT_EQ(mockFb.getDescription(), "NewFbDescription"); - } + if (userPerm.hasPermission(UserPermission::PermissionType::Write)) + { - { - // due to lack of access rights (the writer does not have execute permissions), these calls should fail - // an exception has not been implemented on opc ua client side - // it calls execution without checking of user access rights and when even doesn't check a call result - DISABLE_EXPECT_ANY_THROW(mockDevice->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockDevice->endUpdate()); + EXPECT_EQ(comp.getName(), "NewCompName"); + EXPECT_EQ(comp.getDescription(), "NewCompDescription"); + } + else + { + EXPECT_NE(comp.getName(), "NewCompName"); + EXPECT_NE(comp.getDescription(), "NewCompDescription"); + } - DISABLE_EXPECT_ANY_THROW(mockFb->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockFb->endUpdate()); - } - { - EXPECT_ANY_THROW(clientDevice.addFunctionBlock("mock_fb_uid")); - EXPECT_ANY_THROW(clientDevice.removeFunctionBlock(mockFb)); + } } } -TEST_F(TmsUserAccessTest, ExecutorUser) +TEST_P(TmsUserAccessPTest, BeginEndUpdate) { + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); @@ -338,7 +460,7 @@ TEST_F(TmsUserAccessTest, ExecutorUser) auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); auto nodeId = tmsPropertyObject.registerOpcUaNode(); auto ctx = NullContext(); - CreateClient("executorUser", "executorUserPass"); + CreateClient(userPerm); DevicePtr clientDevice; ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); @@ -348,60 +470,110 @@ TEST_F(TmsUserAccessTest, ExecutorUser) DevicePtr mockDevice = clientDevice.getDevices()[1]; FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - // one private signal in MockFunctionBlockImpl. and one in MockPhysicalDeviceImpl - EXPECT_EQ(device.getAllProperties().getCount() - 1, mockDevice.getAllProperties().getCount()); - EXPECT_EQ(fb.getAllProperties().getCount(), mockFb.getAllProperties().getCount()); + const ErrCode expectedCode = (userPerm.hasPermission(UP::Write | UP::Execute)) ? OPENDAQ_SUCCESS : OPENDAQ_ERR_ACCESSDENIED; - // struct property in a mock device - EXPECT_EQ(device.getSignals().getCount() - 1, mockDevice.getSignals().getCount()); - EXPECT_EQ(fb.getSignals().getCount() - 1, mockFb.getSignals().getCount()); + ErrCode code = OPENDAQ_SUCCESS; + ASSERT_NO_THROW(code = mockDevice->beginUpdate()); + ASSERT_ERROR_CODE_EQ(code, expectedCode); + ASSERT_NO_THROW(code = mockDevice->endUpdate()); + ASSERT_ERROR_CODE_EQ(code, expectedCode); - // due to lack of access rights (the executor does not have write permissions), ally write calls should fail - // an exception has not been implemented on opc ua client side - // it catchs opcua-wrapper exeption (via daqTry) and print error message to log but return seccess - { - DISABLE_EXPECT_ANY_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); - EXPECT_NE(mockDevice.getPropertyValue("TestProperty"), "NewValue"); + ASSERT_NO_THROW(code = mockFb->beginUpdate()); + ASSERT_ERROR_CODE_EQ(code, expectedCode); + ASSERT_NO_THROW(code = mockFb->endUpdate()); + ASSERT_ERROR_CODE_EQ(code, expectedCode); +} - DISABLE_EXPECT_ANY_THROW(mockDevice.setName("NewDevName")); - EXPECT_NE(mockDevice.getName(), "NewDevName"); +TEST_P(TmsUserAccessPTest, AvailableComponents) +{ + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); - DISABLE_EXPECT_ANY_THROW(mockDevice.setDescription("NewDeviceDescription")); - EXPECT_NE(mockDevice.getDescription(), "NewDeviceDescription"); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); - ProcedurePtr proc; - EXPECT_NO_THROW(proc = mockDevice.getPropertyValue("stop")); - EXPECT_NO_THROW(proc()); + auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); + auto nodeId = tmsPropertyObject.registerOpcUaNode(); + auto ctx = NullContext(); + CreateClient(userPerm); + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); - DISABLE_EXPECT_ANY_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); - EXPECT_NE(mockFb.getPropertyValue("TestConfigString"), "NewValue"); + ASSERT_EQ(clientDevice.getDevices().getCount(), 2); + ASSERT_EQ(clientDevice.getFunctionBlocks().getCount(), 1); - DISABLE_EXPECT_ANY_THROW(mockFb.setName("NewFbName")); - EXPECT_NE(mockFb.getName(), "NewFbName"); + DevicePtr mockDevice = clientDevice.getDevices()[1]; + FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - DISABLE_EXPECT_ANY_THROW(mockFb.setDescription("NewFbDescription")); - EXPECT_NE(mockFb.getDescription(), "NewFbDescription"); + if (userPerm.hasPermission(UP::Write)) + { + EXPECT_EQ(clientDevice.getAvailableFunctionBlockTypes().getCount(), instance.getRootDevice().getAvailableFunctionBlockTypes().getCount()); + EXPECT_EQ(mockDevice.getAvailableFunctionBlockTypes().getCount(), device.getAvailableFunctionBlockTypes().getCount()); } - + else { - // due to lack of access rights (the executor does not have write permissions), these calls should fail - // an exception has not been implemented on opc ua client side - // it calls execution without checking of user access rights and when even doesn't check a call result - DISABLE_EXPECT_ANY_THROW(mockDevice->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockDevice->endUpdate()); + EXPECT_EQ(clientDevice.getAvailableFunctionBlockTypes().getCount(), 0); + EXPECT_EQ(mockDevice.getAvailableFunctionBlockTypes().getCount(), 0); + } - DISABLE_EXPECT_ANY_THROW(mockFb->beginUpdate()); - DISABLE_EXPECT_ANY_THROW(mockFb->endUpdate()); + { + // getAvailableFunctionBlockTypes() has not been implemented for a function block (client and server sides), + // so it will return 0 regardless of permissions. + EXPECT_EQ(mockFb.getAvailableFunctionBlockTypes().getCount(), 0); + + // getAvailableDeviceTypes() has not been implemented for a device (client and server sides), + // so it will return 0 regardless of permissions. + EXPECT_EQ(clientDevice.getAvailableDeviceTypes().getCount(), 0); + EXPECT_EQ(mockDevice.getAvailableDeviceTypes().getCount(), 0); + + // getAvailableDevices() has not been implemented for a device (client and server sides), + // so it will return 0 regardless of permissions. + EXPECT_EQ(clientDevice.getAvailableDevices().getCount(), 0); + EXPECT_EQ(mockDevice.getAvailableDevices().getCount(), 0); } +} +TEST_P(TmsUserAccessPTest, AddRemoveFb) +{ + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); + + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + + auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); + auto nodeId = tmsPropertyObject.registerOpcUaNode(); + auto ctx = NullContext(); + CreateClient(userPerm); + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); + + ASSERT_EQ(clientDevice.getDevices().getCount(), 2); + ASSERT_EQ(clientDevice.getFunctionBlocks().getCount(), 1); + + DevicePtr mockDevice = clientDevice.getDevices()[1]; + FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; + + if (userPerm.hasPermission(UP::Write | UP::Execute)) + { + EXPECT_NO_THROW(clientDevice.addFunctionBlock("mock_fb_uid")); + EXPECT_NO_THROW(clientDevice.removeFunctionBlock(mockFb)); + } + else { EXPECT_ANY_THROW(clientDevice.addFunctionBlock("mock_fb_uid")); EXPECT_ANY_THROW(clientDevice.removeFunctionBlock(mockFb)); } } -TEST_F(TmsUserAccessTest, AdminUser) +TEST_P(TmsUserAccessPTest, DeviceOperationMode) { + using UP = UserPermission::PermissionType; + using OMT = daq::OperationModeType; + const UserPermission userPerm(GetParam()); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); @@ -409,7 +581,7 @@ TEST_F(TmsUserAccessTest, AdminUser) auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); auto nodeId = tmsPropertyObject.registerOpcUaNode(); auto ctx = NullContext(); - CreateClient("adminUser", "adminUserPass"); + CreateClient(userPerm); DevicePtr clientDevice; ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); @@ -419,48 +591,123 @@ TEST_F(TmsUserAccessTest, AdminUser) DevicePtr mockDevice = clientDevice.getDevices()[1]; FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - // one private signal in MockFunctionBlockImpl. and one in MockPhysicalDeviceImpl - EXPECT_EQ(device.getAllProperties().getCount() - 1, mockDevice.getAllProperties().getCount()); - EXPECT_EQ(fb.getAllProperties().getCount(), mockFb.getAllProperties().getCount()); + if (userPerm.hasPermission(UP::Write)) + { + OMT op = OMT::SafeOperation; + ASSERT_NO_THROW(clientDevice.setOperationMode(OMT::Idle)); + ASSERT_NO_THROW(op = clientDevice.getOperationMode()); + EXPECT_EQ(op, OMT::Idle); + + op = OMT::Idle; + ASSERT_NO_THROW(clientDevice.setOperationMode(OMT::SafeOperation)); + ASSERT_NO_THROW(op = clientDevice.getOperationMode()); + EXPECT_EQ(op, OMT::SafeOperation); + + op = OMT::Idle; + ASSERT_NO_THROW(clientDevice.setOperationMode(OMT::Operation)); + ASSERT_NO_THROW(op = clientDevice.getOperationMode()); + EXPECT_EQ(op, OMT::Operation); + } + else + { + OMT op = OMT::Idle; + ASSERT_NO_THROW(op = clientDevice.getOperationMode()); - // struct property in a mock device - EXPECT_EQ(device.getSignals().getCount() - 1, mockDevice.getSignals().getCount()); - EXPECT_EQ(fb.getSignals().getCount() - 1, mockFb.getSignals().getCount()); + OMT opToSet = (op == OMT::Idle) ? OMT::SafeOperation : OMT::Idle; + EXPECT_ANY_THROW(clientDevice.setOperationMode(opToSet)); + ASSERT_NO_THROW(op = clientDevice.getOperationMode()); + EXPECT_NE(op, opToSet); + } +} - { - EXPECT_NO_THROW(mockDevice.getProperty("TestProperty").setValue(String("NewValue"))); - EXPECT_EQ(mockDevice.getPropertyValue("TestProperty"), "NewValue"); +TEST_P(TmsUserAccessPTest, Connect) +{ + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); - EXPECT_NO_THROW(mockDevice.setName("NewDevName")); - EXPECT_EQ(mockDevice.getName(), "NewDevName"); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); - EXPECT_NO_THROW(mockDevice.setDescription("NewDeviceDescription")); - EXPECT_EQ(mockDevice.getDescription(), "NewDeviceDescription"); + auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); + auto nodeId = tmsPropertyObject.registerOpcUaNode(); + auto ctx = NullContext(); + CreateClient(userPerm); + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); - ProcedurePtr proc; - EXPECT_NO_THROW(proc = mockDevice.getPropertyValue("stop")); - EXPECT_NO_THROW(proc()); + ASSERT_EQ(clientDevice.getDevices().getCount(), 2); + ASSERT_EQ(clientDevice.getFunctionBlocks().getCount(), 1); - EXPECT_NO_THROW(mockFb.getProperty("TestConfigString").setValue(String("NewValue"))); - EXPECT_EQ(mockFb.getPropertyValue("TestConfigString"), "NewValue"); + DevicePtr mockDevice = clientDevice.getDevices()[1]; + FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; - EXPECT_NO_THROW(mockFb.setName("NewFbName")); - EXPECT_EQ(mockFb.getName(), "NewFbName"); + daq::ListPtr ip; + daq::ListPtr sig; + ASSERT_NO_THROW(ip = mockFb.getInputPorts()); + ASSERT_NE(ip.getCount(), 0); + ASSERT_NO_THROW(sig = mockDevice.getSignals()); + ASSERT_NE(sig.getCount(), 0); - EXPECT_NO_THROW(mockFb.setDescription("NewFbDescription")); - EXPECT_EQ(mockFb.getDescription(), "NewFbDescription"); + if (userPerm.hasPermission(UP::Write | UP::Execute)) + { + ASSERT_NO_THROW(ip[0].connect(sig[0])); } - + else { - EXPECT_NO_THROW(mockDevice->beginUpdate()); - EXPECT_NO_THROW(mockDevice->endUpdate()); + ASSERT_ANY_THROW(ip[0].connect(sig[0])); + } +} + +TEST_P(TmsUserAccessPTest, Disconnect) +{ + using UP = UserPermission::PermissionType; + const UserPermission userPerm(GetParam()); - EXPECT_NO_THROW(mockFb->beginUpdate()); - EXPECT_NO_THROW(mockFb->endUpdate()); + fb.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + device.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + instance.getPermissionManager().setPermissions(test_helpers::CreatePermissionsBuilder().build()); + + { + daq::ListPtr ip; + daq::ListPtr sig; + ASSERT_NO_THROW(ip = fb.getInputPorts()); + ASSERT_NE(ip.getCount(), 0); + ASSERT_NO_THROW(sig = device.getSignals()); + ASSERT_NE(sig.getCount(), 0); + ASSERT_NO_THROW(ip[0].connect(sig[0])); } + auto tmsPropertyObject = TmsServerDevice(instance, this->getServer(), ctx, serverContext); + auto nodeId = tmsPropertyObject.registerOpcUaNode(); + auto ctx = NullContext(); + CreateClient(userPerm); + DevicePtr clientDevice; + ASSERT_NO_THROW(clientDevice = TmsClientRootDevice(ctx, nullptr, "dev", clientContext, nodeId)); + + ASSERT_EQ(clientDevice.getDevices().getCount(), 2); + ASSERT_EQ(clientDevice.getFunctionBlocks().getCount(), 1); + + DevicePtr mockDevice = clientDevice.getDevices()[1]; + FunctionBlockPtr mockFb = clientDevice.getFunctionBlocks()[0]; + + daq::ListPtr ip; + ASSERT_NO_THROW(ip = mockFb.getInputPorts()); + ASSERT_NE(ip.getCount(), 0); + + if (userPerm.hasPermission(UP::Write | UP::Execute)) { - EXPECT_NO_THROW(clientDevice.addFunctionBlock("mock_fb_uid")); - EXPECT_NO_THROW(clientDevice.removeFunctionBlock(mockFb)); + ASSERT_NO_THROW(ip[0].disconnect()); + } + else + { + ASSERT_ANY_THROW(ip[0].disconnect()); } } + +INSTANTIATE_TEST_SUITE_P(UserAccess, + TmsUserAccessPTest, + ::testing::Values(UserPermission::PermissionType::Read, + UserPermission::PermissionType::Read | UserPermission::PermissionType::Write, + UserPermission::PermissionType::Read | UserPermission::PermissionType::Execute, + UserPermission::PermissionType::Read | UserPermission::PermissionType::Write | UserPermission::PermissionType::Execute), ParamNameGenerator);