-
Notifications
You must be signed in to change notification settings - Fork 241
Adding Extensibility for Custom Signed Assertion Providers #3226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JoshLozensky
merged 37 commits into
master
from
lozensky/AddCustomSignedAssertionExtensibility
Feb 6, 2025
Merged
Changes from 4 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
8ca8810
Added logic to DefaultCredentialsLoader to support custom signed asse…
JoshLozensky adc6ec9
adjusted logging message
JoshLozensky b82dd8b
simplified constructor
JoshLozensky 129819b
Added unit tests
JoshLozensky eeb9d7d
Update src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoade…
JoshLozensky b4096af
reworked error logging
JoshLozensky c9ffdef
Update error string
JoshLozensky 6cd4fdc
extra line
JoshLozensky f7dfa87
fixing public API and addressing PR comments
JoshLozensky 4be7c48
changed CustomSignedAssertionCredentialSourceLoader dict to use ICust…
JoshLozensky 39fdc69
finished unit test for behavior when user extension throws an error
JoshLozensky fb145fd
added to method summary
JoshLozensky 51681b4
Changed to concurrent dict and added logging for duplicate keys
JoshLozensky 79fc329
Added more specificity to tests and also added a check for duplicate …
JoshLozensky 9aaaac0
Merge branch 'master' into lozensky/AddCustomSignedAssertionExtensibi…
JoshLozensky 8520c07
Added null check and test
JoshLozensky c6ca484
Added custom mock logger to unit tests
JoshLozensky cb9affe
changed CustomSignedAssertionCredentialSourceLoaders to protected
JoshLozensky b8edcb6
improve null check
JoshLozensky 2a02584
removed Moq dependency
JoshLozensky 5c12e8d
Initial setup of extension classes
JoshLozensky 6779b3d
added snk reference
JoshLozensky 6748f02
Added test
JoshLozensky 06d6700
Added handling in ConfidentialClientApplicationBuilderExtension for C…
JoshLozensky db5546c
removing unneeded project reference
JoshLozensky c32179a
bring constructor up through DefaultCredentialsLoader
JoshLozensky bdd7393
added more configuration
JoshLozensky 9a3f44f
addressing PR feedback
JoshLozensky c5d15b7
Refactored Tests
JoshLozensky 6e55b7a
added appsettings copy to csproj
JoshLozensky 7a47370
Remove duplicitive functionality in test project
JoshLozensky ebc7128
fix typo
JoshLozensky 260e006
formatting
JoshLozensky 0de5970
Update tests/E2E Tests/CustomSignedAssertionProviderTests/CustomSigne…
JoshLozensky 42f611a
Update tests/E2E Tests/CustomSignedAssertionProviderTests/CustomSigne…
JoshLozensky 6a45dca
updated comment
JoshLozensky 2ed6114
fix typo
JoshLozensky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
src/Microsoft.Identity.Web.Certificate/DefaultCredentialsLoaderCustomSignedAssertion.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // 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 | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| /// <summary> | ||
| /// Constructor for DefaultCredentialsLoader when using custom signed assertion provider source loaders. | ||
| /// </summary> | ||
| /// <param name="logger"></param> | ||
| /// <param name="customSignedAssertionProviders">Set of custom signed assertion providers.</param> | ||
| public DefaultCredentialsLoader(ILogger<DefaultCredentialsLoader>? logger, IEnumerable<ICustomSignedAssertionProvider> customSignedAssertionProviders) : this(logger) | ||
| { | ||
| var sourceLoaderDict = new Dictionary<string, ICredentialSourceLoader>(); | ||
|
|
||
| foreach (var provider in customSignedAssertionProviders) | ||
| { | ||
| sourceLoaderDict.Add(provider.Name ?? provider.GetType().FullName!, provider); | ||
| } | ||
|
|
||
| CustomSignedAssertionCredentialSourceLoaders = sourceLoaderDict; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Dictionary of custom signed assertion credential source loaders, by name (fully qualified type name). | ||
| /// </summary> | ||
| public IDictionary<string, ICredentialSourceLoader>? CustomSignedAssertionCredentialSourceLoaders { get; } | ||
|
|
||
|
|
||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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)) | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| providerNotFoundException = CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty(); | ||
| } | ||
|
|
||
| // No source loader for provider name | ||
| else if (!CustomSignedAssertionCredentialSourceLoaders!.TryGetValue(credentialDescription.CustomSignedAssertionProviderName!, out ICredentialSourceLoader? sourceLoader)) | ||
| { | ||
| providerNotFoundException = CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(credentialDescription.CustomSignedAssertionProviderName!); | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // Load the credentials | ||
| else | ||
| { | ||
| await sourceLoader.LoadIfNeededAsync(credentialDescription, parameters); | ||
| return; | ||
| } | ||
|
|
||
| Logger.CustomSignedAssertionProviderLoadingFailure(_logger, credentialDescription, providerNotFoundException); | ||
| throw providerNotFoundException; | ||
| } | ||
| } | ||
|
|
||
| internal class CustomSignedAssertionProviderNotFoundException : Exception | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| 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) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Use when the SourceLoader library has entries, but the given name is not found. | ||
| /// </summary> | ||
| /// <param name="name">Name of custom signed assertion provider</param> | ||
| /// <returns>An instance of this exception with a relevant message</returns> | ||
| public static CustomSignedAssertionProviderNotFoundException ProviderNameNotFound(string name) | ||
| { | ||
| return new CustomSignedAssertionProviderNotFoundException(message: string.Format(CultureInfo.InvariantCulture, ProviderNotFound, name)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Use when the name of the custom signed assertion provider is null or empty. | ||
| /// </summary> | ||
| /// <returns>An instance of this exception with a relevant message</returns> | ||
| public static CustomSignedAssertionProviderNotFoundException ProviderNameNullOrEmpty() | ||
| { | ||
| return new CustomSignedAssertionProviderNotFoundException(NameNullOrEmpty); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Use when the SourceLoader library is null or empty. | ||
| /// </summary> | ||
| /// <returns>An instance of this exception with a relevant message</returns> | ||
| public static CustomSignedAssertionProviderNotFoundException SourceLoadersNullOrEmpty() | ||
| { | ||
| return new CustomSignedAssertionProviderNotFoundException(SourceLoaderNullOrEmpty); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.SourceLoadersNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! |
2 changes: 2 additions & 0 deletions
2
src/Microsoft.Identity.Web.Certificate/PublicAPI.Unshipped.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| #nullable enable | ||
| Microsoft.Identity.Web.DefaultCredentialsLoader.CustomSignedAssertionCredentialSourceLoaders.get -> System.Collections.Generic.IDictionary<string!, Microsoft.Identity.Abstractions.ICredentialSourceLoader!>? | ||
| Microsoft.Identity.Web.DefaultCredentialsLoader.DefaultCredentialsLoader(Microsoft.Extensions.Logging.ILogger<Microsoft.Identity.Web.DefaultCredentialsLoader!>? logger, System.Collections.Generic.IEnumerable<Microsoft.Identity.Abstractions.ICustomSignedAssertionProvider!>! customSignedAssertionProviders) -> void |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomProviderSourceLoaderNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomProviderSourceLoaderNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomProviderSourceLoaderNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException | ||
| Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomSignedAssertionProviderNotFoundException(string! message) -> void | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound(string! name) -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! | ||
| static Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException.CustomProviderSourceLoaderNullOrEmpty() -> Microsoft.Identity.Web.CustomSignedAssertionProviderNotFoundException! |
100 changes: 100 additions & 0 deletions
100
tests/Microsoft.Identity.Web.Test/CustomSignedAssertionProviderTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.Logging.Abstractions; | ||
| using Microsoft.Identity.Abstractions; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Identity.Web.Test | ||
| { | ||
| public class CustomSignedAssertionProviderTests | ||
| { | ||
JoshLozensky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| [Theory] | ||
| [MemberData(nameof(CustomSignedAssertionTestData))] | ||
| public async Task ProcessCustomSignedAssertionAsync_Tests(DefaultCredentialsLoader loader, CredentialDescription credentialDescription, Exception? expectedException = null) | ||
| { | ||
| try | ||
| { | ||
| await loader.LoadCredentialsIfNeededAsync(credentialDescription, null); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Assert.Equal(expectedException?.Message, ex.Message); | ||
| return; | ||
| } | ||
|
|
||
| Assert.Null(expectedException); | ||
| } | ||
|
|
||
| public static IEnumerable<object[]> CustomSignedAssertionTestData() | ||
| { | ||
| // No source loaders | ||
| yield return new object[] | ||
JoshLozensky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| new DefaultCredentialsLoader(NullLogger<DefaultCredentialsLoader>.Instance, new List<ICustomSignedAssertionProvider>()), | ||
| new CredentialDescription { | ||
| CustomSignedAssertionProviderName = "Provider1", | ||
| SourceType = CredentialSource.CustomSignedAssertion | ||
| }, | ||
| CustomSignedAssertionProviderNotFoundException.SourceLoadersNullOrEmpty() | ||
| }; | ||
|
|
||
| // No provider name | ||
| yield return new object[] | ||
| { | ||
| new DefaultCredentialsLoader(NullLogger<DefaultCredentialsLoader>.Instance, new List<ICustomSignedAssertionProvider> { new CustomSignedAssertionProvider("Provider1") }), | ||
| new CredentialDescription | ||
| { | ||
| CustomSignedAssertionProviderName = null, | ||
| SourceType = CredentialSource.CustomSignedAssertion | ||
| }, | ||
| CustomSignedAssertionProviderNotFoundException.ProviderNameNullOrEmpty() | ||
| }; | ||
|
|
||
| // Provider name not found | ||
| yield return new object[] | ||
| { | ||
| new DefaultCredentialsLoader(NullLogger<DefaultCredentialsLoader>.Instance, new List<ICustomSignedAssertionProvider> { new CustomSignedAssertionProvider("OtherProvider") }), | ||
| new CredentialDescription | ||
| { | ||
| CustomSignedAssertionProviderName = "Provider2", | ||
| SourceType = CredentialSource.CustomSignedAssertion | ||
| }, | ||
| CustomSignedAssertionProviderNotFoundException.ProviderNameNotFound("Provider2") | ||
| }; | ||
|
|
||
| // Happy path | ||
| yield return new object[] | ||
| { | ||
| new DefaultCredentialsLoader(NullLogger<DefaultCredentialsLoader>.Instance, new List<ICustomSignedAssertionProvider> { new CustomSignedAssertionProvider("Provider3") }), | ||
| new CredentialDescription | ||
| { | ||
| CustomSignedAssertionProviderName = "Provider3", | ||
| SourceType = CredentialSource.CustomSignedAssertion | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| public class CustomSignedAssertionProvider : ICustomSignedAssertionProvider | ||
| { | ||
| public string Name { get; } | ||
|
|
||
| public CredentialSource CredentialSource => CredentialSource.CustomSignedAssertion; | ||
|
|
||
| public CustomSignedAssertionProvider(string name) | ||
| { | ||
| Name = name; | ||
| } | ||
|
|
||
| public Task LoadIfNeededAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters) | ||
| { | ||
| return Task.CompletedTask; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.