Add LDAP authentication with role-based OPC UA permissions
Replace static user list with GLAuth LDAP authentication. Group membership (ReadOnly, ReadWrite, AlarmAck) maps to granular OPC UA permissions for write and alarm-ack operations. Anonymous can still browse and read but not write. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
private readonly HistorianDataSource? _historianDataSource;
|
||||
private readonly bool _alarmTrackingEnabled;
|
||||
private readonly bool _anonymousCanWrite;
|
||||
private readonly Func<string?, IReadOnlyList<string>?>? _appRoleLookup;
|
||||
private readonly bool _ldapRolesEnabled;
|
||||
private readonly string _namespaceUri;
|
||||
|
||||
// NodeId → full_tag_reference for read/write resolution
|
||||
@@ -192,7 +194,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
PerformanceMetrics metrics,
|
||||
HistorianDataSource? historianDataSource = null,
|
||||
bool alarmTrackingEnabled = false,
|
||||
bool anonymousCanWrite = true)
|
||||
bool anonymousCanWrite = true,
|
||||
Func<string?, IReadOnlyList<string>?>? appRoleLookup = null,
|
||||
bool ldapRolesEnabled = false)
|
||||
: base(server, configuration, namespaceUri)
|
||||
{
|
||||
_namespaceUri = namespaceUri;
|
||||
@@ -201,6 +205,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
_historianDataSource = historianDataSource;
|
||||
_alarmTrackingEnabled = alarmTrackingEnabled;
|
||||
_anonymousCanWrite = anonymousCanWrite;
|
||||
_appRoleLookup = appRoleLookup;
|
||||
_ldapRolesEnabled = ldapRolesEnabled;
|
||||
|
||||
// Wire up data change delivery
|
||||
_mxAccessClient.OnTagValueChanged += OnMxAccessDataChange;
|
||||
@@ -467,6 +473,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
private ServiceResult OnAlarmAcknowledge(
|
||||
ISystemContext context, ConditionState condition, byte[] eventId, LocalizedText comment)
|
||||
{
|
||||
if (!HasAlarmAckPermission(context))
|
||||
return new ServiceResult(StatusCodes.BadUserAccessDenied);
|
||||
|
||||
var alarmInfo = _alarmInAlarmTags.Values
|
||||
.FirstOrDefault(a => a.ConditionNode == condition);
|
||||
if (alarmInfo == null)
|
||||
@@ -1103,9 +1112,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
if (errors[i] != null && errors[i].StatusCode == StatusCodes.BadNotWritable)
|
||||
continue;
|
||||
|
||||
// Enforce role-based write access: reject anonymous writes when AnonymousCanWrite is false
|
||||
if (!_anonymousCanWrite && context.UserIdentity?.GrantedRoleIds != null &&
|
||||
!context.UserIdentity.GrantedRoleIds.Contains(ObjectIds.WellKnownRole_AuthenticatedUser))
|
||||
// Enforce role-based write access
|
||||
if (!HasWritePermission(context))
|
||||
{
|
||||
errors[i] = new ServiceResult(StatusCodes.BadUserAccessDenied);
|
||||
continue;
|
||||
@@ -1155,6 +1163,39 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasWritePermission(OperationContext context)
|
||||
{
|
||||
// When LDAP roles are active, check for ReadWrite role
|
||||
if (_ldapRolesEnabled && _appRoleLookup != null)
|
||||
{
|
||||
var username = context.UserIdentity?.GetIdentityToken() is UserNameIdentityToken token ? token.UserName : null;
|
||||
var roles = _appRoleLookup(username);
|
||||
if (roles == null) return false; // unknown user
|
||||
return roles.Contains("ReadWrite");
|
||||
}
|
||||
|
||||
// Legacy behavior: reject anonymous writes when AnonymousCanWrite is false
|
||||
if (!_anonymousCanWrite && context.UserIdentity?.GrantedRoleIds != null &&
|
||||
!context.UserIdentity.GrantedRoleIds.Contains(ObjectIds.WellKnownRole_AuthenticatedUser))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HasAlarmAckPermission(ISystemContext context)
|
||||
{
|
||||
if (!_ldapRolesEnabled || _appRoleLookup == null)
|
||||
return true; // no LDAP restrictions without LDAP roles
|
||||
|
||||
var opContext = context as SystemContext;
|
||||
var username = opContext?.UserIdentity?.GetIdentityToken() is UserNameIdentityToken token ? token.UserName : null;
|
||||
var roles = _appRoleLookup(username);
|
||||
if (roles == null) return false;
|
||||
return roles.Contains("AlarmAck");
|
||||
}
|
||||
|
||||
private bool TryApplyArrayElementWrite(string tagRef, object? writeValue, string indexRange, out object updatedArray)
|
||||
{
|
||||
updatedArray = null!;
|
||||
|
||||
Reference in New Issue
Block a user