From 7f4db83bee55e84980d2b5d45aa6e3a9cfecdcf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:03:33 +0000 Subject: [PATCH 1/3] Initial plan From c3b0338106d092c16ad751f308d1222c9c121db2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:15:05 +0000 Subject: [PATCH 2/3] Throw InvalidOperationException for unwired custom credentials with helpful error messages Co-authored-by: neha-bhargava <61847233+neha-bhargava@users.noreply.github.com> --- .../CertificateErrorMessage.cs | 6 +-- ...CredentialsLoader.CustomSignedAssertion.cs | 3 ++ .../InternalAPI.Unshipped.txt | 3 ++ .../CustomSignedAssertionProviderTests.cs | 47 ++++++++++++------- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs b/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs index f2e370029..d896f3f9e 100644 --- a/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs +++ b/src/Microsoft.Identity.Web.Certificate/CertificateErrorMessage.cs @@ -18,9 +18,9 @@ internal static class CertificateErrorMessage public const string ClientCertificatesHaveExpiredOrCannotBeLoaded = "IDW10109: All client certificates passed to the configuration have expired or can't be loaded. "; public const string CustomProviderNameAlreadyExists = "IDW10111 The custom signed assertion provider '{0}' already exists, only the the first instance of ICustomSignedAssertionProvider with this name will be used."; - public const string CustomProviderNameNullOrEmpty = "IDW10112 The name of the custom signed assertion provider is null or empty."; - public const string CustomProviderNotFound = "IDW10113: The custom signed assertion provider with name '{0}' was not found. Was it registered in the service collection?"; - public const string CustomProviderSourceLoaderNullOrEmpty = "IDW10114 The dictionary of SourceLoaders for custom signed assertion providers is null or empty."; + public const string CustomProviderNameNullOrEmpty = "IDW10112: You configured a custom signed assertion but did not specify a provider name in the CustomSignedAssertionProviderName property of the CredentialDescription. Please specify the name of the custom assertion provider."; + public const string CustomProviderNotFound = "IDW10113: You configured a custom signed assertion with provider name '{0}' but it was not found. Did you register it in the service collection? You need to add a reference to the credential package and call the appropriate registration method, e.g., services.AddOidcFic() or services.AddFmiSignedAssertion()."; + public const string CustomProviderSourceLoaderNullOrEmpty = "IDW10114: You configured a custom signed assertion but no custom assertion providers have been registered. You need to add a reference to the credential package and call the appropriate registration method, e.g., services.AddOidcFic() or services.AddFmiSignedAssertion()."; // 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 index afab77dcd..ccdd2175a 100644 --- a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs +++ b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs @@ -78,16 +78,19 @@ private async Task ProcessCustomSignedAssertionAsync(CredentialDescription crede { // No source loader(s) _logger.CustomProviderSourceLoaderNullOrEmpty(); + throw new InvalidOperationException(CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty); } else if (string.IsNullOrEmpty(credentialDescription.CustomSignedAssertionProviderName)) { // No provider name _logger.CustomProviderNameNullOrEmpty(); + throw new InvalidOperationException(CertificateErrorMessage.CustomProviderNameNullOrEmpty); } else if (!CustomSignedAssertionCredentialSourceLoaders!.TryGetValue(credentialDescription.CustomSignedAssertionProviderName!, out ICustomSignedAssertionProvider? sourceLoader)) { // No source loader for provider name _logger.CustomProviderNotFound(credentialDescription.CustomSignedAssertionProviderName!); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, CertificateErrorMessage.CustomProviderNotFound, credentialDescription.CustomSignedAssertionProviderName!)); } else { diff --git a/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt index 7dc5c5811..8334d33bb 100644 --- a/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.Certificate/InternalAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderNameNullOrEmpty = "IDW10112: You configured a custom signed assertion but did not specify a provider name in the CustomSignedAssertionProviderName property of the CredentialDescription. Please specify the name of the custom assertion provider." -> string! +const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderNotFound = "IDW10113: You configured a custom signed assertion with provider name '{0}' but it was not found. Did you register it in the service collection? You need to add a reference to the credential package and call the appropriate registration method, e.g., services.AddOidcFic() or services.AddFmiSignedAssertion()." -> string! +const Microsoft.Identity.Web.CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty = "IDW10114: You configured a custom signed assertion but no custom assertion providers have been registered. You need to add a reference to the credential package and call the appropriate registration method, e.g., services.AddOidcFic() or services.AddFmiSignedAssertion()." -> string! diff --git a/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs b/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs index 4e2770e37..badf6667f 100644 --- a/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs +++ b/tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs @@ -32,30 +32,35 @@ public async Task ProcessCustomSignedAssertionAsync_Tests(CustomSignedAssertionP var loader = new DefaultCredentialsLoader(data.AssertionProviderList, loggerMock); // Act - try + if (data.ExpectedExceptionType != null) { - await loader.LoadCredentialsIfNeededAsync(data.CredentialDescription, null); - } - catch (Exception ex) - { - Assert.Equal(data.ExpectedExceptionMessage, ex.Message); + var ex = await Assert.ThrowsAsync(data.ExpectedExceptionType, async () => await loader.LoadCredentialsIfNeededAsync(data.CredentialDescription, null)); + + if (data.ExpectedExceptionMessage != null) + { + Assert.Equal(data.ExpectedExceptionMessage, ex.Message); + } // This is validating the logging behavior defined by DefaultCredentialsLoader.Logger.CustomSignedAssertionProviderLoadingFailure if (data.ExpectedLogMessage is not null) { Assert.Contains(loggerMock.LoggedMessages, log => log.LogLevel == data.ExpectedLogLevel && log.Message.Contains(data.ExpectedLogMessage, StringComparison.InvariantCulture)); } - return; - } - - // Assert - if (data.ExpectedLogMessage is not null) - { - Assert.Contains(loggerMock.LoggedMessages, log => log.LogLevel == data.ExpectedLogLevel && log.Message.Contains(data.ExpectedLogMessage, StringComparison.InvariantCulture)); } else { - Assert.DoesNotContain(loggerMock.LoggedMessages, log => log.LogLevel == data.ExpectedLogLevel); + // No exception expected + await loader.LoadCredentialsIfNeededAsync(data.CredentialDescription, null); + + // Assert + if (data.ExpectedLogMessage is not null) + { + Assert.Contains(loggerMock.LoggedMessages, log => log.LogLevel == data.ExpectedLogLevel && log.Message.Contains(data.ExpectedLogMessage, StringComparison.InvariantCulture)); + } + else + { + Assert.DoesNotContain(loggerMock.LoggedMessages, log => log.LogLevel == data.ExpectedLogLevel); + } } } @@ -74,7 +79,9 @@ public static TheoryData CustomSignedAs Skip = false }, ExpectedLogLevel = LogLevel.Error, - ExpectedLogMessage = CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty + ExpectedLogMessage = CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty, + ExpectedExceptionType = typeof(InvalidOperationException), + ExpectedExceptionMessage = CertificateErrorMessage.CustomProviderSourceLoaderNullOrEmpty }, // No provider name given @@ -87,7 +94,9 @@ public static TheoryData CustomSignedAs SourceType = CredentialSource.CustomSignedAssertion }, ExpectedLogLevel = LogLevel.Error, - ExpectedLogMessage = CertificateErrorMessage.CustomProviderNameNullOrEmpty + ExpectedLogMessage = CertificateErrorMessage.CustomProviderNameNullOrEmpty, + ExpectedExceptionType = typeof(InvalidOperationException), + ExpectedExceptionMessage = CertificateErrorMessage.CustomProviderNameNullOrEmpty }, // Given provider name not found @@ -100,7 +109,9 @@ public static TheoryData CustomSignedAs SourceType = CredentialSource.CustomSignedAssertion }, ExpectedLogLevel = LogLevel.Error, - ExpectedLogMessage = string.Format(CultureInfo.InvariantCulture, CertificateErrorMessage.CustomProviderNotFound, "Provider3") + ExpectedLogMessage = string.Format(CultureInfo.InvariantCulture, CertificateErrorMessage.CustomProviderNotFound, "Provider3"), + ExpectedExceptionType = typeof(InvalidOperationException), + ExpectedExceptionMessage = string.Format(CultureInfo.InvariantCulture, CertificateErrorMessage.CustomProviderNotFound, "Provider3") }, // Happy path (no logging expected) @@ -134,6 +145,7 @@ public static TheoryData CustomSignedAs false.ToString() ) ), + ExpectedExceptionType = typeof(Exception), ExpectedExceptionMessage = FailingCustomSignedAssertionProvider.ExceptionMessage }, @@ -160,6 +172,7 @@ public class CustomSignedAssertionProviderTheoryData public CredentialDescription CredentialDescription { get; set; } = new CredentialDescription(); public LogLevel ExpectedLogLevel { get; set; } public string? ExpectedLogMessage { get; set; } + public Type? ExpectedExceptionType { get; set; } public string? ExpectedExceptionMessage { get; set; } } From dc87196c1e879fd4066beea19049860eddee1268 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:17:03 +0000 Subject: [PATCH 3/3] Improve code readability by extracting string format to variable Co-authored-by: neha-bhargava <61847233+neha-bhargava@users.noreply.github.com> --- .../DefaultCredentialsLoader.CustomSignedAssertion.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs index ccdd2175a..fc81c6a32 100644 --- a/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs +++ b/src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoader.CustomSignedAssertion.cs @@ -90,7 +90,11 @@ private async Task ProcessCustomSignedAssertionAsync(CredentialDescription crede { // No source loader for provider name _logger.CustomProviderNotFound(credentialDescription.CustomSignedAssertionProviderName!); - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, CertificateErrorMessage.CustomProviderNotFound, credentialDescription.CustomSignedAssertionProviderName!)); + string errorMessage = string.Format( + CultureInfo.InvariantCulture, + CertificateErrorMessage.CustomProviderNotFound, + credentialDescription.CustomSignedAssertionProviderName!); + throw new InvalidOperationException(errorMessage); } else {