From 4f43f68cd944dc39edc2aed6aa0ad4e2e265ee83 Mon Sep 17 00:00:00 2001 From: romanett Date: Tue, 16 Jan 2024 22:24:51 +0100 Subject: [PATCH] User Management Improvements for Standard Server (Support Well Known Roles) (#2444) - move IUserDatabase + RoleBased Identity into standard Server - subclass SystemConfigurationIdentity from RoleBased Identity for backward compatibility - add OPC UA Spec Defined Roles to the RoleBasedIdentity --- .../ReferenceServer/ReferenceServer.cs | 26 +- .../ApplicationsNodeManager.cs | 2 +- .../{RoleBasedIdentity.cs => GdsRole.cs} | 84 +------ .../GlobalDiscoverySampleServer.cs | 46 ++-- .../Configuration/ConfigurationNodeManager.cs | 60 +---- .../Diagnostics/DiagnosticsNodeManager.cs | 3 +- .../RoleBasedIdentity.cs | 226 ++++++++++++++++++ .../UserDatabase/IUserDatabase.cs} | 19 +- .../UserDatabase/JsonUserDatabase.cs} | 14 +- .../UserDatabase/LinqUserDatabase.cs} | 34 ++- .../GlobalDiscoveryTestServer.cs | 3 +- .../LinqUserDatabaseTests.cs} | 44 ++-- 12 files changed, 340 insertions(+), 221 deletions(-) rename Libraries/Opc.Ua.Gds.Server.Common/{RoleBasedIdentity.cs => GdsRole.cs} (50%) create mode 100644 Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs rename Libraries/{Opc.Ua.Gds.Server.Common/IUsersDatabase.cs => Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/IUserDatabase.cs} (86%) rename Libraries/{Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs => Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/JsonUserDatabase.cs} (88%) rename Libraries/{Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs => Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/LinqUserDatabase.cs} (91%) rename Tests/{Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs => Opc.Ua.Server.Tests/LinqUserDatabaseTests.cs} (63%) diff --git a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs index 5ae611c0a9..353a848d8a 100644 --- a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs +++ b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceServer.cs @@ -237,16 +237,6 @@ namespace Quickstarts.ReferenceServer Utils.LogInfo(Utils.TraceMasks.Security, "Username Token Accepted: {0}", args.Identity?.DisplayName); - // set AuthenticatedUser role for accepted user/password authentication - args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser); - - if (args.Identity is SystemConfigurationIdentity) - { - // set ConfigureAdmin role for user with permission to configure server - args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_ConfigureAdmin); - args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_SecurityAdmin); - } - return; } @@ -256,11 +246,10 @@ namespace Quickstarts.ReferenceServer if (x509Token != null) { VerifyUserTokenCertificate(x509Token.Certificate); - args.Identity = new UserIdentity(x509Token); - Utils.LogInfo(Utils.TraceMasks.Security, "X509 Token Accepted: {0}", args.Identity?.DisplayName); - // set AuthenticatedUser role for accepted certificate authentication - args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser); + args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), + new List() { Role.AuthenticatedUser }); + Utils.LogInfo(Utils.TraceMasks.Security, "X509 Token Accepted: {0}", args.Identity?.DisplayName); return; } @@ -280,9 +269,8 @@ namespace Quickstarts.ReferenceServer if (args.NewIdentity is AnonymousIdentityToken || args.NewIdentity == null) { // allow anonymous authentication and set Anonymous role for this authentication - args.Identity = new UserIdentity(); - args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_Anonymous); - + args.Identity = new RoleBasedIdentity(new UserIdentity(), + new List() { Role.Anonymous }); return; } @@ -336,8 +324,8 @@ namespace Quickstarts.ReferenceServer LoadServerProperties().ProductUri, new LocalizedText(info))); } - - return new UserIdentity(userNameToken); + return new RoleBasedIdentity(new UserIdentity(userNameToken), + new List() { Role.AuthenticatedUser}); } /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index f3b3bb4bcb..2b61e45c94 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -168,7 +168,7 @@ namespace Opc.Ua.Gds.Server { RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - if ((identity == null) || (identity.Role != GdsRole.ApplicationAdmin)) + if ((identity == null) || (!identity.Roles.Contains(GdsRole.ApplicationAdmin))) { throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs similarity index 50% rename from Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs rename to Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs index e8b7f03b08..89279b2514 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs @@ -27,92 +27,32 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System.Xml; +using Opc.Ua.Server; namespace Opc.Ua.Gds.Server { /// /// The supported roles in a GDS server. /// - public enum GdsRole + public class GdsRole : Role { /// /// The GDS application Administrator. /// - ApplicationAdmin, + public static Role ApplicationAdmin { get; } = new Role(NodeId.Null, "ApplicationAdmin"); + + /// + /// This Role grants rights to register, update and unregister any OPC UA Application. + /// + public static Role DiscoveryAdmin { get; } = new Role( NodeId.Null, "DiscoveryAdmin"); /// /// The GDS application user. /// - ApplicationUser - } + public static Role ApplicationUser { get; } = new Role(NodeId.Null, "ApplicationUser"); - /// - /// The role based identity for a GDS server. - /// - public class RoleBasedIdentity : IUserIdentity - { - private IUserIdentity m_identity; - private GdsRole m_role; - - /// - /// Initialize the role based identity. - /// - public RoleBasedIdentity(IUserIdentity identity, GdsRole role) - { - m_identity = identity; - m_role = role; - } - - /// - public NodeIdCollection GrantedRoleIds - { - get { return m_identity.GrantedRoleIds; } - set { m_identity.GrantedRoleIds = value; } - } - - /// - /// The role in the context of a Gds. - /// - public GdsRole Role - { - get { return m_role; } - } - - /// - public string DisplayName - { - get { return m_identity.DisplayName; } - } - - /// - public string PolicyId - { - get { return m_identity.PolicyId; } - } - - /// - public UserTokenType TokenType - { - get { return m_identity.TokenType; } - } - - /// - public XmlQualifiedName IssuedTokenType - { - get { return m_identity.IssuedTokenType; } - } - - /// - public bool SupportsSignatures - { - get { return m_identity.SupportsSignatures; } - } - - /// - public UserIdentityToken GetIdentityToken() - { - return m_identity.GetIdentityToken(); - } + public GdsRole(NodeId roleId, string name): + base(roleId, name) + {} } } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index e73541b36b..927afc1821 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -33,6 +33,8 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using Opc.Ua.Server; using Opc.Ua.Gds.Server.Database; +using Opc.Ua.Server.UserDatabase; +using System.Linq; namespace Opc.Ua.Gds.Server { @@ -54,7 +56,7 @@ namespace Opc.Ua.Gds.Server IApplicationsDatabase database, ICertificateRequest request, ICertificateGroup certificateGroup, - IUsersDatabase userDatabase, + IUserDatabase userDatabase, bool autoApprove = true, bool createStandardUsers = true ) @@ -206,24 +208,21 @@ namespace Opc.Ua.Gds.Server Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName); return; } - switch (m_userDatabase.GetUserRole(userNameToken.UserName)) + IEnumerable roles = m_userDatabase.GetUserRoles(userNameToken.UserName); + //GdsAdmin + if (roles.Contains(GdsRole.ApplicationAdmin)) { - // GDS administrator - case GdsRole.ApplicationAdmin: - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationAdmin); - Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } - - // GDS user - case GdsRole.ApplicationUser: - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationUser); - Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); - return; - } + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationAdmin }); + Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); + return; } + //GdsUser + if (roles.Contains(GdsRole.ApplicationUser)) + { + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationUser }); + Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); + return; + } } } @@ -231,14 +230,13 @@ namespace Opc.Ua.Gds.Server X509IdentityToken x509Token = args.NewIdentity as X509IdentityToken; if (x509Token != null) { - GdsRole role = GdsRole.ApplicationUser; VerifyUserTokenCertificate(x509Token.Certificate); // todo: is cert listed in admin list? then // role = GdsRole.ApplicationAdmin; - Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, role.ToString()); - args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role); + Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, GdsRole.ApplicationUser); + args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), new List { GdsRole.ApplicationUser }); return; } } @@ -297,9 +295,9 @@ namespace Opc.Ua.Gds.Server /// private void RegisterDefaultUsers() { - m_userDatabase.CreateUser("sysadmin", "demo", GdsRole.ApplicationAdmin); - m_userDatabase.CreateUser("appadmin", "demo", GdsRole.ApplicationAdmin); - m_userDatabase.CreateUser("appuser", "demo", GdsRole.ApplicationUser); + m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.ApplicationAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); + m_userDatabase.CreateUser("appadmin", "demo", new List { GdsRole.ApplicationAdmin }); + m_userDatabase.CreateUser("appuser", "demo", new List { GdsRole.ApplicationUser }); } #endregion @@ -308,7 +306,7 @@ namespace Opc.Ua.Gds.Server private IApplicationsDatabase m_database = null; private ICertificateRequest m_request = null; private ICertificateGroup m_certificateGroup = null; - private IUsersDatabase m_userDatabase = null; + private IUserDatabase m_userDatabase = null; private bool m_autoApprove; private bool m_createStandardUsers; #endregion diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index d30451e54b..1640a59b2c 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -36,67 +36,20 @@ using System.Xml; namespace Opc.Ua.Server { + /// /// Priviledged identity which can access the system configuration. /// - public class SystemConfigurationIdentity : IUserIdentity + public class SystemConfigurationIdentity : RoleBasedIdentity { - private IUserIdentity m_identity; - /// /// Create a user identity with the priviledge /// to modify the system configuration. /// /// The user identity. public SystemConfigurationIdentity(IUserIdentity identity) - { - m_identity = identity; + :base(identity, new List {Role.SecurityAdmin, Role.ConfigureAdmin }){ } - - #region IUserIdentity - /// - public string DisplayName - { - get { return m_identity.DisplayName; } - } - - /// - public string PolicyId - { - get { return m_identity.PolicyId; } - } - - /// - public UserTokenType TokenType - { - get { return m_identity.TokenType; } - } - - /// - public XmlQualifiedName IssuedTokenType - { - get { return m_identity.IssuedTokenType; } - } - - /// - public bool SupportsSignatures - { - get { return m_identity.SupportsSignatures; } - } - - /// - public NodeIdCollection GrantedRoleIds - { - get { return m_identity.GrantedRoleIds; } - set { m_identity.GrantedRoleIds = value; } - } - - /// - public UserIdentityToken GetIdentityToken() - { - return m_identity.GetIdentityToken(); - } - #endregion } /// @@ -355,10 +308,11 @@ namespace Opc.Ua.Server } // allow access to system configuration only through special identity - SystemConfigurationIdentity user = context.UserIdentity as SystemConfigurationIdentity; - if (user == null || user.TokenType == UserTokenType.Anonymous) + IUserIdentity user = context.UserIdentity as RoleBasedIdentity; + if (user == null || user.TokenType == UserTokenType.Anonymous || + !user.GrantedRoleIds.Contains(ObjectIds.WellKnownRole_SecurityAdmin)) { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "System Configuration Administrator access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Security Admin Role required."); } } diff --git a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs index c1e063316e..a78e8bb30a 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs @@ -1547,7 +1547,8 @@ namespace Opc.Ua.Server return false; } - SystemConfigurationIdentity user = context.UserIdentity as SystemConfigurationIdentity; + IUserIdentity user = context.UserIdentity as RoleBasedIdentity; + if (user == null || user.TokenType == UserTokenType.Anonymous || !user.GrantedRoleIds.Contains(ObjectIds.WellKnownRole_SecurityAdmin)) diff --git a/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs new file mode 100644 index 0000000000..ac6550f81b --- /dev/null +++ b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs @@ -0,0 +1,226 @@ +/* ======================================================================== + * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Opc.Ua.Server +{ + /// + /// The well known roles in a server. + /// https://reference.opcfoundation.org/Core/Part3/v105/docs/4.9.2 + /// + public class Role : IEquatable + { + /// + /// The Role is allowed to browse and read non-security related Nodes only in the Server Object and all type Nodes. + /// + public static Role Anonymous { get; } = new Role(ObjectIds.WellKnownRole_Anonymous, BrowseNames.WellKnownRole_Anonymous); + + /// + /// The Role is allowed to browse and read non-security related Nodes. + /// + public static Role AuthenticatedUser { get; } = new Role(ObjectIds.WellKnownRole_AuthenticatedUser, BrowseNames.WellKnownRole_AuthenticatedUser); + + /// + /// The Role is allowed to browse, read live data, read historical data/events or subscribe to data/events. + /// + public static Role Observer { get; } = new Role(ObjectIds.WellKnownRole_Observer, BrowseNames.WellKnownRole_Observer); + + /// + /// The Role is allowed to browse, read live data, read historical data/events or subscribe to data/events. + /// In addition, the Session is allowed to write some live data and call some Methods. + /// + public static Role Operator { get; } = new Role(ObjectIds.WellKnownRole_Operator, BrowseNames.WellKnownRole_Operator); + + /// + /// The Role is allowed to browse, read/write configuration data, read historical data/events, call Methods or subscribe to data/events. + /// + public static Role Engineer { get; } = new Role(ObjectIds.WellKnownRole_Engineer, BrowseNames.WellKnownRole_Engineer); + + /// + /// The Role is allowed to browse, read live data, read historical data/events, call Methods or subscribe to data/events. + /// + public static Role Supervisor { get; } = new Role(ObjectIds.WellKnownRole_Supervisor, BrowseNames.WellKnownRole_Supervisor); + + /// + /// The Role is allowed to change the non-security related configuration settings. + /// + public static Role ConfigureAdmin { get; } = new Role(ObjectIds.WellKnownRole_ConfigureAdmin, BrowseNames.WellKnownRole_ConfigureAdmin); + + /// + /// The Role is allowed to change security related settings. + /// + public static Role SecurityAdmin { get; } = new Role(ObjectIds.WellKnownRole_SecurityAdmin, BrowseNames.WellKnownRole_SecurityAdmin); + + /// + /// Constructor for new Role + /// + /// NodeId of the Role, used for WellKnownRoles + /// Name of the Role + public Role(NodeId roleId, string name) + { + RoleId = roleId; + Name = name; + } + /// + /// the NodeId of the role + /// + public NodeId RoleId { get; private set; } + + /// + /// the name of the role + /// + public string Name { get; private set; } + #region value equality + /// + public bool Equals(Role other) + { + if (other is null) + { + return false; + } + if (Object.ReferenceEquals(this, other)) + { + return true; + } + if (this.GetType() != other.GetType()) + { + return false; + } + return (Name == other.Name) && (RoleId == other.RoleId); + } + /// + public override bool Equals(object obj) + { + return Equals(obj as Role); + } + /// + public override int GetHashCode() + { + return (Name, RoleId).GetHashCode(); + } + /// + public static bool operator ==(Role lhs, Role rhs) + { + if (lhs is null) + { + if (rhs is null) + { + return true; + } + + // Only the left side is null. + return false; + } + // Equals handles case of null on right side. + return lhs.Equals(rhs); + } + /// + public static bool operator !=(Role lhs, Role rhs) => !(lhs == rhs); + #endregion + } + + /// + /// The role based identity for a server. + /// + public class RoleBasedIdentity : IUserIdentity + { + private IUserIdentity m_identity; + private IEnumerable m_roles; + + /// + /// Initialize the role based identity. + /// + public RoleBasedIdentity(IUserIdentity identity, IEnumerable roles) + { + m_identity = identity; + m_roles = roles; + foreach (Role role in roles) + { + if (!(role.RoleId?.IsNullNodeId ?? true)) + { + GrantedRoleIds.Add(role.RoleId); + } + } + } + + /// + public NodeIdCollection GrantedRoleIds + { + get { return m_identity.GrantedRoleIds; } + set { m_identity.GrantedRoleIds = value; } + } + + /// + /// The role in the context of a server. + /// + public IEnumerable Roles + { + get { return m_roles; } + } + + /// + public string DisplayName + { + get { return m_identity.DisplayName; } + } + + /// + public string PolicyId + { + get { return m_identity.PolicyId; } + } + + /// + public UserTokenType TokenType + { + get { return m_identity.TokenType; } + } + + /// + public XmlQualifiedName IssuedTokenType + { + get { return m_identity.IssuedTokenType; } + } + + /// + public bool SupportsSignatures + { + get { return m_identity.SupportsSignatures; } + } + + /// + public UserIdentityToken GetIdentityToken() + { + return m_identity.GetIdentityToken(); + } + } +} diff --git a/Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/IUserDatabase.cs similarity index 86% rename from Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs rename to Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/IUserDatabase.cs index 2d69b2e7c3..dd4ff5c58d 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/IUsersDatabase.cs +++ b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/IUserDatabase.cs @@ -28,29 +28,30 @@ * ======================================================================*/ using System; +using System.Collections.Generic; using System.Security; -namespace Opc.Ua.Gds.Server.Database +namespace Opc.Ua.Server.UserDatabase { /// - /// An abstract interface to the user database which stores logins + /// An abstract interface to the user database which stores logins with associated Roles /// - public interface IUsersDatabase + public interface IUserDatabase { /// /// Initialize User Database /// void Initialize(); /// - /// Register new GDS User + /// Register new user /// /// the username /// the password - /// the GdsRole of the new User + /// the role of the new user /// true if registered sucessfull - bool CreateUser(string userName, string password, GdsRole role); + bool CreateUser(string userName, string password, IEnumerable roles); /// - /// Delete existring GDS user + /// Delete existring user /// /// the user to delete /// true if deleted sucessfully @@ -66,9 +67,9 @@ namespace Opc.Ua.Gds.Server.Database /// returns the Role of the provided user /// /// - /// the GdsRole of the provided users + /// the Role of the provided users /// When the user is not found - GdsRole GetUserRole(string userName); + IEnumerable GetUserRoles(string userName); /// /// changes the password of an existing users /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/JsonUserDatabase.cs similarity index 88% rename from Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs rename to Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/JsonUserDatabase.cs index d338aeb6fd..9c9a621c8f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/JsonUsersDatabase.cs +++ b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/JsonUserDatabase.cs @@ -31,21 +31,21 @@ using System; using System.IO; using Newtonsoft.Json; -namespace Opc.Ua.Gds.Server.Database.Linq +namespace Opc.Ua.Server.UserDatabase { /// - /// A GDS database with JSON storage. + /// A user database with JSON storage. /// /// /// This db is good for testing but not for production use. /// - public class JsonUsersDatabase : LinQUsersDatabase + public class JsonUserDatabase : LinqUserDatabase { #region Constructors /// /// Create a JSON database. /// - public JsonUsersDatabase(string fileName) + public JsonUserDatabase(string fileName) { m_fileName = fileName; } @@ -53,7 +53,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq /// /// Load the JSON application database. /// - static public JsonUsersDatabase Load(string fileName) + static public JsonUserDatabase Load(string fileName) { if (fileName == null) throw new ArgumentNullException(nameof(fileName)); try @@ -61,7 +61,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq if (File.Exists(fileName)) { string json = File.ReadAllText(fileName); - JsonUsersDatabase db = JsonConvert.DeserializeObject(json); + JsonUserDatabase db = JsonConvert.DeserializeObject(json); db.FileName = fileName; return db; } @@ -70,7 +70,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq { } - return new JsonUsersDatabase(fileName); + return new JsonUserDatabase(fileName); } #endregion diff --git a/Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/LinqUserDatabase.cs similarity index 91% rename from Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs rename to Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/LinqUserDatabase.cs index 8fbb27c0c4..9c5a5a1f61 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/LinqUsersDatabase.cs +++ b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/UserDatabase/LinqUserDatabase.cs @@ -31,31 +31,40 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Cryptography; using Newtonsoft.Json; using static System.Net.Mime.MediaTypeNames; -namespace Opc.Ua.Gds.Server.Database.Linq +namespace Opc.Ua.Server.UserDatabase { [Serializable] class User { + [JsonRequired] public Guid ID { get; set; } public string UserName { get; set; } public string Hash { get; set; } - public GdsRole GdsRole { get; set; } + public IEnumerable Roles { get; set; } } + /// + /// Implementation of a Serializable User Database using LINQ for querying users + /// [Serializable] - public class LinQUsersDatabase : IUsersDatabase + public class LinqUserDatabase : IUserDatabase { #region IUsersDatabase + /// + /// initializes the collection the users database is working with + /// public virtual void Initialize() { } - public bool CreateUser(string userName, string password, GdsRole role) + /// + public bool CreateUser(string userName, string password, IEnumerable roles) { if (string.IsNullOrEmpty(userName)) { @@ -73,7 +82,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq string hash = Hash(password); - var user = new User { UserName = userName, Hash = hash, GdsRole = role }; + var user = new User { UserName = userName, Hash = hash, Roles = roles }; Users.Add(user); @@ -81,7 +90,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq return true; } - + /// public bool DeleteUser(string userName) { if (string.IsNullOrEmpty(userName)) @@ -98,7 +107,7 @@ namespace Opc.Ua.Gds.Server.Database.Linq Users.Remove(user); return true; } - + /// public bool CheckCredentials(string userName, string password) { if (string.IsNullOrEmpty(userName)) @@ -119,8 +128,8 @@ namespace Opc.Ua.Gds.Server.Database.Linq return Check(user.Hash, password); } - - public GdsRole GetUserRole(string userName) + /// + public IEnumerable GetUserRoles(string userName) { if (string.IsNullOrEmpty(userName)) { @@ -133,9 +142,9 @@ namespace Opc.Ua.Gds.Server.Database.Linq throw new ArgumentException("No user found with the UserName " + userName); } - return user.GdsRole; + return user.Roles; } - + /// public bool ChangePassword(string userName, string oldPassword, string newPassword) { if (string.IsNullOrEmpty(userName)) @@ -169,6 +178,9 @@ namespace Opc.Ua.Gds.Server.Database.Linq #endregion #region Public Members + /// + /// Persists the changes to the users database + /// public virtual void Save() { } diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index 1b727bdc77..af862722c0 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -33,6 +33,7 @@ using System.Threading.Tasks; using Opc.Ua.Configuration; using Opc.Ua.Gds.Server; using Opc.Ua.Gds.Server.Database.Linq; +using Opc.Ua.Server.UserDatabase; namespace Opc.Ua.Gds.Tests { @@ -119,7 +120,7 @@ namespace Opc.Ua.Gds.Tests } var applicationsDatabase = JsonApplicationsDatabase.Load(databaseStorePath); - var usersDatabase = JsonUsersDatabase.Load(usersDatabaseStorePath); + var usersDatabase = JsonUserDatabase.Load(usersDatabaseStorePath); // start the server. m_server = new GlobalDiscoverySampleServer( diff --git a/Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs b/Tests/Opc.Ua.Server.Tests/LinqUserDatabaseTests.cs similarity index 63% rename from Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs rename to Tests/Opc.Ua.Server.Tests/LinqUserDatabaseTests.cs index 6e29de6d88..fa7a552ead 100644 --- a/Tests/Opc.Ua.Gds.Tests/LinqUsersDatabaseTests.cs +++ b/Tests/Opc.Ua.Server.Tests/LinqUserDatabaseTests.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; -using Opc.Ua.Gds.Server.Database.Linq; +using Opc.Ua.Server; +using Opc.Ua.Server.UserDatabase; -namespace Opc.Ua.Gds.Tests +namespace Opc.Ua.Server.Tests { - [TestFixture, Category("GDS")] + [TestFixture, Category("Server")] [SetCulture("en-us"), SetUICulture("en-us")] [Parallelizable] - internal class LinqUsersDatabaseTests + internal class LinqUserDatabaseTests { #region Test Methods @@ -19,21 +17,21 @@ namespace Opc.Ua.Gds.Tests public void CreateInvalidUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - + var usersDb = new LinqUserDatabase(); + //Act+ Assert Assert.Throws( - () => usersDb.CreateUser("", "PW", Server.GdsRole.ApplicationAdmin)); + () => usersDb.CreateUser("", "PW", new List { Role.AuthenticatedUser })); Assert.Throws( - () => usersDb.CreateUser("Name", "", Server.GdsRole.ApplicationAdmin)); + () => usersDb.CreateUser("Name", "", new List { Role.AuthenticatedUser })); } [Test] public void DeleteExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.DeleteUser("TestUser"); //Assert @@ -44,8 +42,8 @@ namespace Opc.Ua.Gds.Tests public void DeleteNonExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.DeleteUser("NoTestUser"); //Assert @@ -56,8 +54,8 @@ namespace Opc.Ua.Gds.Tests public void ChangePwOfExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.ChangePassword("TestUser", "PW", "newPW"); var login = usersDb.CheckCredentials("TestUser", "newPW"); @@ -72,8 +70,8 @@ namespace Opc.Ua.Gds.Tests public void ChangePwOfNonExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.DeleteUser("NoTestUser"); //Assert @@ -84,8 +82,8 @@ namespace Opc.Ua.Gds.Tests public void CheckPWofExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.CheckCredentials("TestUser", "PW"); var loginWrongPw = usersDb.CheckCredentials("TestUser", "newPW"); @@ -98,8 +96,8 @@ namespace Opc.Ua.Gds.Tests public void CheckPWofNonExistingUser() { //Arrrange - var usersDb = new LinQUsersDatabase(); - usersDb.CreateUser("TestUser", "PW", Server.GdsRole.ApplicationAdmin); + var usersDb = new LinqUserDatabase(); + usersDb.CreateUser("TestUser", "PW", new List { Role.AuthenticatedUser }); //Act var result = usersDb.CheckCredentials("NoTestUser", "PW"); //Assert