Skip to content

Commit d1f9066

Browse files
authored
[Server] Enhance role permission validation (#3047)
* Enhance role permission validation - Make `ValidateRolePermissions` virtual in `CustomNodeManager.cs` to allow custom validation - Initialize OperationContext in Node.ReadAttribute of `MonitoredNode` to allow custom validation - Update `ValidateRolePermissions` in `MasterNodeManager.cs` to prevent null reference exception * Add caching * Make Cache timeout after 5 minutes * fix cache return * use low res ticks
1 parent e2e65dd commit d1f9066

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3849,7 +3849,7 @@ protected virtual void OnMonitoredItemCreated(
38493849
/// <param name="nodeId"></param>
38503850
/// <param name="requestedPermission"></param>
38513851
/// <returns></returns>
3852-
public ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission)
3852+
public virtual ServiceResult ValidateRolePermissions(OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission)
38533853
{
38543854
if (requestedPermission == PermissionType.None)
38553855
{

Libraries/Opc.Ua.Server/Diagnostics/MonitoredNode.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* ======================================================================*/
2929

3030
using System;
31+
using System.Collections.Concurrent;
3132
using System.Collections.Generic;
3233
using Opc.Ua;
3334
using Opc.Ua.Server;
@@ -142,6 +143,10 @@ public void Remove(MonitoredItem datachangeItem)
142143
if (Object.ReferenceEquals(DataChangeMonitoredItems[ii], datachangeItem))
143144
{
144145
DataChangeMonitoredItems.RemoveAt(ii);
146+
147+
// Remove the cached context for the monitored item
148+
m_contextCache.TryRemove(datachangeItem.Id, out _);
149+
145150
break;
146151
}
147152
}
@@ -271,7 +276,7 @@ public void OnReportEvent(ISystemContext context, NodeState node, IFilterTarget
271276

272277
}
273278
}
274-
}
279+
}
275280

276281
/// <summary>
277282
/// Called when the state of a Node changes.
@@ -331,8 +336,15 @@ public void QueueValue(
331336
value.SourceTimestamp = DateTime.MinValue;
332337
value.StatusCode = StatusCodes.Good;
333338

339+
ISystemContext contextToUse = context;
340+
341+
if (context is ServerSystemContext systemContext)
342+
{
343+
contextToUse = GetOrCreateContext(systemContext, monitoredItem);
344+
}
345+
334346
ServiceResult error = node.ReadAttribute(
335-
context,
347+
contextToUse,
336348
monitoredItem.AttributeId,
337349
monitoredItem.IndexRange,
338350
monitoredItem.DataEncoding,
@@ -347,11 +359,53 @@ public void QueueValue(
347359
}
348360
#endregion
349361

362+
#region Private Methods
363+
/// <summary>
364+
/// Gets or creates a cached context for the monitored item.
365+
/// </summary>
366+
/// <param name="monitoredItem">The monitored item.</param>
367+
/// <param name="context">The system context.</param>
368+
/// <returns>The cached or newly created context.</returns>
369+
private ServerSystemContext GetOrCreateContext(ServerSystemContext context, MonitoredItem monitoredItem)
370+
{
371+
uint monitoredItemId = monitoredItem.Id;
372+
int currentTicks = HiResClock.TickCount;
373+
374+
// Check if the context already exists in the cache
375+
if (m_contextCache.TryGetValue(monitoredItemId, out var cachedEntry))
376+
{
377+
// Check if the session or user identity has changed or the entry has expired
378+
if (cachedEntry.Context.OperationContext.Session != monitoredItem.Session
379+
|| cachedEntry.Context.OperationContext.UserIdentity != monitoredItem.EffectiveIdentity
380+
|| (currentTicks - cachedEntry.CreatedAtTicks) > m_cacheLifetimeTicks)
381+
{
382+
var updatedContext = context.Copy(new OperationContext(monitoredItem));
383+
m_contextCache[monitoredItemId] = (updatedContext, currentTicks);
384+
385+
return updatedContext;
386+
}
387+
// return cached entry
388+
else
389+
{
390+
return cachedEntry.Context;
391+
}
392+
}
393+
394+
// Create a new context and add it to the cache
395+
var newContext = context.Copy(new OperationContext(monitoredItem));
396+
m_contextCache.TryAdd(monitoredItemId, (newContext, currentTicks));
397+
398+
return newContext;
399+
}
400+
#endregion
401+
350402
#region Private Fields
351403
private CustomNodeManager2 m_nodeManager;
352404
private NodeState m_node;
353405
private List<MonitoredItem> m_dataChangeMonitoredItems;
354406
private List<IEventMonitoredItem> m_eventMonitoredItems;
407+
private readonly ConcurrentDictionary<uint, (ServerSystemContext Context, int CreatedAtTicks)> m_contextCache = new();
408+
private readonly int m_cacheLifetimeTicks = (int)TimeSpan.FromMinutes(5).TotalMilliseconds;
355409
#endregion
356410
}
357411
}

Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3431,7 +3431,7 @@ protected internal static ServiceResult ValidateRolePermissions(OperationContext
34313431
}
34323432
}
34333433

3434-
var currentRoleIds = context.UserIdentity.GrantedRoleIds;
3434+
var currentRoleIds = context?.UserIdentity?.GrantedRoleIds;
34353435
if (currentRoleIds == null || currentRoleIds.Count == 0)
34363436
{
34373437
return ServiceResult.Create(StatusCodes.BadUserAccessDenied, "Current user has no granted role.");

0 commit comments

Comments
 (0)