/* ======================================================================== * Copyright (c) 2005-2025 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.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Opc.Ua.Gds.Server.Database; using Opc.Ua.Server; namespace Opc.Ua.Gds.Server { internal static class AuthorizationHelper { internal static List AuthenticatedUser { get; } = [Role.AuthenticatedUser]; internal static List DiscoveryAdmin { get; } = [GdsRole.DiscoveryAdmin]; /// /// Roles/privileges accepted by RegisterApplication /// per OPC 10000-12 §6.5.6: DiscoveryAdmin Role or /// ApplicationAdmin Privilege. /// internal static List DiscoveryAdminOrAppAdmin { get; } = [GdsRole.DiscoveryAdmin, GdsRole.ApplicationAdmin]; internal static List DiscoveryAdminOrSelfAdmin { get; } = [GdsRole.DiscoveryAdmin, GdsRole.ApplicationSelfAdmin]; /// /// Roles/privileges accepted by UpdateApplication / /// UnregisterApplication per OPC 10000-12 §6.5.7 / §6.5.8. /// internal static List DiscoveryAdminOrSelfAdminOrAppAdmin { get; } = [GdsRole.DiscoveryAdmin, GdsRole.ApplicationSelfAdmin, GdsRole.ApplicationAdmin]; internal static List AuthenticatedUserOrSelfAdmin { get; } = [Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin]; internal static List CertificateAuthorityAdminOrSelfAdmin { get; } = [GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin]; /// /// Roles/privileges accepted by certificate-management methods that /// permit ApplicationSelfAdmin / ApplicationAdmin /// (OPC 10000-12 §7.6.4 - §7.6.10). /// internal static List CertificateAuthorityAdminOrSelfAdminOrAppAdmin { get; } = [ GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin, GdsRole.ApplicationAdmin ]; internal static List CertificateAuthorityAdmin { get; } = [GdsRole.CertificateAuthorityAdmin]; /// /// Checks if the current session (context) has one of the requested /// roles. If or /// is included in /// , the is /// checked against the identity's bound application(s). /// /// The current . /// /// All allowed roles; may include /// and/or /// . /// /// /// When or /// is allowed this specifies /// the id of the Application entry to access. /// /// /// Thrown with when the /// caller lacks the required roles/privileges. /// public static void HasAuthorization( ISystemContext context, IEnumerable roles, [Optional] NodeId applicationId) { if (context != null) { var allowedRoles = roles.ToHashSet(); bool selfAdmin = allowedRoles.Remove(GdsRole.ApplicationSelfAdmin); bool appAdmin = allowedRoles.Remove(GdsRole.ApplicationAdmin); //if true access is allowed IUserIdentity? userIdentity = (context as ISessionSystemContext)?.UserIdentity; if (HasRole(userIdentity, allowedRoles, context.NamespaceUris)) { return; } if (selfAdmin) { //if true access to own application is allowed if (CheckSelfAdminPrivilege(userIdentity, applicationId)) { return; } } if (appAdmin && CheckApplicationAdminPrivilege(userIdentity, applicationId)) { return; } throw new ServiceResultException( StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} is required to call the method"); } } /// /// Checks if the current session (context) is allowed to access the /// trust list (has the CertificateAuthorityAdmin / /// SecurityAdmin Role, the ApplicationSelfAdmin /// Privilege, or the ApplicationAdmin Privilege). /// /// the current /// Certificate store Identifier needed to check for Application Self Admin privilege /// all supported cert types, needed to check for Application Self Admin privilege /// all registered applications , needed to check for Application Self Admin privilege /// public static void HasTrustListAccess( ISystemContext context, CertificateStoreIdentifier trustedStore, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) { var roles = new List { GdsRole.CertificateAuthorityAdmin, Role.SecurityAdmin }; IUserIdentity? userIdentity = (context as ISessionSystemContext)?.UserIdentity; if (HasRole(userIdentity, roles, context.NamespaceUris)) { return; } if (trustedStore != null && certTypeMap != null && applicationsDatabase != null && CheckSelfAdminPrivilegeForTrustList( userIdentity, trustedStore, certTypeMap, applicationsDatabase)) { return; } if (trustedStore != null && certTypeMap != null && applicationsDatabase != null && CheckApplicationAdminPrivilegeForTrustList( userIdentity, trustedStore, certTypeMap, applicationsDatabase)) { return; } throw new ServiceResultException( StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)}, ApplicationSelfAdmin Privilege, or ApplicationAdmin Privilege is required to use the TrustList"); } /// /// Checks if current session (context) is connected using an /// authenticated secure channel ( /// or ). /// /// the current /// /// When true the channel must use /// ; otherwise /// is also accepted. /// /// /// Thrown with /// per OPC 10000-12 method result tables. /// public static void HasAuthenticatedSecureChannel( ISystemContext context, bool requireEncryption = false) { // Extract the OperationContext from whichever concrete // system context type is in use. ServerSystemContext // extends SessionSystemContext (server path); SystemContext // is the standalone variant (Opc.Ua.Types). OperationContext? operationContext = context switch { SessionSystemContext sc => sc.OperationContext as OperationContext, SystemContext sc => sc.OperationContext as OperationContext, _ => null } ?? throw new ServiceResultException( StatusCodes.BadSecurityModeInsufficient, "Unable to verify secure channel requirements."); MessageSecurityMode securityMode = operationContext .ChannelContext? .EndpointDescription? .SecurityMode ?? MessageSecurityMode.Invalid; bool ok = requireEncryption ? securityMode == MessageSecurityMode.SignAndEncrypt : securityMode is MessageSecurityMode.Sign or MessageSecurityMode.SignAndEncrypt; if (!ok) { throw new ServiceResultException( StatusCodes.BadSecurityModeInsufficient, requireEncryption ? "Method has to be called from an encrypted secure channel." : "Method has to be called from an authenticated secure channel."); } } private static bool HasRole(IUserIdentity? userIdentity, IEnumerable roles, NamespaceTable namespaces) { if (userIdentity != null && userIdentity.TokenType != UserTokenType.Anonymous) { foreach (Role role in roles) { if (!role.RoleId.IsNull && userIdentity.GrantedRoleIds.Contains( ExpandedNodeId.ToNodeId(role.RoleId, namespaces))) { return true; } } } return false; } private static bool CheckSelfAdminPrivilege( IUserIdentity? userIdentity, NodeId applicationId) { if (applicationId.IsNull) { return false; } if (userIdentity is GdsRoleBasedIdentity identity) { //self Admin only has access to own application if (identity.ApplicationId == applicationId) { return true; } } return false; } /// /// Checks whether the identity carries the /// privilege and (when /// is supplied) administers it. /// /// /// Per OPC 10000-12 §7.2 the ApplicationAdmin Privilege authorizes a /// configurable set of Applications. When no /// is supplied (e.g. for RegisterApplication) the privilege /// alone is sufficient. /// private static bool CheckApplicationAdminPrivilege( IUserIdentity? userIdentity, NodeId applicationId) { if (userIdentity is not GdsRoleBasedIdentity identity) { return false; } // The ApplicationAdmin privilege is asserted by the impersonation // logic via Role.ApplicationAdmin and a non-empty // AdministeredApplicationIds list. if (identity.AdministeredApplicationIds.Count == 0) { return false; } // No specific application: privilege alone is sufficient // (e.g. RegisterApplication before the appId exists). if (applicationId.IsNull) { return true; } for (int i = 0; i < identity.AdministeredApplicationIds.Count; i++) { if (identity.AdministeredApplicationIds[i] == applicationId) { return true; } } return false; } private static bool CheckSelfAdminPrivilegeForTrustList( IUserIdentity? userIdentity, CertificateStoreIdentifier trustedStore, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) { if (userIdentity is GdsRoleBasedIdentity identity && !identity.ApplicationId.IsNull) { foreach (string certType in certTypeMap.Values) { applicationsDatabase.GetApplicationTrustLists( identity.ApplicationId, certType, out string? trustListId); if (trustedStore.StorePath == trustListId) { return true; } } } return false; } private static bool CheckApplicationAdminPrivilegeForTrustList( IUserIdentity? userIdentity, CertificateStoreIdentifier trustedStore, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) { if (userIdentity is not GdsRoleBasedIdentity identity || identity.AdministeredApplicationIds.Count == 0) { return false; } for (int i = 0; i < identity.AdministeredApplicationIds.Count; i++) { NodeId appId = identity.AdministeredApplicationIds[i]; foreach (string certType in certTypeMap.Values) { applicationsDatabase.GetApplicationTrustLists( appId, certType, out string? trustListId); if (trustedStore.StorePath == trustListId) { return true; } } } return false; } } }