Files
open62541/tests/server/check_server_rbac_interlocked.c

1078 lines
45 KiB
C

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner)
*/
/* RBAC interlocked-permission tests
*
* Compact regression suite for OPC UA Part 3 v1.05, §8.55 PermissionType
* semantics. The tests verify that permission bits are independent and that
* interlocked operations keep their conjunctive checks (notably Call on
* Object+Method and ReceiveEvents on EventType+SourceNode). Coverage also
* includes history and node-management permission mappings via the default
* AccessControl callbacks.
*
* NOTE on ReceiveEvents (bit 11): the bit is now enforced in
* src/server/ua_subscription_event.c::createEvent via the internal
* helper getEffectivePermissions(). The unit-level tests below
* validate the data path the enforcement reads from (independent
* storage on EventType vs. SourceNode); a full client-subscription
* end-to-end test belongs in check_server_rbac_client.c.
*/
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/nodeids.h>
#include "test_helpers.h"
#include "ua_server_rbac.h"
#include <check.h>
#include <stdio.h>
#include <stdlib.h>
static UA_Server *server = NULL;
/* The well-known admin session id installed by UA_Server_newForUnitTest() so test
* code can act as that session via the session-attribute API. */
static const UA_NodeId adminSessionId =
{0, UA_NODEIDTYPE_GUID, {.guid = {1, 0, 0, {0,0,0,0,0,0,0,0}}}};
static void setup(void) {
server = UA_Server_newForUnitTest();
ck_assert(server != NULL);
}
static void teardown(void) {
if(server) {
UA_Server_delete(server);
server = NULL;
}
}
/* --------------------------------------------------------------------- */
/* Helpers */
/* --------------------------------------------------------------------- */
/* Add a custom role with AuthenticatedUser identity mapping. */
static UA_NodeId
addRole(const char *name) {
UA_Role role;
UA_Role_init(&role);
role.roleName = UA_QUALIFIEDNAME(0, (char*)(uintptr_t)name);
UA_NodeId roleId;
UA_StatusCode r = UA_Server_addRole(server, &role, &roleId);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
UA_Role upd;
r = UA_Server_getRoleById(server, roleId, &upd);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
upd.identityMappingRules = (UA_IdentityMappingRuleType*)
UA_calloc(1, sizeof(UA_IdentityMappingRuleType));
ck_assert_ptr_nonnull(upd.identityMappingRules);
upd.identityMappingRules[0].criteriaType =
UA_IDENTITYCRITERIATYPE_AUTHENTICATEDUSER;
upd.identityMappingRulesSize = 1;
r = UA_Server_updateRole(server, &upd);
UA_Role_clear(&upd);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
return roleId;
}
/* Make `roleId` the only role currently active on the admin session. */
static void
assignRoleToAdminSession(const UA_NodeId roleId) {
UA_NodeId tmp = roleId;
UA_Variant v;
UA_Variant_setArray(&v, &tmp, 1, &UA_TYPES[UA_TYPES_NODEID]);
UA_StatusCode r =
UA_Server_setSessionAttribute(server, &adminSessionId,
UA_QUALIFIEDNAME(0, "roles"), &v);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
}
static void
clearAdminSessionRoles(void) {
(void)UA_Server_deleteSessionAttribute(server, &adminSessionId,
UA_QUALIFIEDNAME(0, "roles"));
}
/* Add a Variable child of ObjectsFolder. */
static UA_NodeId
addVariable(const char *browseName) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", (char*)(uintptr_t)browseName);
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
attr.userAccessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
attr.writeMask = 0xFFFFFFFF;
UA_Int32 v = 0;
UA_Variant_setScalar(&attr.value, &v, &UA_TYPES[UA_TYPES_INT32]);
UA_NodeId out;
UA_StatusCode r = UA_Server_addVariableNode(server,
UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, (char*)(uintptr_t)browseName),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, &out);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
return out;
}
/* Add an Object child of ObjectsFolder (as a Method container). */
static UA_NodeId
addObject(const char *browseName) {
UA_ObjectAttributes attr = UA_ObjectAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", (char*)(uintptr_t)browseName);
UA_NodeId out;
UA_StatusCode r = UA_Server_addObjectNode(server,
UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, (char*)(uintptr_t)browseName),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
attr, NULL, &out);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
return out;
}
#ifdef UA_ENABLE_METHODCALLS
static UA_StatusCode
dummyMethodCb(UA_Server *s, const UA_NodeId *sId, void *sCtx,
const UA_NodeId *mId, void *mCtx,
const UA_NodeId *oId, void *oCtx,
size_t inSz, const UA_Variant *in,
size_t outSz, UA_Variant *out) {
(void)s; (void)sId; (void)sCtx; (void)mId; (void)mCtx;
(void)oId; (void)oCtx; (void)inSz; (void)in; (void)outSz; (void)out;
return UA_STATUSCODE_GOOD;
}
/* Add a Method on `parentObjectId`. */
static UA_NodeId
addMethod(UA_NodeId parentObjectId, const char *browseName) {
UA_MethodAttributes attr = UA_MethodAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", (char*)(uintptr_t)browseName);
attr.executable = true;
attr.userExecutable = true;
UA_NodeId out;
UA_StatusCode r = UA_Server_addMethodNode(server, UA_NODEID_NULL,
parentObjectId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, (char*)(uintptr_t)browseName),
attr, dummyMethodCb,
0, NULL, 0, NULL, NULL, &out);
ck_assert_uint_eq(r, UA_STATUSCODE_GOOD);
return out;
}
#endif
/* --------------------------------------------------------------------- */
/* 1. Call interlock — Part 3 §8.55 bit 12 */
/* "...the Method if this bit is set on the Object or ObjectType Node */
/* passed in the Call request and the Method Instance associated */
/* with that Object or ObjectType." */
/* --------------------------------------------------------------------- */
#ifdef UA_ENABLE_METHODCALLS
START_TEST(Call_interlock_objectOnly) {
UA_NodeId roleId = addRole("CallRole_objOnly");
assignRoleToAdminSession(roleId);
UA_NodeId obj = addObject("CallObj_objOnly");
UA_NodeId mth = addMethod(obj, "CallMth_objOnly");
/* CALL granted on object, but only BROWSE on method (RBAC engaged
* for the method node, but no CALL bit). */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, obj, roleId,
UA_PERMISSIONTYPE_CALL, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, mth, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean exec = ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &mth, NULL, &obj, NULL);
ck_assert_msg(exec == false,
"Spec §8.55: CALL on object alone must NOT permit method invocation");
clearAdminSessionRoles();
UA_Server_deleteNode(server, mth, true);
UA_Server_deleteNode(server, obj, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "CallRole_objOnly"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(Call_interlock_methodOnly) {
UA_NodeId roleId = addRole("CallRole_mthOnly");
assignRoleToAdminSession(roleId);
UA_NodeId obj = addObject("CallObj_mthOnly");
UA_NodeId mth = addMethod(obj, "CallMth_mthOnly");
/* CALL granted on method only, only BROWSE on object. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, obj, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, mth, roleId,
UA_PERMISSIONTYPE_CALL, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean exec = ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &mth, NULL, &obj, NULL);
ck_assert_msg(exec == false,
"Spec §8.55: CALL on method alone must NOT permit method invocation");
clearAdminSessionRoles();
UA_Server_deleteNode(server, mth, true);
UA_Server_deleteNode(server, obj, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "CallRole_mthOnly"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(Call_interlock_both) {
UA_NodeId roleId = addRole("CallRole_both");
assignRoleToAdminSession(roleId);
UA_NodeId obj = addObject("CallObj_both");
UA_NodeId mth = addMethod(obj, "CallMth_both");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, obj, roleId,
UA_PERMISSIONTYPE_CALL, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, mth, roleId,
UA_PERMISSIONTYPE_CALL, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean exec = ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &mth, NULL, &obj, NULL);
ck_assert_msg(exec == true,
"Spec §8.55: CALL on both object and method must permit invocation");
clearAdminSessionRoles();
UA_Server_deleteNode(server, mth, true);
UA_Server_deleteNode(server, obj, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "CallRole_both"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(Call_interlock_neither) {
UA_NodeId roleId = addRole("CallRole_neither");
assignRoleToAdminSession(roleId);
UA_NodeId obj = addObject("CallObj_neither");
UA_NodeId mth = addMethod(obj, "CallMth_neither");
/* RBAC engaged on both nodes (with BROWSE only) so no node falls
* back to the 0xFFFFFFFF "no entries" permissive default. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, obj, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, mth, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean exec = ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &mth, NULL, &obj, NULL);
ck_assert_msg(exec == false,
"Spec §8.55: no CALL on either node => method invocation denied");
clearAdminSessionRoles();
UA_Server_deleteNode(server, mth, true);
UA_Server_deleteNode(server, obj, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "CallRole_neither"));
UA_NodeId_clear(&roleId);
}
END_TEST
#endif /* UA_ENABLE_METHODCALLS */
/* --------------------------------------------------------------------- */
/* 2. Read / Write / Browse / ReadRolePermissions are independent bits. */
/* Part 3 §8.55 bits 0/1/5/6. */
/* --------------------------------------------------------------------- */
START_TEST(Browse_does_not_imply_Read) {
UA_NodeId roleId = addRole("BrowseOnlyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarBrowseOnly");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_READ) == 0,
"Spec §8.55 bit 5: BROWSE alone must not grant CurrentRead "
"(got UserAccessLevel=0x%02x)", ual);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_WRITE) == 0,
"BROWSE alone must not grant CurrentWrite");
UA_Boolean canBrowse = ac->allowBrowseNode(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg(canBrowse == true,
"BROWSE bit must allow allowBrowseNode");
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "BrowseOnlyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(Read_does_not_imply_Browse) {
UA_NodeId roleId = addRole("ReadOnlyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarReadOnly");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_READ, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_READ) != 0,
"Spec §8.55 bit 5: READ must drive CurrentRead "
"(got UserAccessLevel=0x%02x)", ual);
UA_Boolean canBrowse = ac->allowBrowseNode(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg(canBrowse == false,
"Spec §8.55 bit 0: READ alone must not grant Browse "
"(got allowBrowseNode=%d)", (int)canBrowse);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "ReadOnlyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(ReadRolePermissions_distinct_from_Read) {
UA_NodeId roleId = addRole("ReadValueOnlyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarReadValueOnly");
/* Grant READ + BROWSE but NOT ReadRolePermissions. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_READ | UA_PERMISSIONTYPE_BROWSE,
false, false), UA_STATUSCODE_GOOD);
UA_PermissionType eff = 0;
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &v, &eff), UA_STATUSCODE_GOOD);
ck_assert_msg((eff & UA_PERMISSIONTYPE_READROLEPERMISSIONS) == 0,
"Spec §8.55 bit 1: READ must not imply ReadRolePermissions");
/* The plugin must not advertise WriteRolePermissions in WriteMask. */
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_UInt32 uwm = ac->getUserRightsMask(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((uwm & UA_WRITEMASK_ROLEPERMISSIONS) == 0,
"READ must not imply WriteRolePermissions (UserWriteMask=0x%08x)",
uwm);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "ReadValueOnlyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(Write_does_not_imply_WriteAttribute) {
UA_NodeId roleId = addRole("WriteValueOnlyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarWriteValueOnly");
/* Grant WRITE (Value) only, not WriteAttribute. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_WRITE, false, false), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_WRITE) != 0,
"Spec §8.55 bit 6: WRITE must drive CurrentWrite "
"(got UserAccessLevel=0x%02x)", ual);
/* WriteMask must be 0 because WriteAttribute bit is not granted. */
UA_UInt32 uwm = ac->getUserRightsMask(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg(uwm == 0,
"Spec §8.55 bit 2: WRITE (Value) must not imply WriteAttribute "
"(UserWriteMask=0x%08x)", uwm);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "WriteValueOnlyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(WriteAttribute_does_not_imply_WriteRolePermissions) {
UA_NodeId roleId = addRole("WriteAttrOnlyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarWriteAttrOnly");
/* Only WriteAttribute. WriteMask should expose every writable
* attribute except RolePermissions (bit 23) and Historizing
* (bit 9), per Part 3 §8.55. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_WRITEATTRIBUTE, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_UInt32 uwm = ac->getUserRightsMask(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((uwm & UA_WRITEMASK_ROLEPERMISSIONS) == 0,
"Spec §8.55 bit 3: WriteAttribute must NOT imply "
"WriteRolePermissions (UserWriteMask=0x%08x)", uwm);
ck_assert_msg((uwm & UA_WRITEMASK_HISTORIZING) == 0,
"Spec §8.55 bit 4: WriteAttribute must NOT imply "
"WriteHistorizing (UserWriteMask=0x%08x)", uwm);
/* But it MUST grant some other writable bit (e.g. DisplayName). */
ck_assert_msg((uwm & UA_WRITEMASK_DISPLAYNAME) != 0,
"WriteAttribute must grant DisplayName writability "
"(UserWriteMask=0x%08x)", uwm);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "WriteAttrOnlyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(WriteRolePermissions_distinct_bit) {
UA_NodeId roleId = addRole("WriteRPRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarWriteRP");
/* Only WriteRolePermissions, nothing else. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_WRITEROLEPERMISSIONS, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_UInt32 uwm = ac->getUserRightsMask(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((uwm & UA_WRITEMASK_ROLEPERMISSIONS) != 0,
"Spec §8.55 bit 3: WriteRolePermissions must set "
"WriteMask.RolePermissions (UserWriteMask=0x%08x)", uwm);
ck_assert_msg((uwm & UA_WRITEMASK_DISPLAYNAME) == 0,
"WriteRolePermissions alone must NOT grant general attribute "
"writability (UserWriteMask=0x%08x)", uwm);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "WriteRPRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
/* --------------------------------------------------------------------- */
/* 3. Multiple roles ORed together fulfil interlocked requirements. */
/* --------------------------------------------------------------------- */
START_TEST(MultiRole_OR_satisfies_interlock) {
UA_NodeId roleA = addRole("PartialA");
UA_NodeId roleB = addRole("PartialB");
/* Activate BOTH roles on the session. */
UA_NodeId roles[2] = {roleA, roleB};
UA_Variant v;
UA_Variant_setArray(&v, roles, 2, &UA_TYPES[UA_TYPES_NODEID]);
ck_assert_uint_eq(UA_Server_setSessionAttribute(server, &adminSessionId,
UA_QUALIFIEDNAME(0, "roles"), &v), UA_STATUSCODE_GOOD);
UA_NodeId var = addVariable("VarPartial");
/* Role A gives BROWSE, role B gives READ. Effective should be both. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, var, roleA,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, var, roleB,
UA_PERMISSIONTYPE_READ, false, false), UA_STATUSCODE_GOOD);
UA_PermissionType eff = 0;
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &var, &eff), UA_STATUSCODE_GOOD);
ck_assert_msg((eff & UA_PERMISSIONTYPE_BROWSE) &&
(eff & UA_PERMISSIONTYPE_READ),
"OR over the session's roles must combine BROWSE+READ "
"(effective=0x%08x)", eff);
clearAdminSessionRoles();
UA_Server_deleteNode(server, var, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "PartialA"));
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "PartialB"));
UA_NodeId_clear(&roleA);
UA_NodeId_clear(&roleB);
}
END_TEST
/* --------------------------------------------------------------------- */
/* 4. ReceiveEvents (Part 3 §8.55 bit 11) — interlocked on EventType + */
/* SourceNode. As of this commit the bit is enforced by */
/* src/server/ua_subscription_event.c::createEvent which calls the */
/* internal getEffectivePermissions() helper for both the */
/* EventType and the SourceNode of every event being delivered to a */
/* non-admin session, skipping the MonitoredItem when either node is */
/* RBAC-restricted and lacks the bit. */
/* */
/* A full end-to-end subscription test would require spinning up a */
/* networked client (see check_server_rbac_client.c). The unit-level */
/* coverage below validates the data path the enforcement reads from: */
/* each node must independently expose the RECEIVEEVENTS bit, and a */
/* role with the bit on only one of the two nodes must NOT yield the */
/* bit on the other. */
/* --------------------------------------------------------------------- */
START_TEST(ReceiveEvents_storedSeparatelyOnEventTypeAndSource) {
UA_NodeId roleId = addRole("EventRole");
assignRoleToAdminSession(roleId);
UA_NodeId source = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
UA_NodeId etype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
/* Grant RECEIVEEVENTS only on the EventType, not on the source. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, etype, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false, false),
UA_STATUSCODE_GOOD);
UA_PermissionType effEt = 0, effSrc = 0;
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &etype, &effEt), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &source, &effSrc), UA_STATUSCODE_GOOD);
ck_assert_msg((effEt & UA_PERMISSIONTYPE_RECEIVEEVENTS) != 0,
"Spec §8.55 bit 11: bit must be readable on the EventType "
"(eff=0x%08x)", effEt);
/* The source has no entries -> UA_PERMISSIONTYPE_ALL (permissive). The
* createEvent enforcement treats UA_PERMISSIONTYPE_ALL as "no RBAC entries
* configured" and so allows delivery; the bit is NOT inferred from
* the EventType. */
ck_assert_msg(effSrc == UA_PERMISSIONTYPE_ALL ||
!(effSrc & UA_PERMISSIONTYPE_RECEIVEEVENTS) ||
(effSrc & UA_PERMISSIONTYPE_RECEIVEEVENTS),
"Source effective perms must be independently retrievable "
"(eff=0x%08x)", effSrc);
/* Now restrict the source to BROWSE only — no RECEIVEEVENTS. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, source, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &source, &effSrc), UA_STATUSCODE_GOOD);
ck_assert_msg(effSrc != UA_PERMISSIONTYPE_ALL &&
!(effSrc & UA_PERMISSIONTYPE_RECEIVEEVENTS),
"Source must NOT inherit RECEIVEEVENTS from the EventType "
"(effSrc=0x%08x)", effSrc);
/* Cleanup the bits we set on well-known nodes. */
(void)UA_Server_removeRolePermissions(server, etype, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false);
(void)UA_Server_removeRolePermissions(server, source, roleId,
UA_PERMISSIONTYPE_BROWSE, false);
clearAdminSessionRoles();
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "EventRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(ReceiveEvents_grantedOnBothPropagates) {
UA_NodeId roleId = addRole("EventRoleBoth");
assignRoleToAdminSession(roleId);
UA_NodeId source = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
UA_NodeId etype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, etype, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false, false),
UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_addRolePermissions(server, source, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false, false),
UA_STATUSCODE_GOOD);
UA_PermissionType effEt = 0, effSrc = 0;
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &etype, &effEt), UA_STATUSCODE_GOOD);
ck_assert_uint_eq(UA_Server_getEffectivePermissions(server,
&adminSessionId, &source, &effSrc), UA_STATUSCODE_GOOD);
ck_assert_msg((effEt & UA_PERMISSIONTYPE_RECEIVEEVENTS) != 0,
"EventType missing RECEIVEEVENTS (eff=0x%08x)", effEt);
ck_assert_msg((effSrc & UA_PERMISSIONTYPE_RECEIVEEVENTS) != 0,
"Source missing RECEIVEEVENTS (eff=0x%08x)", effSrc);
(void)UA_Server_removeRolePermissions(server, etype, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false);
(void)UA_Server_removeRolePermissions(server, source, roleId,
UA_PERMISSIONTYPE_RECEIVEEVENTS, false);
clearAdminSessionRoles();
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "EventRoleBoth"));
UA_NodeId_clear(&roleId);
}
END_TEST
#if defined(UA_ENABLE_DRIVER_GDS_RECEIVER) && defined(UA_ENABLE_METHODCALLS) && \
defined(UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST_OPEN)
/* Applies the SecurityAdmin GDS RolePermissions. In a running server this is
* invoked from the GDS driver start; the unit test applies it directly. */
UA_StatusCode initGDSRolePermissions(UA_Server *server);
START_TEST(GDS_SecurityAdmin_call_and_visibility) {
UA_Server_getConfig(server)->allPermissionsForAnonymous = false;
UA_NodeId securityAdmin =
UA_NODEID_NUMERIC(0, UA_NS0ID_WELLKNOWNROLE_SECURITYADMIN);
UA_NodeId anonymous =
UA_NODEID_NUMERIC(0, UA_NS0ID_WELLKNOWNROLE_ANONYMOUS);
UA_NodeId serverConfig = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION);
UA_NodeId updateCertificate =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_UPDATECERTIFICATE);
UA_NodeId certificateGroups =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS);
UA_NodeId defaultApplicationGroup =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP);
UA_NodeId trustList =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST);
UA_NodeId openTrustList =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST_OPEN);
/* The GDS build requires UA_NAMESPACE_ZERO=FULL, so the ServerConfiguration
* object must exist. Fail loudly instead of silently skipping. */
UA_QualifiedName bn;
ck_assert_msg(UA_Server_readBrowseName(server, serverConfig, &bn) ==
UA_STATUSCODE_GOOD,
"ServerConfiguration (NS0 i=%u) must exist in a GDS-enabled build",
(unsigned)UA_NS0ID_SERVERCONFIGURATION);
UA_QualifiedName_clear(&bn);
/* Apply the GDS SecurityAdmin RolePermissions (driver start does this in a
* running server; this unit test does not start the GDS driver). */
ck_assert_uint_eq(initGDSRolePermissions(server), UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
assignRoleToAdminSession(securityAdmin);
ck_assert_msg(ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &updateCertificate, NULL, &serverConfig, NULL),
"SecurityAdmin must be able to call ServerConfiguration methods");
ck_assert_msg(ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &openTrustList, NULL, &trustList, NULL),
"SecurityAdmin must have CALL on TrustList object and method");
ck_assert_msg(ac->allowBrowseNode(server, ac,
&adminSessionId, NULL, &defaultApplicationGroup, NULL),
"SecurityAdmin must be able to browse CertificateGroups children");
assignRoleToAdminSession(anonymous);
ck_assert_msg(!ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &updateCertificate, NULL, &serverConfig, NULL),
"Anonymous must not call ServerConfiguration security methods");
ck_assert_msg(!ac->getUserExecutableOnObject(server, ac,
&adminSessionId, NULL, &openTrustList, NULL, &trustList, NULL),
"Anonymous must not call TrustList methods");
ck_assert_msg(ac->allowBrowseNode(server, ac,
&adminSessionId, NULL, &certificateGroups, NULL),
"CertificateGroups is an immediate ServerConfiguration child and remains visible");
ck_assert_msg(!ac->allowBrowseNode(server, ac,
&adminSessionId, NULL, &defaultApplicationGroup, NULL),
"CertificateGroups children must be visible only to SecurityAdmin");
clearAdminSessionRoles();
}
END_TEST
#endif
/* --------------------------------------------------------------------- */
/* 5. WriteHistorizing (Part 3 §8.55 bit 4) — exposes the Historizing */
/* bit in the WriteMask, distinct from WriteAttribute (bit 2). */
/* --------------------------------------------------------------------- */
START_TEST(WriteHistorizing_distinct_bit) {
UA_NodeId roleId = addRole("WriteHistRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarWriteHist");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_WRITEHISTORIZING, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_UInt32 uwm = ac->getUserRightsMask(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((uwm & UA_WRITEMASK_HISTORIZING) != 0,
"Spec §8.55 bit 4: WriteHistorizing must set "
"WriteMask.Historizing (UserWriteMask=0x%08x)", uwm);
ck_assert_msg((uwm & UA_WRITEMASK_DISPLAYNAME) == 0,
"WriteHistorizing alone must NOT grant general WriteAttribute "
"(UserWriteMask=0x%08x)", uwm);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "WriteHistRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
/* --------------------------------------------------------------------- */
/* 6. History permission bits (Part 3 §8.55 bits 7..10). */
/* ReadHistory drives UserAccessLevel.HistoryRead; */
/* Insert/Modify/DeleteHistory drive UserAccessLevel.HistoryWrite. */
/* --------------------------------------------------------------------- */
START_TEST(ReadHistory_setsHistoryReadAccessLevel) {
UA_NodeId roleId = addRole("HistReadRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarHistRead");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_READHISTORY, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYREAD) != 0,
"Spec §8.55 bit 7: ReadHistory must set HistoryRead "
"(UserAccessLevel=0x%02x)", ual);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYWRITE) == 0,
"ReadHistory must NOT set HistoryWrite "
"(UserAccessLevel=0x%02x)", ual);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_READ) == 0,
"ReadHistory must NOT imply CurrentRead "
"(UserAccessLevel=0x%02x)", ual);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "HistReadRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(InsertHistory_setsHistoryWriteAccessLevel) {
UA_NodeId roleId = addRole("HistInsertRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarHistInsert");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_INSERTHISTORY, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYWRITE) != 0,
"Spec §8.55 bit 8: InsertHistory must set HistoryWrite "
"(UserAccessLevel=0x%02x)", ual);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYREAD) == 0,
"InsertHistory must NOT set HistoryRead "
"(UserAccessLevel=0x%02x)", ual);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "HistInsertRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(ModifyHistory_setsHistoryWriteAccessLevel) {
UA_NodeId roleId = addRole("HistModifyRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarHistModify");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_MODIFYHISTORY, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYWRITE) != 0,
"Spec §8.55 bit 9: ModifyHistory must set HistoryWrite "
"(UserAccessLevel=0x%02x)", ual);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "HistModifyRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(DeleteHistory_setsHistoryWriteAccessLevel) {
UA_NodeId roleId = addRole("HistDeleteRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarHistDelete");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_DELETEHISTORY, false, false),
UA_STATUSCODE_GOOD);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Byte ual = ac->getUserAccessLevel(server, ac,
&adminSessionId, NULL, &v, NULL);
ck_assert_msg((ual & UA_ACCESSLEVELMASK_HISTORYWRITE) != 0,
"Spec §8.55 bit 10: DeleteHistory must set HistoryWrite "
"(UserAccessLevel=0x%02x)", ual);
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "HistDeleteRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
/* --------------------------------------------------------------------- */
/* 7. Node-management permissions (Part 3 §8.55 bits 13..16). */
/* AddReference / RemoveReference are checked on the source node, */
/* DeleteNode on the node itself, AddNode on the parent node. */
/* --------------------------------------------------------------------- */
START_TEST(AddReference_checked_on_source) {
UA_NodeId roleId = addRole("AddRefRole");
assignRoleToAdminSession(roleId);
UA_NodeId src = addVariable("AddRefSource");
UA_NodeId tgt = addVariable("AddRefTarget");
/* Only BROWSE on source — no AddReference bit. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, src, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_AddReferencesItem item;
UA_AddReferencesItem_init(&item);
item.sourceNodeId = src;
item.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
item.isForward = true;
item.targetNodeId.nodeId = tgt;
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean allowed = ac->allowAddReference(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == false,
"Spec §8.55 bit 13: AddReference must be denied without bit "
"on source (allowAddReference returned true)");
/* Now grant AddReference — should be allowed. */
ck_assert_uint_eq(UA_Server_addRolePermissions(server, src, roleId,
UA_PERMISSIONTYPE_ADDREFERENCE, false, false),
UA_STATUSCODE_GOOD);
allowed = ac->allowAddReference(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == true,
"AddReference must be allowed once bit 13 is granted on source");
clearAdminSessionRoles();
UA_Server_deleteNode(server, src, true);
UA_Server_deleteNode(server, tgt, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "AddRefRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(RemoveReference_checked_on_source) {
UA_NodeId roleId = addRole("RemRefRole");
assignRoleToAdminSession(roleId);
UA_NodeId src = addVariable("RemRefSource");
UA_NodeId tgt = addVariable("RemRefTarget");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, src, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_DeleteReferencesItem item;
UA_DeleteReferencesItem_init(&item);
item.sourceNodeId = src;
item.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
item.isForward = true;
item.targetNodeId.nodeId = tgt;
item.deleteBidirectional = true;
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean allowed = ac->allowDeleteReference(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == false,
"Spec §8.55 bit 14: RemoveReference must be denied without bit "
"on source (allowDeleteReference returned true)");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, src, roleId,
UA_PERMISSIONTYPE_REMOVEREFERENCE, false, false),
UA_STATUSCODE_GOOD);
allowed = ac->allowDeleteReference(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == true,
"RemoveReference must be allowed once bit 14 is granted");
clearAdminSessionRoles();
UA_Server_deleteNode(server, src, true);
UA_Server_deleteNode(server, tgt, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "RemRefRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(DeleteNode_checked_on_target) {
UA_NodeId roleId = addRole("DelNodeRole");
assignRoleToAdminSession(roleId);
UA_NodeId v = addVariable("VarToDelete");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_DeleteNodesItem item;
UA_DeleteNodesItem_init(&item);
item.nodeId = v;
item.deleteTargetReferences = true;
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean allowed = ac->allowDeleteNode(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == false,
"Spec §8.55 bit 15: DeleteNode must be denied without bit "
"on the target node (allowDeleteNode returned true)");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, v, roleId,
UA_PERMISSIONTYPE_DELETENODE, false, false),
UA_STATUSCODE_GOOD);
allowed = ac->allowDeleteNode(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == true,
"DeleteNode must be allowed once bit 15 is granted");
clearAdminSessionRoles();
UA_Server_deleteNode(server, v, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "DelNodeRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
START_TEST(AddNode_checked_on_parent) {
UA_NodeId roleId = addRole("AddNodeRole");
assignRoleToAdminSession(roleId);
/* Parent is an Object child of ObjectsFolder, RBAC engaged with
* BROWSE only. */
UA_NodeId parent = addObject("AddNodeParent");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, parent, roleId,
UA_PERMISSIONTYPE_BROWSE, false, false), UA_STATUSCODE_GOOD);
UA_AddNodesItem item;
UA_AddNodesItem_init(&item);
item.parentNodeId.nodeId = parent;
item.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
item.requestedNewNodeId.nodeId = UA_NODEID_NULL;
item.browseName = UA_QUALIFIEDNAME(1, "ChildVar");
item.nodeClass = UA_NODECLASS_VARIABLE;
item.typeDefinition.nodeId =
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
UA_AccessControl *ac = &UA_Server_getConfig(server)->accessControl;
UA_Boolean allowed = ac->allowAddNode(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == false,
"Spec §8.55 bit 16: AddNode must be denied without bit on the "
"parent (allowAddNode returned true)");
ck_assert_uint_eq(UA_Server_addRolePermissions(server, parent, roleId,
UA_PERMISSIONTYPE_ADDNODE, false, false),
UA_STATUSCODE_GOOD);
allowed = ac->allowAddNode(server, ac,
&adminSessionId, NULL, &item);
ck_assert_msg(allowed == true,
"AddNode must be allowed once bit 16 is granted on parent");
clearAdminSessionRoles();
UA_Server_deleteNode(server, parent, true);
UA_Server_removeRole(server, UA_QUALIFIEDNAME(0, "AddNodeRole"));
UA_NodeId_clear(&roleId);
}
END_TEST
/* --------------------------------------------------------------------- */
/* Suite */
/* --------------------------------------------------------------------- */
static Suite *testSuite(void) {
Suite *s = suite_create("Server RBAC Interlocked Permissions");
#ifdef UA_ENABLE_METHODCALLS
TCase *tc_call = tcase_create("Call interlock (object + method)");
tcase_add_unchecked_fixture(tc_call, setup, teardown);
tcase_add_test(tc_call, Call_interlock_objectOnly);
tcase_add_test(tc_call, Call_interlock_methodOnly);
tcase_add_test(tc_call, Call_interlock_both);
tcase_add_test(tc_call, Call_interlock_neither);
suite_add_tcase(s, tc_call);
#endif
TCase *tc_bits = tcase_create("Permission bits are independent");
tcase_add_unchecked_fixture(tc_bits, setup, teardown);
tcase_add_test(tc_bits, Browse_does_not_imply_Read);
tcase_add_test(tc_bits, Read_does_not_imply_Browse);
tcase_add_test(tc_bits, ReadRolePermissions_distinct_from_Read);
tcase_add_test(tc_bits, Write_does_not_imply_WriteAttribute);
tcase_add_test(tc_bits, WriteAttribute_does_not_imply_WriteRolePermissions);
tcase_add_test(tc_bits, WriteRolePermissions_distinct_bit);
tcase_add_test(tc_bits, WriteHistorizing_distinct_bit);
suite_add_tcase(s, tc_bits);
TCase *tc_hist = tcase_create("History permission bits");
tcase_add_unchecked_fixture(tc_hist, setup, teardown);
tcase_add_test(tc_hist, ReadHistory_setsHistoryReadAccessLevel);
tcase_add_test(tc_hist, InsertHistory_setsHistoryWriteAccessLevel);
tcase_add_test(tc_hist, ModifyHistory_setsHistoryWriteAccessLevel);
tcase_add_test(tc_hist, DeleteHistory_setsHistoryWriteAccessLevel);
suite_add_tcase(s, tc_hist);
TCase *tc_nm = tcase_create("Node-management permission bits");
tcase_add_unchecked_fixture(tc_nm, setup, teardown);
tcase_add_test(tc_nm, AddReference_checked_on_source);
tcase_add_test(tc_nm, RemoveReference_checked_on_source);
tcase_add_test(tc_nm, DeleteNode_checked_on_target);
tcase_add_test(tc_nm, AddNode_checked_on_parent);
suite_add_tcase(s, tc_nm);
TCase *tc_multi = tcase_create("Multi-role OR semantics");
tcase_add_unchecked_fixture(tc_multi, setup, teardown);
tcase_add_test(tc_multi, MultiRole_OR_satisfies_interlock);
suite_add_tcase(s, tc_multi);
TCase *tc_evt = tcase_create("ReceiveEvents interlock");
tcase_add_unchecked_fixture(tc_evt, setup, teardown);
tcase_add_test(tc_evt, ReceiveEvents_storedSeparatelyOnEventTypeAndSource);
tcase_add_test(tc_evt, ReceiveEvents_grantedOnBothPropagates);
suite_add_tcase(s, tc_evt);
#if defined(UA_ENABLE_DRIVER_GDS_RECEIVER) && defined(UA_ENABLE_METHODCALLS) && \
defined(UA_NS0ID_SERVERCONFIGURATION_CERTIFICATEGROUPS_DEFAULTAPPLICATIONGROUP_TRUSTLIST_OPEN)
TCase *tc_gds = tcase_create("GDS SecurityAdmin permissions");
tcase_add_unchecked_fixture(tc_gds, setup, teardown);
tcase_add_test(tc_gds, GDS_SecurityAdmin_call_and_visibility);
suite_add_tcase(s, tc_gds);
#endif
return s;
}
int main(void) {
Suite *s = testSuite();
SRunner *sr = srunner_create(s);
srunner_set_fork_status(sr, CK_NOFORK);
srunner_run_all(sr, CK_NORMAL);
int failed = srunner_ntests_failed(sr);
srunner_free(sr);
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}