/* ======================================================================== * Copyright (c) 2005-2026 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.Threading; using System.Threading.Tasks; namespace Opc.Ua.Identity { /// /// Default implementation. /// /// /// /// Authenticators are dispatched in registration order: this /// preserves predictable precedence when more than one /// authenticator claims the same /// (e.g. two /// different JWT authenticators for two different issuers — the /// first to or /// wins; a /// moves on). /// /// public sealed class ServerIdentityRegistry : IServerIdentityRegistry { /// /// Creates an empty registry. /// public ServerIdentityRegistry() { } /// /// Creates a registry pre-populated with the supplied /// authenticators. /// public ServerIdentityRegistry(params IUserTokenAuthenticator[] authenticators) { if (authenticators == null) { throw new ArgumentNullException(nameof(authenticators)); } foreach (IUserTokenAuthenticator authenticator in authenticators) { Register(authenticator); } } /// public void Register(IUserTokenAuthenticator authenticator) { if (authenticator == null) { throw new ArgumentNullException(nameof(authenticator)); } string key = Key(authenticator.TokenType, authenticator.IssuedTokenProfileUri); lock (m_lock) { if (m_byKey.TryGetValue(key, out IUserTokenAuthenticator? existing)) { int index = m_order.IndexOf(existing); if (index >= 0) { m_order[index] = authenticator; } else { m_order.Add(authenticator); } } else { m_order.Add(authenticator); } m_byKey[key] = authenticator; } } /// public bool Unregister(IUserTokenAuthenticator authenticator) { if (authenticator == null) { throw new ArgumentNullException(nameof(authenticator)); } string key = Key(authenticator.TokenType, authenticator.IssuedTokenProfileUri); lock (m_lock) { if (!m_byKey.TryGetValue(key, out IUserTokenAuthenticator? existing) || !ReferenceEquals(existing, authenticator)) { return false; } m_byKey.Remove(key); m_order.Remove(authenticator); return true; } } /// public void RegisterAugmenter(IIdentityAugmenter augmenter) { if (augmenter == null) { throw new ArgumentNullException(nameof(augmenter)); } lock (m_lock) { m_augmenters.Add(augmenter); } } /// public bool UnregisterAugmenter(IIdentityAugmenter augmenter) { if (augmenter == null) { throw new ArgumentNullException(nameof(augmenter)); } lock (m_lock) { return m_augmenters.Remove(augmenter); } } /// public async ValueTask AuthenticateAsync( AuthenticationContext context, CancellationToken ct = default) { IUserTokenAuthenticator[] snapshot; lock (m_lock) { snapshot = [.. m_order]; } UserTokenType tokenType = context.TokenHandler.TokenType; string? issuedProfile = (context.TokenHandler as IssuedIdentityTokenHandler)? .IssuedTokenTypeProfileUri; foreach (IUserTokenAuthenticator authenticator in snapshot) { if (authenticator.TokenType != tokenType) { continue; } if (tokenType == UserTokenType.IssuedToken && !string.IsNullOrEmpty(authenticator.IssuedTokenProfileUri) && !string.Equals( authenticator.IssuedTokenProfileUri, issuedProfile, StringComparison.Ordinal)) { continue; } AuthenticationResult result = await authenticator.AuthenticateAsync(context, ct).ConfigureAwait(false); if (result.Outcome != AuthenticationOutcome.NotHandled) { if (result.Outcome == AuthenticationOutcome.Accepted && result.Identity != null) { IIdentityAugmenter[] augSnapshot; lock (m_lock) { augSnapshot = [.. m_augmenters]; } IUserIdentity identity = result.Identity; foreach (IIdentityAugmenter augmenter in augSnapshot) { AuthenticationResult augmentResult = await augmenter .AugmentAsync(identity, context, ct) .ConfigureAwait(false); if (augmentResult.Outcome == AuthenticationOutcome.Rejected) { return augmentResult; } if (augmentResult.Outcome == AuthenticationOutcome.NotHandled) { continue; } if (augmentResult.Identity == null) { throw new InvalidOperationException( $"IIdentityAugmenter '{augmenter.GetType().Name}' returned Accepted " + "without an identity."); } identity = augmentResult.Identity; } return AuthenticationResult.Accept(identity); } return result; } } return AuthenticationResult.NotHandled; } private static string Key(UserTokenType type, string? profileUri) { return profileUri == null ? type.ToString() : $"{type}|{profileUri}"; } private readonly Lock m_lock = new(); private readonly Dictionary m_byKey = new(StringComparer.Ordinal); private readonly List m_order = []; private readonly List m_augmenters = []; } }