Remove static Users auth, use shared QualityMapper for historian, simplify LDAP permission checks
- Remove ConfigUserAuthenticationProvider and Users property — LDAP is the only auth mechanism - Fix historian quality mapping to use existing QualityMapper (OPC DA quality bytes, not custom mapping) - Add AppRoles constants, unify HasWritePermission/HasAlarmAckPermission into shared HasRole helper - Hoist write permission check out of per-item loop, eliminate redundant _ldapRolesEnabled field - Update docs (Configuration.md, Security.md, OpcUaServer.md, HistoricalDataAccess.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,6 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
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
|
||||
@@ -195,8 +194,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
HistorianDataSource? historianDataSource = null,
|
||||
bool alarmTrackingEnabled = false,
|
||||
bool anonymousCanWrite = true,
|
||||
Func<string?, IReadOnlyList<string>?>? appRoleLookup = null,
|
||||
bool ldapRolesEnabled = false)
|
||||
Func<string?, IReadOnlyList<string>?>? appRoleLookup = null)
|
||||
: base(server, configuration, namespaceUri)
|
||||
{
|
||||
_namespaceUri = namespaceUri;
|
||||
@@ -206,7 +204,6 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
_alarmTrackingEnabled = alarmTrackingEnabled;
|
||||
_anonymousCanWrite = anonymousCanWrite;
|
||||
_appRoleLookup = appRoleLookup;
|
||||
_ldapRolesEnabled = ldapRolesEnabled;
|
||||
|
||||
// Wire up data change delivery
|
||||
_mxAccessClient.OnTagValueChanged += OnMxAccessDataChange;
|
||||
@@ -1103,6 +1100,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
{
|
||||
base.Write(context, nodesToWrite, errors);
|
||||
|
||||
var canWrite = HasWritePermission(context);
|
||||
|
||||
for (int i = 0; i < nodesToWrite.Count; i++)
|
||||
{
|
||||
if (nodesToWrite[i].AttributeId != Attributes.Value)
|
||||
@@ -1112,8 +1111,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
if (errors[i] != null && errors[i].StatusCode == StatusCodes.BadNotWritable)
|
||||
continue;
|
||||
|
||||
// Enforce role-based write access
|
||||
if (!HasWritePermission(context))
|
||||
if (!canWrite)
|
||||
{
|
||||
errors[i] = new ServiceResult(StatusCodes.BadUserAccessDenied);
|
||||
continue;
|
||||
@@ -1165,14 +1163,8 @@ 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");
|
||||
}
|
||||
if (_appRoleLookup != null)
|
||||
return HasRole(context.UserIdentity, Domain.AppRoles.ReadWrite);
|
||||
|
||||
// Legacy behavior: reject anonymous writes when AnonymousCanWrite is false
|
||||
if (!_anonymousCanWrite && context.UserIdentity?.GrantedRoleIds != null &&
|
||||
@@ -1186,14 +1178,18 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
|
||||
private bool HasAlarmAckPermission(ISystemContext context)
|
||||
{
|
||||
if (!_ldapRolesEnabled || _appRoleLookup == null)
|
||||
return true; // no LDAP restrictions without LDAP roles
|
||||
if (_appRoleLookup == null)
|
||||
return true;
|
||||
|
||||
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");
|
||||
var identity = (context as SystemContext)?.UserIdentity;
|
||||
return HasRole(identity, Domain.AppRoles.AlarmAck);
|
||||
}
|
||||
|
||||
private bool HasRole(IUserIdentity? identity, string requiredRole)
|
||||
{
|
||||
var username = identity?.GetIdentityToken() is UserNameIdentityToken token ? token.UserName : null;
|
||||
var roles = _appRoleLookup!(username);
|
||||
return roles != null && roles.Contains(requiredRole);
|
||||
}
|
||||
|
||||
private bool TryApplyArrayElementWrite(string tagRef, object? writeValue, string indexRange, out object updatedArray)
|
||||
|
||||
Reference in New Issue
Block a user