UA Ansi C Server Professional  1.3.2.233
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Security Lesson 3: Assigning Access Rights to Nodes

This lesson provides an example how to use the SDK's authorization module. For detailed information about this module, please refer to Authorization.

Content:

Step 1: Configuring the Endpoint

For demonstrating the abilities of the SDK's authorization module, we will allow the clients to connect either anonymous or with a username and password. This is done by configuring two user token policies:

/* Set the endpoint configuration to allow Anonymous and Username/Password logon */
pServerConfig->uNoOfUserTokenPolicy = 2;
pServerConfig->pUserTokenPolicy = (OpcUa_UserTokenPolicy*)OpcUa_Alloc(pServerConfig->uNoOfUserTokenPolicy *
sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pServerConfig->pUserTokenPolicy[0]);
pServerConfig->pUserTokenPolicy[0].TokenType = OpcUa_UserTokenType_Anonymous;
OpcUa_String_StrnCat(&pServerConfig->pUserTokenPolicy[0].PolicyId,
OpcUa_String_FromCString("0"),
OPCUA_STRING_LENDONTCARE);
OpcUa_UserTokenPolicy_Initialize(&pServerConfig->pUserTokenPolicy[1]);
pServerConfig->pUserTokenPolicy[1].TokenType = OpcUa_UserTokenType_UserName;
OpcUa_String_StrnCat(&pServerConfig->pUserTokenPolicy[1].PolicyId,
OpcUa_String_FromCString("1"),
OPCUA_STRING_LENDONTCARE);
OpcUa_String_StrnCat(&pServerConfig->pUserTokenPolicy[1].SecurityPolicyUri,
OpcUa_String_FromCString(OpcUa_SecurityPolicy_Basic256),
OPCUA_STRING_LENDONTCARE);

Step 2: Creating Nodes with Custom Access Rights

Additionally we add a custom provider, equal to Lesson 2: Extending the Address Space with Real World Data. The code of the custom provider is the same, for some slight differences which are the following:

Instead of creating object types and their instances, we just create a new folder and three nodes in it, using the function CustomProvider_CreateSampleNodes. They will be used as examples for how to set user rights with the authorization module.

The first node should be read- and writable by all users. To set these access rights, we get the UaServer_iNode of the created node and assign the according rights. The user and group id don't matter in this case, as everyone can do everything with the node, so we set them to 0 which represents the anonymous user in our custom users file. The mode parameter is set to a value of 0x0FFF which is equivalent to all access rights OR'ed together.

OpcUa_StatusCode CustomProvider_CreateSampleNodes(OpcUa_BaseNode *a_pOwner, OpcUa_NodeId *a_pStartingNodeId)
{
...
/* Create variable that is read- and writable by all users */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteAll_ReadAll");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
0,
0,
0x0FFF);

The second node shall be restricted in a way that all users are allowed to read it, but only the user 'root' should be able to write values to it. To achieve this, we set the user id and group id to 1, which stands for the user and the group 'root'. The access rights are a combination that gives the user full rights, but only read and browse access for the group and others.

/* Create variable that is readable by all users and only writable by user 'root' */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteRoot_ReadAll");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
1,
1,
UA_USER_ATTRWRITABLE | UA_USER_WRITABLE | UA_USER_READABLE | UA_USER_BROWSEABLE |
UA_GROUP_READABLE | UA_GROUP_BROWSEABLE |
UA_OTHER_READABLE | UA_OTHER_BROWSEABLE);

The third node in our example will have the most restrictive rights. It will be read- and writable only for the user 'root', all other users will see it but will not be able to read or write to it. The user and group ids are set to 1 like for the last node, the mode is now set to full access rights just for the owner of the node.

/* Create variable that is read- and writable only by user 'root' */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteRoot_ReadRoot");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
1,
1,
UA_USER_ATTRWRITABLE | UA_USER_WRITABLE | UA_USER_READABLE | UA_USER_BROWSEABLE);

Step 3: Checking the Access Rights in Service Calls

Now the service call handlers have to be extended to check if the requesting client has sufficient rights for the according service call. As an example, the interesting part of CustomProvider_ReadAsync is shown below. Depending on the attribute that is read, it simply calls the helper function of the SDK to check if the session has sufficient rights to read the attribute, passing the INode structure of the affected OpcUa_BaseNode and the UserIdentityData of the calling session. If the rights are not sufficient, the read result is set to BadNotReadable.

if ( (a_pReadCtx->pRequest->NodesToRead[i].AttributeId == OpcUa_Attributes_Value &&
a_pReadCtx->pSession->UserIdentityData)) ||
(a_pReadCtx->pRequest->NodesToRead[i].AttributeId != OpcUa_Attributes_Value &&
a_pReadCtx->pSession->UserIdentityData)))
{
pRes->Results[i].StatusCode = OpcUa_BadNotReadable;
continue;
}

Similar to this, all other UA service call handlers are extended to check the access rights of a client.

Step 4: Configuring Users and Groups

The next step is to setup the user database to be used for verifying logons and authenticating users. The SDK's internal authorization module uses the users file (as used in the last lesson) for this. Additionally we will need the group file provided with the example for correlating users and groups. There are some predefined users in the files, the passwords are listed in Internal authentication module. Simply copy the files next to the generated server application and start up the server.

Step 5: Testing the Access Rights with UaExpert

To test our configuration, we connect to the server using UaExpert the same way as in the last lesson, using encryption and the username 'root'.

Figure 3-1 Writing Variables as User 'root'

Being connected to the server as the user 'root' we can read, write and subscribe all three variables without a problem. Let's now change to connect to the server as anonymous user and try again:

Figure 3-2 Change to Anonymous User

Simply disconnect the connection to the server with 'Disconnect Server', click on 'Server Properties...' and check Anonymous in the 'Authentication Settings'. After this, click on 'Connect Server'.

Figure 3-3 Writing Variables as Anonymous User

When trying to write to the variable Sample_WriteRoot_ReadAll you will notice that it returns with the error 'BadNotWritable'. Also, trying to read the Sample_WriteRoot_ReadRoot will return an error, as this is the variable readable only by the user 'root'.