Parcourir la source

feature: additional userExecutable callback for method + object; use
callback for reading access rights attributes

Julius Pfrommer il y a 8 ans
Parent
commit
8282de5af3

+ 6 - 2
include/ua_server.h

@@ -104,7 +104,11 @@ typedef struct {
     UA_Byte (*getUserAccessLevel)(const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *nodeId);
 
     /* Additional access control for method nodes */
-    UA_Boolean (*getUserExecutable)(const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *nodeId);
+    UA_Boolean (*getUserExecutable)(const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *methodId);
+
+    /* Additional access control for calling a method node in the context of a specific object */
+    UA_Boolean (*getUserExecutableOnObject)(const UA_NodeId *sessionId, void *sessionHandle,
+                                            const UA_NodeId *methodId, const UA_NodeId *objectId);
 
     /* Allow adding a node */
     UA_Boolean (*allowAddNode)(const UA_NodeId *sessionId, void *sessionHandle, const UA_AddNodesItem *item);
@@ -425,7 +429,7 @@ UA_Server_readExecutable(UA_Server *server, const UA_NodeId nodeId,
  * - ContainsNoLoop
  *
  * The following attributes cannot be written from the server, as they are
- * specific to the different users:
+ * specific to the different users and set by the access control callback:
  *
  * - UserWriteMask
  * - UserAccessLevel

+ 6 - 0
plugins/ua_accesscontrol_default.c

@@ -87,6 +87,12 @@ getUserExecutable_default(const UA_NodeId *sessionId, void *sessionHandle, const
     return true;
 }
 
+UA_Boolean
+getUserExecutableOnObject_default(const UA_NodeId *sessionId, void *sessionHandle,
+                                  const UA_NodeId *methodId, const UA_NodeId *objectId) {
+    return true;
+}
+
 UA_Boolean
 allowAddNode_default(const UA_NodeId *sessionId, void *sessionHandle, const UA_AddNodesItem *item) {
     return true;

+ 4 - 0
plugins/ua_accesscontrol_default.h

@@ -30,6 +30,10 @@ getUserAccessLevel_default(const UA_NodeId *sessionId, void *sessionHandle, cons
 UA_EXPORT UA_Boolean
 getUserExecutable_default(const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *nodeId);
 
+UA_EXPORT UA_Boolean
+getUserExecutableOnObject_default(const UA_NodeId *sessionId, void *sessionHandle,
+                                  const UA_NodeId *methodId, const UA_NodeId *objectId);
+
 UA_EXPORT UA_Boolean
 allowAddNode_default(const UA_NodeId *sessionId, void *sessionHandle, const UA_AddNodesItem *item);
 

+ 3 - 1
plugins/ua_config_standard.c

@@ -31,7 +31,8 @@ const UA_EXPORT UA_ConnectionConfig UA_ConnectionConfig_standard = {
 #define UA_STRING_STATIC(s) {sizeof(s)-1, (UA_Byte*)s}
 #define UA_STRING_STATIC_NULL {0, NULL}
 
-/* Access Control */
+/* Access Control. The following definitions are defined as "extern" in
+   ua_accesscontrol_default.h */
 #define ENABLEANONYMOUSLOGIN true
 #define ENABLEUSERNAMEPASSWORDLOGIN true
 const UA_Boolean enableAnonymousLogin = ENABLEANONYMOUSLOGIN;
@@ -78,6 +79,7 @@ const UA_EXPORT UA_ServerConfig UA_ServerConfig_standard = {
         .getUserRightsMask = getUserRightsMask_default,
         .getUserAccessLevel = getUserAccessLevel_default,
         .getUserExecutable = getUserExecutable_default,
+        .getUserExecutableOnObject = getUserExecutableOnObject_default,
         .allowAddNode = allowAddNode_default,
         .allowAddReference = allowAddReference_default,
         .allowDeleteNode = allowDeleteNode_default,

+ 0 - 3
src/server/ua_nodes.c

@@ -80,7 +80,6 @@ static UA_StatusCode
 UA_VariableNode_copy(const UA_VariableNode *src, UA_VariableNode *dst) {
     UA_StatusCode retval = UA_CommonVariableNode_copy(src, dst);
     dst->accessLevel = src->accessLevel;
-    dst->userAccessLevel = src->userAccessLevel;
     dst->minimumSamplingInterval = src->minimumSamplingInterval;
     dst->historizing = src->historizing;
     return retval;
@@ -98,7 +97,6 @@ UA_VariableTypeNode_copy(const UA_VariableTypeNode *src,
 static UA_StatusCode
 UA_MethodNode_copy(const UA_MethodNode *src, UA_MethodNode *dst) {
     dst->executable = src->executable;
-    dst->userExecutable = src->userExecutable;
     dst->methodHandle  = src->methodHandle;
     dst->attachedMethod = src->attachedMethod;
     return UA_STATUSCODE_GOOD;
@@ -145,7 +143,6 @@ UA_StatusCode UA_Node_copyAnyNodeClass(const UA_Node *src, UA_Node *dst) {
     retval |= UA_LocalizedText_copy(&src->displayName, &dst->displayName);
     retval |= UA_LocalizedText_copy(&src->description, &dst->description);
     dst->writeMask = src->writeMask;
-    dst->userWriteMask = src->userWriteMask;
     if(retval != UA_STATUSCODE_GOOD) {
         UA_Node_deleteMembersAnyNodeClass(dst);
         return retval;

+ 0 - 3
src/server/ua_nodes.h

@@ -52,7 +52,6 @@ extern "C" {
     UA_LocalizedText displayName;               \
     UA_LocalizedText description;               \
     UA_UInt32 writeMask;                        \
-    UA_UInt32 userWriteMask;                    \
     size_t referencesSize;                      \
     UA_ReferenceNode *references;
 
@@ -153,7 +152,6 @@ typedef struct {
     UA_NODE_BASEATTRIBUTES
     UA_NODE_VARIABLEATTRIBUTES
     UA_Byte accessLevel;
-    UA_Byte userAccessLevel;
     UA_Double minimumSamplingInterval;
     UA_Boolean historizing; /* currently unsupported */
 } UA_VariableNode;
@@ -197,7 +195,6 @@ typedef struct {
 typedef struct {
     UA_NODE_BASEATTRIBUTES
     UA_Boolean executable;
-    UA_Boolean userExecutable;
 
     /* Members specific to open62541 */
     void *methodHandle;

+ 24 - 25
src/server/ua_services_attribute.c

@@ -660,9 +660,13 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
     case UA_ATTRIBUTEID_WRITEMASK:
         forceVariantSetScalar(&v->value, &node->writeMask, &UA_TYPES[UA_TYPES_UINT32]);
         break;
-    case UA_ATTRIBUTEID_USERWRITEMASK:
-        forceVariantSetScalar(&v->value, &node->userWriteMask, &UA_TYPES[UA_TYPES_UINT32]);
-        break;
+    case UA_ATTRIBUTEID_USERWRITEMASK: {
+        UA_UInt32 userWriteMask = node->writeMask;
+        userWriteMask &=
+            server->config.accessControl.getUserRightsMask(&session->sessionId,
+                                                           session->sessionHandle, &id->nodeId);
+        forceVariantSetScalar(&v->value, &userWriteMask, &UA_TYPES[UA_TYPES_UINT32]);
+        break; }
     case UA_ATTRIBUTEID_ISABSTRACT:
         retval = readIsAbstractAttribute(node, &v->value);
         break;
@@ -710,11 +714,14 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->accessLevel,
                               &UA_TYPES[UA_TYPES_BYTE]);
         break;
-    case UA_ATTRIBUTEID_USERACCESSLEVEL:
+    case UA_ATTRIBUTEID_USERACCESSLEVEL: {
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
-        forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->userAccessLevel,
-                              &UA_TYPES[UA_TYPES_BYTE]);
-        break;
+        UA_Byte userAccessLevel = ((const UA_VariableNode*)node)->accessLevel;
+        userAccessLevel &=
+            server->config.accessControl.getUserAccessLevel(&session->sessionId,
+                                                            session->sessionHandle, &id->nodeId);
+        forceVariantSetScalar(&v->value, &userAccessLevel, &UA_TYPES[UA_TYPES_BYTE]);
+        break; }
     case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE);
         forceVariantSetScalar(&v->value, &((const UA_VariableNode*)node)->minimumSamplingInterval,
@@ -730,11 +737,14 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->executable,
                               &UA_TYPES[UA_TYPES_BOOLEAN]);
         break;
-    case UA_ATTRIBUTEID_USEREXECUTABLE:
+    case UA_ATTRIBUTEID_USEREXECUTABLE: {
         CHECK_NODECLASS(UA_NODECLASS_METHOD);
-        forceVariantSetScalar(&v->value, &((const UA_MethodNode*)node)->userExecutable,
-                              &UA_TYPES[UA_TYPES_BOOLEAN]);
-        break;
+        UA_Boolean userExecutable = ((const UA_MethodNode*)node)->executable;
+        userExecutable &=
+            server->config.accessControl.getUserExecutable(&session->sessionId,
+                                                           session->sessionHandle, &id->nodeId);
+        forceVariantSetScalar(&v->value, &userExecutable, &UA_TYPES[UA_TYPES_BOOLEAN]);
+        break; }
     default:
         retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
     }
@@ -964,6 +974,9 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
     switch(wvalue->attributeId) {
     case UA_ATTRIBUTEID_NODEID:
     case UA_ATTRIBUTEID_NODECLASS:
+    case UA_ATTRIBUTEID_USERWRITEMASK:
+    case UA_ATTRIBUTEID_USERACCESSLEVEL:
+    case UA_ATTRIBUTEID_USEREXECUTABLE:
         retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
         break;
     case UA_ATTRIBUTEID_BROWSENAME:
@@ -985,10 +998,6 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
         CHECK_DATATYPE_SCALAR(UINT32);
         node->writeMask = *(const UA_UInt32*)value;
         break;
-    case UA_ATTRIBUTEID_USERWRITEMASK:
-        CHECK_DATATYPE_SCALAR(UINT32);
-        node->userWriteMask = *(const UA_UInt32*)value;
-        break;
     case UA_ATTRIBUTEID_ISABSTRACT:
         CHECK_DATATYPE_SCALAR(BOOLEAN);
         retval = writeIsAbstractAttribute(node, *(const UA_Boolean*)value);
@@ -1041,11 +1050,6 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
         CHECK_DATATYPE_SCALAR(BYTE);
         ((UA_VariableNode*)node)->accessLevel = *(const UA_Byte*)value;
         break;
-    case UA_ATTRIBUTEID_USERACCESSLEVEL:
-        CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
-        CHECK_DATATYPE_SCALAR(BYTE);
-        ((UA_VariableNode*)node)->userAccessLevel = *(const UA_Byte*)value;
-        break;
     case UA_ATTRIBUTEID_MINIMUMSAMPLINGINTERVAL:
         CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE);
         CHECK_DATATYPE_SCALAR(DOUBLE);
@@ -1061,11 +1065,6 @@ CopyAttributeIntoNode(UA_Server *server, UA_Session *session,
         CHECK_DATATYPE_SCALAR(BOOLEAN);
         ((UA_MethodNode*)node)->executable = *(const UA_Boolean*)value;
         break;
-    case UA_ATTRIBUTEID_USEREXECUTABLE:
-        CHECK_NODECLASS_WRITE(UA_NODECLASS_METHOD);
-        CHECK_DATATYPE_SCALAR(BOOLEAN);
-        ((UA_MethodNode*)node)->userExecutable = *(const UA_Boolean*)value;
-        break;
     default:
         retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
         break;

+ 13 - 2
src/server/ua_services_call.c

@@ -63,8 +63,8 @@ Service_Call_single(UA_Server *server, UA_Session *session,
         result->statusCode = UA_STATUSCODE_BADNODECLASSINVALID;
         return;
     }
-    if(!methodCalled->executable || !methodCalled->userExecutable || !methodCalled->attachedMethod) {
-        result->statusCode = UA_STATUSCODE_BADNOTWRITABLE; // There is no NOTEXECUTABLE?
+    if(!methodCalled->attachedMethod) {
+        result->statusCode = UA_STATUSCODE_BADINTERNALERROR;
         return;
     }
 
@@ -80,6 +80,17 @@ Service_Call_single(UA_Server *server, UA_Session *session,
         return;
     }
 
+    /* Verify access rights */
+    UA_Boolean executable = methodCalled->executable;
+    if(session != &adminSession)
+        executable = executable &&
+            server->config.accessControl.getUserExecutableOnObject(&session->sessionId, session->sessionHandle,
+                                                                   &request->objectId, &request->methodId);
+    if(!executable) {
+        result->statusCode = UA_STATUSCODE_BADNOTWRITABLE; // There is no NOTEXECUTABLE?
+        return;
+    }
+
     /* Verify method/object relations. Object must have a hasComponent or a
      * subtype of hasComponent reference to the method node. Therefore, check
      * every reference between the parent object and the method node if there is

+ 0 - 6
src/server/ua_services_nodemanagement.c

@@ -41,14 +41,12 @@ copyExistingVariable(UA_Server *server, UA_Session *session, const UA_NodeId *va
     attr.displayName = node->displayName;
     attr.description = node->description;
     attr.writeMask = node->writeMask;
-    attr.userWriteMask = node->userWriteMask;
     attr.value = value.value;
     attr.dataType = node->dataType;
     attr.valueRank = node->valueRank;
     attr.arrayDimensionsSize = node->arrayDimensionsSize;
     attr.arrayDimensions = node->arrayDimensions;
     attr.accessLevel = node->accessLevel;
-    attr.userAccessLevel = node->userAccessLevel;
     attr.minimumSamplingInterval = node->minimumSamplingInterval;
     attr.historizing = node->historizing;
 
@@ -109,7 +107,6 @@ copyExistingObject(UA_Server *server, UA_Session *session, const UA_NodeId *obje
     attr.displayName = node->displayName;
     attr.description = node->description;
     attr.writeMask = node->writeMask;
-    attr.userWriteMask = node->userWriteMask;
     attr.eventNotifier = node->eventNotifier;
 
     UA_AddNodesItem item;
@@ -504,7 +501,6 @@ copyStandardAttributes(UA_Node *node, const UA_AddNodesItem *item,
     retval |= UA_LocalizedText_copy(&attr->displayName, &node->displayName);
     retval |= UA_LocalizedText_copy(&attr->description, &node->description);
     node->writeMask = attr->writeMask;
-    node->userWriteMask = attr->userWriteMask;
     return retval;
 }
 
@@ -575,7 +571,6 @@ variableNodeFromAttributes(UA_Server *server, UA_VariableNode *vnode,
                            const UA_AddNodesItem *item,
                            const UA_VariableAttributes *attr) {
     vnode->accessLevel = attr->accessLevel;
-    vnode->userAccessLevel = attr->userAccessLevel;
     vnode->historizing = attr->historizing;
     vnode->minimumSamplingInterval = attr->minimumSamplingInterval;
     return copyCommonVariableAttributes(server, vnode, item, attr);
@@ -869,7 +864,6 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
     item.browseName = browseName;
     copyStandardAttributes((UA_Node*)node, &item, (const UA_NodeAttributes*)&attr);
     node->executable = attr.executable;
-    node->userExecutable = attr.userExecutable;
     node->attachedMethod = method;
     node->methodHandle = handle;
 

+ 1 - 46
tests/check_services_attributes.c

@@ -521,7 +521,7 @@ START_TEST(ReadSingleAttributeUserAccessLevelWithoutTimestamp) {
         (const UA_VariableNode*)UA_NodeStore_get(server->nodestore, &rReq.nodesToRead[0].nodeId);
     ck_assert_int_eq(0, resp.value.arrayLength);
     ck_assert_ptr_eq(&UA_TYPES[UA_TYPES_BYTE], resp.value.type);
-    ck_assert_int_eq(*(UA_Byte*)resp.value.data, compNode->userAccessLevel);
+    ck_assert_int_eq(*(UA_Byte*)resp.value.data, compNode->accessLevel & 0xFF); // 0xFF is the default userAccessLevel
     UA_Server_delete(server);
     UA_DataValue_deleteMembers(&resp);
     UA_ReadRequest_deleteMembers(&rReq);
@@ -754,20 +754,6 @@ START_TEST(WriteSingleAttributeWriteMask) {
     UA_Server_delete(server);
 } END_TEST
 
-START_TEST(WriteSingleAttributeUserWriteMask) {
-    UA_Server *server = makeTestSequence();
-    UA_WriteValue wValue;
-    UA_WriteValue_init(&wValue);
-    UA_Int32 testValue = 0;
-    UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_UINT32]);
-    wValue.nodeId = UA_NODEID_STRING(1, "the.answer");
-    wValue.attributeId = UA_ATTRIBUTEID_USERWRITEMASK;
-    wValue.value.hasValue = true;
-    UA_StatusCode retval = UA_Server_write(server, &wValue);
-    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    UA_Server_delete(server);
-} END_TEST
-
 START_TEST(WriteSingleAttributeIsAbstract) {
     UA_Server *server = makeTestSequence();
     UA_WriteValue wValue;
@@ -953,20 +939,6 @@ START_TEST(WriteSingleAttributeAccessLevel) {
     UA_Server_delete(server);
 } END_TEST
 
-START_TEST(WriteSingleAttributeUserAccessLevel) {
-    UA_Server *server = makeTestSequence();
-    UA_WriteValue wValue;
-    UA_WriteValue_init(&wValue);
-    UA_Byte testValue = 0;
-    UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_BYTE]);
-    wValue.nodeId = UA_NODEID_STRING(1, "the.answer");
-    wValue.attributeId = UA_ATTRIBUTEID_USERACCESSLEVEL;
-    wValue.value.hasValue = true;
-    UA_StatusCode retval = UA_Server_write(server, &wValue);
-    ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    UA_Server_delete(server);
-} END_TEST
-
 START_TEST(WriteSingleAttributeMinimumSamplingInterval) {
     UA_Server *server = makeTestSequence();
     UA_WriteValue wValue;
@@ -1009,20 +981,6 @@ START_TEST(WriteSingleAttributeExecutable) {
     UA_Server_delete(server);
 } END_TEST
 
-START_TEST(WriteSingleAttributeUserExecutable) {
-    UA_Server *server = makeTestSequence();
-    UA_WriteValue wValue;
-    UA_WriteValue_init(&wValue);
-    UA_Boolean testValue = true;
-    UA_Variant_setScalar(&wValue.value.value, &testValue, &UA_TYPES[UA_TYPES_BOOLEAN]);
-    wValue.nodeId = UA_NODEID_STRING(1, "the.answer");
-    wValue.attributeId = UA_ATTRIBUTEID_USEREXECUTABLE;
-    wValue.value.hasValue = true;
-    UA_StatusCode retval = UA_Server_write(server, &wValue);
-    ck_assert_int_eq(retval, UA_STATUSCODE_BADNODECLASSINVALID);
-    UA_Server_delete(server);
-} END_TEST
-
 START_TEST(WriteSingleDataSourceAttributeValue) {
     UA_Server *server = makeTestSequence();
     UA_WriteValue wValue;
@@ -1077,7 +1035,6 @@ static Suite * testSuite_services_attributes(void) {
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeDisplayName);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeDescription);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeWriteMask);
-    tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeUserWriteMask);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeIsAbstract);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeSymmetric);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeInverseName);
@@ -1090,11 +1047,9 @@ static Suite * testSuite_services_attributes(void) {
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeValueRank);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeArrayDimensions);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeAccessLevel);
-    tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeUserAccessLevel);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeMinimumSamplingInterval);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeHistorizing);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeExecutable);
-    tcase_add_test(tc_writeSingleAttributes, WriteSingleAttributeUserExecutable);
     tcase_add_test(tc_writeSingleAttributes, WriteSingleDataSourceAttributeValue);
 
     suite_add_tcase(s, tc_writeSingleAttributes);