diff --git a/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs b/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs
index 6e17ff181..b70aca88f 100644
--- a/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs
+++ b/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs
@@ -16,6 +16,9 @@ internal static class CertificateErrorMessage
public const string BothClientSecretAndCertificateProvided = "IDW10105: Both client secret and client certificate, " +
"cannot be included in the configuration of the web app when calling a web API. ";
public const string ClientCertificatesHaveExpiredOrCannotBeLoaded = "IDW10109: All client certificates passed to the configuration have expired or can't be loaded. ";
+ public const string CustomProviderNameNullOrEmpty = "IDW10111 The name of the custom signed assertion provider is null or empty.";
+ public const string CustomProviderNotFound = "IDW10112: The custom signed assertion provider with name '{0}' was not found.";
+ public const string CustomProviderSourceLoaderNullOrEmpty = "IDW10113 The dictionary of SourceLoaders for custom signed assertion providers is null or empty.";
// Encoding IDW10600 = "IDW10600:"
public const string InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. ";
diff --git a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs
new file mode 100644
index 000000000..b2d709ac8
--- /dev/null
+++ b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Identity.Abstractions;
+
+
+namespace Microsoft.Identity.Web
+{
+ public partial class DefaultCredentialsLoader
+ {
+ ///
+ /// Constructor for DefaultCredentialsLoader when using custom signed assertion provider source loaders.
+ ///
+ ///
+ /// Set of custom signed assertion providers.
+ public DefaultCredentialsLoader(ILogger? logger, IEnumerable customSignedAssertionProviders) : this(logger)
+ {
+ var sourceLoaderDict = new Dictionary();
+
+ foreach (var provider in customSignedAssertionProviders)
+ {
+ sourceLoaderDict.Add(provider.Name ?? provider.GetType().FullName!, provider);
+ }
+
+ CustomSignedAssertionCredentialSourceLoaders = sourceLoaderDict;
+ }
+
+ ///
+ /// Dictionary of custom signed assertion credential source loaders, by name (fully qualified type name).
+ ///
+ public IDictionary? CustomSignedAssertionCredentialSourceLoaders { get; }
+
+
+ private async Task ProcessCustomSignedAssertionAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters)
+ {
+ // No source loader(s)
+ if (CustomSignedAssertionCredentialSourceLoaders == null || !CustomSignedAssertionCredentialSourceLoaders.Any())
+ {
+ _logger.LogError(CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty);
+ }
+
+ // No provider name
+ else if (string.IsNullOrEmpty(credentialDescription.CustomSignedAssertionProviderName))
+ {
+ _logger.LogError(CertificateErrorMessage.CustomProviderNameNullOrEmpty);
+ }
+
+ // No source loader for provider name
+ else if (!CustomSignedAssertionCredentialSourceLoaders!.TryGetValue(credentialDescription.CustomSignedAssertionProviderName!, out ICredentialSourceLoader? sourceLoader))
+ {
+ _logger.LogError(CertificateErrorMessage.CustomProviderNotFound, credentialDescription.CustomSignedAssertionProviderName);
+ }
+
+ // Load the credentials, if there is an error, it is coming from the user's custom extension and should be logged and propagated.
+ else
+ {
+ try
+ {
+ await sourceLoader.LoadIfNeededAsync(credentialDescription, parameters);
+ }
+ catch (Exception ex)
+ {
+ Logger.CustomSignedAssertionProviderLoadingFailure(_logger, credentialDescription, ex);
+ throw;
+ }
+ return;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.Logger.cs b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.Logger.cs
index acb0fb2e4..3bbe7d35a 100644
--- a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.Logger.cs
+++ b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.Logger.cs
@@ -37,7 +37,7 @@ public static void CredentialLoadingFailure(ILogger logger, CredentialDescriptio
public static void CustomSignedAssertionProviderLoadingFailure(
ILogger logger,
CredentialDescription cd,
- CustomSignedAssertionProviderNotFoundException ex
+ Exception ex
) => s_customSignedAssertionProviderLoadingFailure(logger, cd.CustomSignedAssertionProviderName ?? "NameMissing", cd.SourceType.ToString(), cd.Skip, ex);
}
}
diff --git a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoaderCustomSignedAssertion.cs b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoaderCustomSignedAssertion.cs
deleted file mode 100644
index c706cdded..000000000
--- a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoaderCustomSignedAssertion.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using Microsoft.Identity.Abstractions;
-
-namespace Microsoft.Identity.Web
-{
- public partial class DefaultCredentialsLoader : ICredentialsLoader
- {
- ///
- /// Constructor for DefaultCredentialsLoader when using custom signed assertion provider source loaders.
- ///
- ///
- /// Set of custom signed assertion providers.
- public DefaultCredentialsLoader(ILogger? logger, IEnumerable customSignedAssertionProviders) : this(logger)
- {
- var sourceLoaderDict = new Dictionary();
-
- foreach (var provider in customSignedAssertionProviders)
- {
- sourceLoaderDict.Add(provider.Name ?? provider.GetType().FullName!, provider);
- }
-
- CustomSignedAssertionCredentialSourceLoaders = sourceLoaderDict;
- }
-
- ///
- /// Dictionary of custom signed assertion credential source loaders, by name (fully qualified type name).
- ///
- public IDictionary? CustomSignedAssertionCredentialSourceLoaders { get; }
-
- private async Task ProcessCustomSignedAssertionAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters)
- {
- CustomSignedAssertionProviderNotFoundException providerNotFoundException;
-
- // No source loader(s)
- if (CustomSignedAssertionCredentialSourceLoaders == null || !CustomSignedAssertionCredentialSourceLoaders.Any())
- {
- providerNotFoundException = CustomSignedAssertionProviderNotFoundException.SourceLoadersNullOrEmpty();
- }
-
- // No provider name
- else if (string.IsNullOrEmpty(credentialDescription.CustomSignedAssertionProviderName))
- {
- providerNotFoundException = CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty();
- }
-
- // No source loader for provider name
- else if (!CustomSignedAssertionCredentialSourceLoaders!.TryGetValue(credentialDescription.CustomSignedAssertionProviderName!, out ICredentialSourceLoader? sourceLoader))
- {
- providerNotFoundException = CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(credentialDescription.CustomSignedAssertionProviderName!);
- }
-
- // Load the credentials
- else
- {
- await sourceLoader.LoadIfNeededAsync(credentialDescription, parameters);
- return;
- }
-
- Logger.CustomSignedAssertionProviderLoadingFailure(_logger, credentialDescription, providerNotFoundException);
- throw providerNotFoundException;
- }
- }
-
- internal class CustomSignedAssertionProviderNotFoundException : Exception
- {
- private const string NameNullOrEmpty = "The name of the custom signed assertion provider is null or empty.";
- private const string SourceLoaderNullOrEmpty = "The dictionary of SourceLoaders for custom signed assertion providers is null or empty.";
- private const string ProviderNotFound = "The custom signed assertion provider with name '{0}' was not found.";
-
- public CustomSignedAssertionProviderNotFoundException(string message) : base(message)
- {
- }
-
- ///
- /// Use when the SourceLoader library has entries, but the given name is not found.
- ///
- /// Name of custom signed assertion provider
- /// An instance of this exception with a relevant message
- public static CustomSignedAssertionProviderNotFoundException ProviderNameNotFound(string name)
- {
- return new CustomSignedAssertionProviderNotFoundException(message: string.Format(CultureInfo.InvariantCulture, ProviderNotFound, name));
- }
-
- ///
- /// Use when the name of the custom signed assertion provider is null or empty.
- ///
- /// An instance of this exception with a relevant message
- public static CustomSignedAssertionProviderNotFoundException ProviderNameNullOrEmpty()
- {
- return new CustomSignedAssertionProviderNotFoundException(NameNullOrEmpty);
- }
-
- ///
- /// Use when the SourceLoader library is null or empty.
- ///
- /// An instance of this exception with a relevant message
- public static CustomSignedAssertionProviderNotFoundException SourceLoadersNullOrEmpty()
- {
- return new CustomSignedAssertionProviderNotFoundException(SourceLoaderNullOrEmpty);
- }
- }
-}
diff --git a/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt
index 9815dc396..8ad8d88d1 100644
--- a/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt
@@ -1,3 +1,6 @@
+const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderNameNullOrEmpty = "IDW10111 The name of the custom signed assertion provider is null or empty." -> string!
+const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderNotFound = "IDW10112: The custom signed assertion provider with name '{0}' was not found." -> string!
+const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty = "IDW10113 The dictionary of SourceLoaders for custom signed assertion providers is null or empty." -> string!
Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException
Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void
static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException!
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index 5e0813a88..92f8696cb 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -34,6 +34,7 @@
2.22.06.0.121.48.0
+ 4.20.722.2.45.0.3
diff --git a/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs b/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs
index 696b15601..3ed503e57 100644
--- a/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs
@@ -3,8 +3,10 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Threading.Tasks;
-using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Microsoft.Extensions.Logging;
using Microsoft.Identity.Abstractions;
using Xunit;
@@ -12,22 +14,66 @@ namespace Microsoft.Identity.Web.Test
{
public class CustomSignedAssertionProviderTests
{
-
[Theory]
[MemberData(nameof(CustomSignedAssertionTestData))]
- public async Task ProcessCustomSignedAssertionAsync_Tests(DefaultCredentialsLoader loader, CredentialDescription credentialDescription, Exception? expectedException = null)
+ public async Task ProcessCustomSignedAssertionAsync_Tests(
+ List providerList,
+ CredentialDescription credentialDescription,
+ string? expectedMessage = null)
{
+ // Arrange
+ var loggedMessages = new List();
+ var loggerMock = new Mock>();
+ loggerMock.Setup(x => x.IsEnabled(It.IsAny())).Returns(true);
+
+ var loader = new DefaultCredentialsLoader(loggerMock.Object, providerList);
+
+ // Act
try
{
await loader.LoadCredentialsIfNeededAsync(credentialDescription, null);
+
}
catch (Exception ex)
{
- Assert.Equal(expectedException?.Message, ex.Message);
+ Assert.Equal(expectedMessage, ex.Message);
+
+ // Haven't figured out yet how to get the mock logger to see the log coming from DefaultCredentialsLoader.Logger where it is logged using LogMessage.Define()
+ /* loggerMock.Verify(
+ x =>
+ x.Log(
+ LogLevel.Information,
+ It.IsAny(),
+ It.Is((v, t) => v.ToString()!.Contains(expectedMessage!)),
+ It.IsAny(),
+ It.IsAny>()),
+ Times.Once);*/
return;
}
- Assert.Null(expectedException);
+ // Assert
+ if (expectedMessage != null)
+ {
+ loggerMock.Verify(
+ x => x.Log(
+ LogLevel.Error,
+ It.IsAny(),
+ It.Is((v, t) => v.ToString()!.Contains(expectedMessage)),
+ It.IsAny(),
+ It.IsAny>()),
+ Times.Once);
+ }
+ else
+ {
+ loggerMock.Verify(
+ x => x.Log(
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>()),
+ Times.Never);
+ }
}
public static IEnumerable