.NET Based OPC UA Client/Server SDK  2.6.3.431
Lesson 9: Setting Access Rights Using INodeAccessInfo

Overview

This example shows how to use INodeAccessInfo to set access rights to nodes and events. The class NodeAccessInfo is implementing this interface. This class allows to set distinct access rights for two particular scopes and for all other scopes together. This lesson will only use NodeAccessInfo. If your use case requires to set access rights for more than two scopes for single nodes, you can create your own class implementing the interface.

In this lesson, the access rights are set directly in code. UaModeler 1.5 will allow you set the NodeAccessInfo in the model. Please refer to the documentation of UaModeler (once it is released) for a tutorial if you prefer this way of setting the access rights.

Another possibility to assign access rights to nodes by overriding HasAccess methods that are defined at the BaseNodeManager. Please see the UaDemoServer example for a description.

Add ImpersonateUser EventHandler to Assign ScopeIds to UserIdentity

Using the ImpersonateUser EventHandler you can not only control which clients are allowed connect to a server, but also enables you to add users to one or more groups, so-called scopes.

You can create a UserIdentity that will be asigned to the context of a session. This UserIdentity then contains the current scopes.

Define Scopes

The SDK contains some well-known scopes that are defined in the WellKnownScopes enumeration. It is recommended to use these well-known scopes and extend the list if required. In this lesson, we will only use the well-known scopes Operator and Observer.

Define Usernames and Passwords

This example contains two hard coded users:

  • john (password: master)
  • joe (password: god)

John will be added to the scopes Operator and Observer, joe only to Observer.

Note
For simplicity, the usernames and passwords in this example are specified in code. In real world applications, you should use some kind of database.

Enable Logon with Username and Password

For being able to check access rights, we have to enable UserName tokens in the server configuration. Therefore we extend the ServerSettings in app.config:

<ServerSettings xmlns="http://unifiedautomation.com/schemas/2011/12/Application.xsd">
<UserIdentity>
<EnableAnonymous>true</EnableAnonymous>
<EnableUserName>true</EnableUserName>
</UserIdentity>

Sample Code

The following code snippets can be found in the file GettingStartedServerManager.cs.

this.SessionManager.ImpersonateUser += new ImpersonateEventHandler(SessionManager_ImpersonateUser);
private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args)
{
// allow anonymous log on
AnonymousIdentityToken anonynmousToken = args.NewIdentity as AnonymousIdentityToken;
if (anonynmousToken != null)
{
return;
}
// check for UserName token
UserNameIdentityToken userNameToken = args.NewIdentity as UserNameIdentityToken;
if (userNameToken != null)
{
if (String.IsNullOrEmpty(userNameToken.UserName))
{
args.IdentityValidationError = StatusCodes.BadIdentityTokenInvalid;
return;
}
if (userNameToken.UserName == "john")
{
if (userNameToken.DecryptedPassword != "master")
{
args.IdentityValidationError = StatusCodes.BadUserAccessDenied;
}
else
{
args.Identity = new UserIdentity(userNameToken);
args.Identity.ScopeIds.Add((uint)WellKnownScopes.Operator);
args.Identity.ScopeIds.Add((uint)WellKnownScopes.Observer);
}
return;
}
if (userNameToken.UserName == "joe")
{
if (userNameToken.DecryptedPassword != "god")
{
args.IdentityValidationError = StatusCodes.BadUserAccessDenied;
}
else
{
args.Identity = new UserIdentity(userNameToken);
args.Identity.ScopeIds.Add((uint)WellKnownScopes.Observer);
}
return;
}
return;
}
args.IdentityValidationError = StatusCodes.BadIdentityTokenRejected;
return;
}

Add NodeAccessInfo to Variable

Now that we have added our users to scopes, we can set NodeAccessInfo to restrict access to nodes based on these scopes. This section shows how to do this for variables. We set the NodeAccessInfo for the variables “Temperature” and “TemperatureSetPoint”. All users shall be able to read the non-value attributes and to browse the nodes. The access to the Value attribute will be restricted.

Operators shall be able to

  • read the current value and the historical data of Temperature
  • and read and write the value of TemperatureSetPoint.

Observers shall be able to

  • read the value of Temperature
  • and read the value of TemperatureSetPoint.

Others shall only be able to read the value of TemperatureSetPoint.

NodeAccessInfo nodeAccessInfoTemperature
= new NodeAccessInfo(
PermissionType.AllPermissions,
PermissionType.AttrReadable | PermissionType.Browseable | PermissionType.Readable,
PermissionType.AttrReadable | PermissionType.Browseable,
(uint)WellKnownScopes.Operator,
(uint)WellKnownScopes.Observer);
NodeAccessInfo nodeAccessInfoTemperatureSetPoint
= new NodeAccessInfo(
PermissionType.AllPermissions,
PermissionType.AttrReadable | PermissionType.Browseable | PermissionType.Readable | PermissionType.Writable,
PermissionType.AttrReadable | PermissionType.Browseable | PermissionType.Readable,
(uint)WellKnownScopes.Operator,
(uint)WellKnownScopes.Observer);
NodeId parentId = new NodeId(block.Name, InstanceNamespaceIndex);
SetNodePermissions(
parentId,
new QualifiedName("Temperature", TypeNamespaceIndex),
nodeAccessInfoTemperature);
SetNodePermissions(
parentId,
new QualifiedName("TemperatureSetPoint", TypeNamespaceIndex),
nodeAccessInfoTemperatureSetPoint);

If the nodes have NodeHandleType Internal or InternalPolled, there’s nothing else to implement. But our nodes are configured to have the NodeHandleType ExternalPolled. Thus, Read and Write are implemented in the NodeManager and we have to check the access permissions before returning values. We are using the HasAccess methods implemented in the BaseNodeManager to check the user’s scope against the INodeAccessInfo set for the node (see Lesson9aNodeManager.cs):

if (!HasAccess(context, operationHandles[ii].NodeHandle, UserAccessMask.Read))
{
dv = new DataValue(StatusCodes.BadUserAccessDenied);
}
else
else if (!HasAccess(context, operationHandles[ii].NodeHandle, UserAccessMask.Write))
{
error = StatusCodes.BadUserAccessDenied;
}

To test the implementation, we connect to the server with UaExpert as anonymous user first. When selecting one of the Temperature variables, we notice in the Attributes window that we’re not allowed to read the Value attribute and the UserAccessLevel is “None”. When changing the user to “john”, the value can be read as the UserAccessLevel is now “CurrentRead, HistoryRead” (see screenshots below).

serverlesson09_variable.png
Figure 9.1: Attributes of variable Temperature when connected as anonymous user (left) and as user john (right)

Add NodeAccessInfo to Method

Setting the NodeAccessInfo of methods is quite similar. We will restrict the access so that only users in the scope Operator will be able to call the Controller methodes.

In this case, we only have to assign the NodeAccessInfo. The SDK perfoms all access checks. The following sample code can be found in the file Lesson9NodeManager.NodeAccessInfo.cs

NodeAccessInfo nodeAccessInfo
= new NodeAccessInfo(
PermissionType.AllPermissions,
PermissionType.AllPermissions,
PermissionType.AttrReadable | PermissionType.Browseable,
(uint)WellKnownScopes.Operator,
(uint)WellKnownScopes.Anonymous);
foreach (BlockConfiguration block in m_system.GetBlocks())
{
NodeId parentId = new NodeId(block.Name, InstanceNamespaceIndex);
SetNodePermissions(
parentId,
new QualifiedName("Start", TypeNamespaceIndex),
nodeAccessInfo);
SetNodePermissions(
parentId,
new QualifiedName("StartWithSetPoint", TypeNamespaceIndex),
nodeAccessInfo);
SetNodePermissions(
parentId,
new QualifiedName("Stop", TypeNamespaceIndex),
nodeAccessInfo);
}

To test the implementation, we connect to the server as anonymous user and call the method stop on a controller. The method call will fail with error “BadUserAccessDenied”. After changing to user “john” we are able to successfully call the method.

Add NodeAccessInfo to Alarms and Events

INodeAccessInfo can also be used to control the access to events. In this example we will create a NodeAccessInfo and add it to the UserData of the alarm. When sending the event associated with the alarm, we assign this UserData as INodeAccessInfo to the event. The following sample code can be found in the file Lesson9aNodeManager.cs.

alarm.UserData = new NodeAccessInfo(
PermissionType.AllPermissions,
PermissionType.AllPermissions,
0,
(uint)WellKnownScopes.Operator,
(uint)WellKnownScopes.Observer);
e = alarm.CreateEvent(Server.FilterManager, true);
e.NodeAccessInfo = alarm.UserData as INodeAccessInfo;

Again, we will test the implementation using UaExpert. First we connect with UaExpert as anonymous user, create an EventView, and drag and drop the Server object to this EventView. Then we create another session with the server as user john, create another EventView, and again drag and drop the Server object to the EventView.

We now call a method being user john and compare the EventViews (see screenshots below). Both users receive the event indicating that the controller state has changed, but only john is able to receive the event associated with the alarm.

serverlesson09_event.png
Figure 9.2: EventViews of the anonymous user (top) and user john (bottom)