12:42, EEST
March 31, 2022
Hello,
I am evaluating the Java SDK for the following use case:
A multi-user “proxy” OPC UA server offers a set of user’s devices based on the currently logged user (token).
This means I have to provide a list of user-specific nodes when browsing the root node (deviceSet). For instance, the user U1 has two devices D1 and D2 of types T1 and T2, the user U2 has one device D3 of type T1, and so on. Obviously, U1 can see only D1 and D2, and U2 can see only D3, etc.
How is this possible with the SDK? A code snippet would be really helpful!
Many thanks
15:55, EEST
Moderators
February 11, 2020
Hello,
The onBrowseNode method of NodeManagerListener interface can be used to filter the UaReferences to be returned in browse results based on the ServiceContext parameter, which contains the UserIdentityToken representing the user of the Client application that made the browse request.
You will need to implement a method that takes UserIdentityToken and UaReference as its inputs and returns true when the the user is allowed to receive the UaReference in browse results and false otherwise. You can also include the NodeId of the Node being browsed in the parameters of this method if you wish. You can then have the onBrowseNode method return the value of this method to filter the returned UaReferences based on the user.
UaReferences contain the NodeIds of the Source Node and the Target Node of the References. For example, you could use a Map from usernames as Strings to Lists of NodeIds of Target Nodes and return true for UaReferences where the NodeId of the Target Node is on the list for the username and false otherwise.
Here’s an example of how the method could look:
private boolean checkUserAccess(UserIdentityToken token, NodeId nodeId) { if (token instanceof UserNameIdentityToken) { UserNameIdentityToken user = (UserNameIdentityToken) token; if (map.containsKey(user.getUserName())) { if (map.get(user.getUserName()).contains(nodeId)) { return true; } } } // TODO handle other types of UserIdentityToken, e.g. AnonymousIdentityToken return false; }
In the method, map is an instance of Map<String, List>. Using this method, the onBrowseNode method would be
@Override public boolean onBrowseNode(ServiceContext serviceContext, ViewDescription view, NodeId nodeId, UaNode node, UaReference reference) { return checkUserAccess(serviceContext.getSession().getUserIdentity().getToken(), reference.getTargetNode().getNodeId()); }
Note, that using a method like this to filter UaReferences would mean that UaReferences of the NodeManager are not included in browse results unless the NodeIds of their Target Nodes have been added to the lists in the map. If there are Nodes that should be accessible by all users, I suggest adding a separate list of their NodeIds and checking if the NodeId of the Target Node is on that list before checking the UserIdentityToken. Alternatively, you can specify that some particular user doesn’t have access to UaReferences targeting a particular Node and have the method return false in such situation and true otherwise. The most efficient way to implement this check will depend on your application.
Since you’re working with Device Nodes, you could probably just filter the direct UaReferences from DeviceSet Node to the Device Nodes based on users. The onBrowseNode would be called with DeviceSet as the Source Node of the UaReferences and this filtering would need to be done with a class implementing NodeManagerListener interface added to the NodeManager of DeviceSet Node.
You can check MyNodeManagerListener class for an example of a class that implements the NodeManagerListener interface. This listener is assigned to an instance of MyNodeManager class in createAddressSpace method of SampleConsoleServer class.
However, filtering the UaReferences is just the first step of implementing user access management. You would then need to make sure that users can’t access information on the Nodes even if they have guesses their NodeIds correctly. This would be done using IoManagerListener and MethodManager to prevent unauthorized users from performing IO operations and calling Methods respectively. I’ll leave the details concerning those aside, but the ServiceContext would be used similarly to check if the user has the right to perform the action on the chosen Node.
18:06, EEST
March 31, 2022
Matti Siponen said
The onBrowseNode method of NodeManagerListener interface can be used to filter the UaReferences to be returned in browse results based on the ServiceContext parameter
If I understand you correctly, implementing the onBrowseNode method will allow me to filter, that means, to show/hide particular at-the-startup-time-created sub-nodes of a node based on the user’s token. This is a nice start.
But, what I need is to dynamically create the sub-nodes, on-the-fly, based on the user’s profile. For instance, a user can have zero or many devices of a particular type, so it is not possible to prepare the whole deviceSet on the startup and then just show/hide. Moreover, some attributes such as DisplayName change based on the user.
Example:
The User A sees:
- DeviceSet |- My Coffee Machine |- Boss's Coffee Machine
while the User B sees in parallel:
- DeviceSet |- Coffee Machine in Teakitchen |- Ice Machine in Teakitchen |- Ice Machine in Cantina
This is baked by a remote service that returns a list of devices (name, type) for a particular user, so the OPC UA server would build the node-set based on this information.
Is this possible?
Thanks!
8:38, EEST
Moderators
February 11, 2020
Hello,
If you instead need to add References, then you can use the onGetReferences method of the NodeManagerListener interface. This method is called whenever a Node is browsed and allows you to add additional UaReferences to the returned list of UaReferences. You can use the ServiceContext parameter to determine the user’s identity and add the UaReferences to the list of UaReferences that you want to browse to return for that user.
Since you can’t create the AddressSpace in advance either, you will need to implement a solution where the Nodes don’t exist in the Server application’s memory, but the information on them is provided as requested. This can be quite complicated, but I recommend you to take a look at MyBigNodeManager class of SampleConsoleServer sample application. This class extends NodeManager class and doesn’t store any UaNodes into memory. Instead it provides information on the Nodes such as References, BrowseNames, Values of Attributes, etc. on demand.
By using this type of approach together with classes that implement NodeManagerListener and IoManagerListener, you can create your own class that extends NodeManager that will make it so that different users will see different Nodes on the AddressSpace.
However, this topic is quite advanced. While we can point you in the right direction, we can’t really provide sample code for the solution as whole.
17:48, EEST
March 31, 2022
Matti Siponen said
This class extends NodeManager class and doesn’t store any UaNodes into memory. Instead it provides information on the Nodes such as References, BrowseNames, Values of Attributes, etc. on demand.
Thanks!
This approach could work fine for simple node structures defined directly by the custom NodeManager. Is there also a solution for reusing pre-defined node types (in a companion specification for example)?
Let’s say I want to provide a user-specific list of devices (as described above) and these nodes are all (or some of them) instances of the CommercialKitchenDeviceType type from the CommercialKitchenEquipment companion specification (http://opcfoundation.org/UA/Co…..Equipment/).
For good reasons I don’t want to re-create the whole type (structure, attributes, …) again in the custom NodeManager by custom code, rather I want to use the generated code from the specification (generated from NodeSet2.xml by the codegen).
Is there a way how to achieve this with the approach you mentioned above?
8:25, EEST
Moderators
February 11, 2020
Hello,
You could load the information model of Commercial Kitchen Equipment from its NodeSet XML file to your Server’s AddressSpace and configure the getTypeDefinition method of your NodeManager to return ExpandedNodeIds from that model to provide correct types for the managed Nodes based on their ExpandedNodeIds. See the loadInformationModels method of SampleConsoleServer class for an example of loading information models from NodeSet XML files.
I’m not sure if generating code would be useful if the UaNodes are not stored in memory. The point of generating code is to make creating instances of types easier by using the generated classes, but you said that you don’t want to create the entire AddressSpace in advance, so you’ll probably have to use an approach where Nodes are not stored in memory as UaNodes. Basically, you would be recognizing Nodes by their ExpandedNodeIds and then returning the requested information based on some sort of solution that doesn’t use UaNodes. For another existing example of NodeManager that doesn’t use UaNodes, see NonUaNodeComplianceNodeManager class.
Perhaps there are other solutions as well. For example, you could create parts of AddressSpace to memory as UaNodes depending on which users have connected to the Server and then filter the References returned when browsing in a way that different users would see different Nodes. In that case, you would be using the generated classes to create instances of types from the Commercial Kitchen Equipment companion specification. You could also use IoManager and IoManagerListener to provide different Values of Attributes to different users if necessary. However, there are no examples of this type of solution so you would have to experiment and figure it out on your own.
Again, this topic is very advanced, so we can’t really provide samples or detailed instructions on how to do any of this. All we can really say is that there shouldn’t be any technical limitations that would prevent you from doing this with or without UaNodes. The typical way of using the SDK is to create the entire AddressSpace to memory using UaNodes during Server’s start up. Accomplishing the same without using UaNodes at all or creating the AddressSpace in parts should be possible, but it will be more complicated.
Most Users Ever Online: 1919
Currently Online:
10 Guest(s)
Currently Browsing this Page:
1 Guest(s)
Top Posters:
Heikki Tahvanainen: 402
hbrackel: 144
rocket science: 88
pramanj: 86
Francesco Zambon: 83
Ibrahim: 78
Sabari: 62
kapsl: 57
gjevremovic: 49
Xavier: 43
Member Stats:
Guest Posters: 0
Members: 737
Moderators: 7
Admins: 1
Forum Stats:
Groups: 3
Forums: 15
Topics: 1524
Posts: 6451
Newest Members:
jonathonmcintyre, fannielima, kristiewinkle8, rust, christamcdowall, redaahern07571, nigelbdhmp, travistimmons, AnnelCib, dalenegettingerModerators: Jouni Aro: 1026, Pyry: 1, Petri: 0, Bjarne Boström: 1026, Jimmy Ni: 26, Matti Siponen: 346, Lusetti: 0
Administrators: admin: 1