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:
@@ -20,8 +20,15 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of username/password pairs accepted for authenticated access.
|
||||
/// Ignored when Ldap.Enabled is true.
|
||||
/// </summary>
|
||||
public List<UserCredential> Users { get; set; } = new List<UserCredential>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP authentication settings. When Ldap.Enabled is true,
|
||||
/// credentials are validated against the LDAP server and the Users list is ignored.
|
||||
/// </summary>
|
||||
public LdapConfiguration Ldap { get; set; } = new LdapConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -104,6 +104,32 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration
|
||||
Log.Warning("Only the 'None' security profile is configured — transport security is disabled");
|
||||
}
|
||||
|
||||
// Authentication
|
||||
Log.Information("Authentication.AllowAnonymous={AllowAnonymous}, AnonymousCanWrite={AnonymousCanWrite}",
|
||||
config.Authentication.AllowAnonymous, config.Authentication.AnonymousCanWrite);
|
||||
|
||||
if (config.Authentication.Ldap.Enabled)
|
||||
{
|
||||
Log.Information("Authentication.Ldap.Enabled=true, Host={Host}, Port={Port}, BaseDN={BaseDN}",
|
||||
config.Authentication.Ldap.Host, config.Authentication.Ldap.Port, config.Authentication.Ldap.BaseDN);
|
||||
Log.Information("Authentication.Ldap groups: ReadOnly={ReadOnly}, ReadWrite={ReadWrite}, AlarmAck={AlarmAck}",
|
||||
config.Authentication.Ldap.ReadOnlyGroup, config.Authentication.Ldap.ReadWriteGroup, config.Authentication.Ldap.AlarmAckGroup);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.Authentication.Ldap.ServiceAccountDn))
|
||||
{
|
||||
Log.Warning("Authentication.Ldap.ServiceAccountDn is empty — group lookups will fail");
|
||||
}
|
||||
|
||||
if (config.Authentication.Users.Count > 0)
|
||||
{
|
||||
Log.Warning("Authentication.Users list is ignored when Ldap is enabled");
|
||||
}
|
||||
}
|
||||
else if (config.Authentication.Users.Count > 0)
|
||||
{
|
||||
Log.Information("Authentication.Users configured: {Count} user(s)", config.Authentication.Users.Count);
|
||||
}
|
||||
|
||||
// Redundancy
|
||||
if (config.OpcUa.ApplicationUri != null)
|
||||
Log.Information("OpcUa.ApplicationUri={ApplicationUri}", config.OpcUa.ApplicationUri);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// LDAP authentication and group-to-role mapping settings.
|
||||
/// </summary>
|
||||
public class LdapConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether LDAP authentication is enabled.
|
||||
/// When true, user credentials are validated against the configured LDAP server
|
||||
/// and group membership determines OPC UA permissions.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP server hostname or IP address.
|
||||
/// </summary>
|
||||
public string Host { get; set; } = "localhost";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP server port.
|
||||
/// </summary>
|
||||
public int Port { get; set; } = 3893;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base DN for LDAP operations.
|
||||
/// </summary>
|
||||
public string BaseDN { get; set; } = "dc=lmxopcua,dc=local";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bind DN template. Use {username} as a placeholder.
|
||||
/// </summary>
|
||||
public string BindDnTemplate { get; set; } = "cn={username},dc=lmxopcua,dc=local";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service account DN used for LDAP searches (group lookups).
|
||||
/// </summary>
|
||||
public string ServiceAccountDn { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service account password.
|
||||
/// </summary>
|
||||
public string ServiceAccountPassword { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP connection timeout in seconds.
|
||||
/// </summary>
|
||||
public int TimeoutSeconds { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP group name that grants read-only access.
|
||||
/// </summary>
|
||||
public string ReadOnlyGroup { get; set; } = "ReadOnly";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP group name that grants read-write access.
|
||||
/// </summary>
|
||||
public string ReadWriteGroup { get; set; } = "ReadWrite";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LDAP group name that grants alarm acknowledgment access.
|
||||
/// </summary>
|
||||
public string AlarmAckGroup { get; set; } = "AlarmAck";
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZB.MOM.WW.LmxOpcUa.Host.Domain
|
||||
{
|
||||
/// <summary>
|
||||
@@ -10,4 +12,18 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Domain
|
||||
/// </summary>
|
||||
bool ValidateCredentials(string username, string password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extended interface for providers that can resolve application-level roles for authenticated users.
|
||||
/// When the auth provider implements this interface, OnImpersonateUser uses the returned roles
|
||||
/// to control write and alarm-ack permissions.
|
||||
/// </summary>
|
||||
public interface IRoleProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the set of application-level roles granted to the user.
|
||||
/// Known roles: "ReadOnly", "ReadWrite", "AlarmAck".
|
||||
/// </summary>
|
||||
IReadOnlyList<string> GetUserRoles(string username);
|
||||
}
|
||||
}
|
||||
|
||||
149
src/ZB.MOM.WW.LmxOpcUa.Host/Domain/LdapAuthenticationProvider.cs
Normal file
149
src/ZB.MOM.WW.LmxOpcUa.Host/Domain/LdapAuthenticationProvider.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices.Protocols;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Serilog;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
|
||||
|
||||
namespace ZB.MOM.WW.LmxOpcUa.Host.Domain
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates credentials via LDAP bind and resolves group membership to application roles.
|
||||
/// </summary>
|
||||
public class LdapAuthenticationProvider : IUserAuthenticationProvider, IRoleProvider
|
||||
{
|
||||
private static readonly ILogger Log = Serilog.Log.ForContext<LdapAuthenticationProvider>();
|
||||
|
||||
private readonly LdapConfiguration _config;
|
||||
private readonly Dictionary<string, string> _groupToRole;
|
||||
|
||||
public LdapAuthenticationProvider(LdapConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_groupToRole = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ config.ReadOnlyGroup, "ReadOnly" },
|
||||
{ config.ReadWriteGroup, "ReadWrite" },
|
||||
{ config.AlarmAckGroup, "AlarmAck" }
|
||||
};
|
||||
}
|
||||
|
||||
public bool ValidateCredentials(string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bindDn = _config.BindDnTemplate.Replace("{username}", username);
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.Bind(new NetworkCredential(bindDn, password));
|
||||
}
|
||||
Log.Debug("LDAP bind succeeded for {Username}", username);
|
||||
return true;
|
||||
}
|
||||
catch (LdapException ex)
|
||||
{
|
||||
Log.Debug("LDAP bind failed for {Username}: {Error}", username, ex.Message);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "LDAP error during credential validation for {Username}", username);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> GetUserRoles(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
// Bind with service account to search
|
||||
connection.Bind(new NetworkCredential(_config.ServiceAccountDn, _config.ServiceAccountPassword));
|
||||
|
||||
var request = new SearchRequest(
|
||||
_config.BaseDN,
|
||||
$"(cn={EscapeLdapFilter(username)})",
|
||||
SearchScope.Subtree,
|
||||
"memberOf");
|
||||
|
||||
var response = (SearchResponse)connection.SendRequest(request);
|
||||
|
||||
if (response.Entries.Count == 0)
|
||||
{
|
||||
Log.Warning("LDAP search returned no entries for {Username}", username);
|
||||
return new[] { "ReadOnly" }; // safe fallback
|
||||
}
|
||||
|
||||
var entry = response.Entries[0];
|
||||
var memberOf = entry.Attributes["memberOf"];
|
||||
if (memberOf == null || memberOf.Count == 0)
|
||||
{
|
||||
Log.Debug("No memberOf attributes for {Username}, defaulting to ReadOnly", username);
|
||||
return new[] { "ReadOnly" };
|
||||
}
|
||||
|
||||
var roles = new List<string>();
|
||||
for (int i = 0; i < memberOf.Count; i++)
|
||||
{
|
||||
var dn = memberOf[i]?.ToString() ?? "";
|
||||
// Extract the OU/CN from the memberOf DN (e.g., "ou=ReadWrite,ou=groups,dc=...")
|
||||
var groupName = ExtractGroupName(dn);
|
||||
if (groupName != null && _groupToRole.TryGetValue(groupName, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
}
|
||||
}
|
||||
|
||||
if (roles.Count == 0)
|
||||
{
|
||||
Log.Debug("No matching role groups for {Username}, defaulting to ReadOnly", username);
|
||||
roles.Add("ReadOnly");
|
||||
}
|
||||
|
||||
Log.Debug("LDAP roles for {Username}: [{Roles}]", username, string.Join(", ", roles));
|
||||
return roles;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to resolve LDAP roles for {Username}, defaulting to ReadOnly", username);
|
||||
return new[] { "ReadOnly" };
|
||||
}
|
||||
}
|
||||
|
||||
private LdapConnection CreateConnection()
|
||||
{
|
||||
var identifier = new LdapDirectoryIdentifier(_config.Host, _config.Port);
|
||||
var connection = new LdapConnection(identifier)
|
||||
{
|
||||
AuthType = AuthType.Basic,
|
||||
Timeout = TimeSpan.FromSeconds(_config.TimeoutSeconds)
|
||||
};
|
||||
connection.SessionOptions.ProtocolVersion = 3;
|
||||
return connection;
|
||||
}
|
||||
|
||||
private static string? ExtractGroupName(string dn)
|
||||
{
|
||||
// Parse "ou=ReadWrite,ou=groups,dc=..." or "cn=ReadWrite,..."
|
||||
if (string.IsNullOrEmpty(dn)) return null;
|
||||
var parts = dn.Split(',');
|
||||
if (parts.Length == 0) return null;
|
||||
var first = parts[0].Trim();
|
||||
var eqIdx = first.IndexOf('=');
|
||||
return eqIdx >= 0 ? first.Substring(eqIdx + 1) : null;
|
||||
}
|
||||
|
||||
private static string EscapeLdapFilter(string input)
|
||||
{
|
||||
return input
|
||||
.Replace("\\", "\\5c")
|
||||
.Replace("*", "\\2a")
|
||||
.Replace("(", "\\28")
|
||||
.Replace(")", "\\29")
|
||||
.Replace("\0", "\\00");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Opc.Ua;
|
||||
@@ -29,6 +30,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
private readonly RedundancyConfiguration _redundancyConfig;
|
||||
private readonly string? _applicationUri;
|
||||
private readonly ServiceLevelCalculator _serviceLevelCalculator = new ServiceLevelCalculator();
|
||||
private readonly ConcurrentDictionary<string, IReadOnlyList<string>> _userAppRoles = new ConcurrentDictionary<string, IReadOnlyList<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
private LmxNodeManager? _nodeManager;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +38,22 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
/// </summary>
|
||||
public LmxNodeManager? NodeManager => _nodeManager;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the application-level roles cached for the given username, or null if no roles are stored.
|
||||
/// Called by LmxNodeManager to enforce per-role write and alarm-ack permissions.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? GetUserAppRoles(string? username)
|
||||
{
|
||||
if (username != null && _userAppRoles.TryGetValue(username, out var roles))
|
||||
return roles;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether LDAP role-based access control is active.
|
||||
/// </summary>
|
||||
public bool LdapRolesEnabled => _authProvider is Domain.IRoleProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of active OPC UA sessions currently connected to the server.
|
||||
/// </summary>
|
||||
@@ -69,7 +87,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
{
|
||||
var namespaceUri = $"urn:{_galaxyName}:LmxOpcUa";
|
||||
_nodeManager = new LmxNodeManager(server, configuration, namespaceUri, _mxAccessClient, _metrics,
|
||||
_historianDataSource, _alarmTrackingEnabled, _authConfig.AnonymousCanWrite);
|
||||
_historianDataSource, _alarmTrackingEnabled, _authConfig.AnonymousCanWrite,
|
||||
LdapRolesEnabled ? (Func<string?, IReadOnlyList<string>?>)GetUserAppRoles : null,
|
||||
LdapRolesEnabled);
|
||||
|
||||
var nodeManagers = new List<INodeManager> { _nodeManager };
|
||||
return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray());
|
||||
@@ -206,10 +226,23 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Invalid username or password");
|
||||
}
|
||||
|
||||
var roles = new List<Role> { Role.AuthenticatedUser };
|
||||
|
||||
// Resolve LDAP-based roles when the provider supports it
|
||||
if (_authProvider is Domain.IRoleProvider roleProvider)
|
||||
{
|
||||
var appRoles = roleProvider.GetUserRoles(userNameToken.UserName);
|
||||
_userAppRoles[userNameToken.UserName] = appRoles;
|
||||
Log.Information("User {Username} authenticated with roles [{Roles}]",
|
||||
userNameToken.UserName, string.Join(", ", appRoles));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("User {Username} authenticated", userNameToken.UserName);
|
||||
}
|
||||
|
||||
args.Identity = new RoleBasedIdentity(
|
||||
new UserIdentity(userNameToken),
|
||||
new List<Role> { Role.AuthenticatedUser });
|
||||
Log.Information("User {Username} authenticated", userNameToken.UserName);
|
||||
new UserIdentity(userNameToken), roles);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
var policies = new UserTokenPolicyCollection();
|
||||
if (_authConfig.AllowAnonymous)
|
||||
policies.Add(new UserTokenPolicy(UserTokenType.Anonymous));
|
||||
if (_authConfig.Users.Count > 0)
|
||||
if (_authConfig.Users.Count > 0 || _authConfig.Ldap.Enabled)
|
||||
policies.Add(new UserTokenPolicy(UserTokenType.UserName));
|
||||
|
||||
if (policies.Count == 0)
|
||||
|
||||
@@ -161,9 +161,21 @@ namespace ZB.MOM.WW.LmxOpcUa.Host
|
||||
var historianDataSource = _config.Historian.Enabled
|
||||
? new Historian.HistorianDataSource(_config.Historian)
|
||||
: null;
|
||||
var authProvider = _config.Authentication.Users.Count > 0
|
||||
? new Domain.ConfigUserAuthenticationProvider(_config.Authentication.Users)
|
||||
: (Domain.IUserAuthenticationProvider?)null;
|
||||
Domain.IUserAuthenticationProvider? authProvider;
|
||||
if (_config.Authentication.Ldap.Enabled)
|
||||
{
|
||||
authProvider = new Domain.LdapAuthenticationProvider(_config.Authentication.Ldap);
|
||||
Log.Information("LDAP authentication enabled (server={Host}:{Port}, baseDN={BaseDN})",
|
||||
_config.Authentication.Ldap.Host, _config.Authentication.Ldap.Port, _config.Authentication.Ldap.BaseDN);
|
||||
}
|
||||
else if (_config.Authentication.Users.Count > 0)
|
||||
{
|
||||
authProvider = new Domain.ConfigUserAuthenticationProvider(_config.Authentication.Users);
|
||||
}
|
||||
else
|
||||
{
|
||||
authProvider = null;
|
||||
}
|
||||
_serverHost = new OpcUaServerHost(_config.OpcUa, effectiveMxClient, _metrics, historianDataSource,
|
||||
_config.Authentication, authProvider, _config.Security, _config.Redundancy);
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- LDAP authentication -->
|
||||
<Reference Include="System.DirectoryServices.Protocols" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- MXAccess COM interop -->
|
||||
<Reference Include="ArchestrA.MxAccess">
|
||||
|
||||
@@ -35,8 +35,21 @@
|
||||
},
|
||||
"Authentication": {
|
||||
"AllowAnonymous": true,
|
||||
"AnonymousCanWrite": true,
|
||||
"Users": []
|
||||
"AnonymousCanWrite": false,
|
||||
"Users": [],
|
||||
"Ldap": {
|
||||
"Enabled": false,
|
||||
"Host": "localhost",
|
||||
"Port": 3893,
|
||||
"BaseDN": "dc=lmxopcua,dc=local",
|
||||
"BindDnTemplate": "cn={username},dc=lmxopcua,dc=local",
|
||||
"ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local",
|
||||
"ServiceAccountPassword": "serviceaccount123",
|
||||
"TimeoutSeconds": 5,
|
||||
"ReadOnlyGroup": "ReadOnly",
|
||||
"ReadWriteGroup": "ReadWrite",
|
||||
"AlarmAckGroup": "AlarmAck"
|
||||
}
|
||||
},
|
||||
"Security": {
|
||||
"Profiles": ["None"],
|
||||
|
||||
Reference in New Issue
Block a user