Skip to content

Commit 6d5da9c

Browse files
jmprieurbgavrilMS
andauthored
FIC+OIDC credential provider (#3255)
* Proto for FIC+OIDC * Attempt to avoid blocking on WaitAsync() * Use credential ID to key CCA objects * Add credential ID to the key of the CCA dictionary * Update to use semaphores * Update test to use separate app + TokenAcquisition to not use semaphores * Update test config with x-cloud setup * Finalize tests * Propose a fix for: - `GetValue(key, out IConfidentialClientApplication? application) && application != null)` was not right because if `TryGetValue` returns `false`, application will necessarilly be **null**. So the condition `!TryGetValue(...) && application != null` can never be true. - which implies that we'll have a race condition: `BuildConfidentialClientApplicationAsync to be executed multiple times concurrently for the same key. The pattern used here:` The propsed code with a semaphore per key released on all cases should prevents multiple concurrent builds while still allowing concurrent access to different applications. * Productize OIDC FIC Moves the xcloud fix code to a new assembly Fixes #3243 (Certr otation test bug due to SFI) Adds a TestOnly namespace to reset the TokenAcquirerFactory * Add back unit test * Update src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Co-authored-by: Jean-Marc Prieur <jmprieur@microsoft.com> * More test fixes * Ignore integration test on GH build * Proto for FIC+OIDC * Attempt to avoid blocking on WaitAsync() * Use credential ID to key CCA objects * Add credential ID to the key of the CCA dictionary * Update to use semaphores * Update test to use separate app + TokenAcquisition to not use semaphores * Update test config with x-cloud setup * Finalize tests * Propose a fix for: - `GetValue(key, out IConfidentialClientApplication? application) && application != null)` was not right because if `TryGetValue` returns `false`, application will necessarilly be **null**. So the condition `!TryGetValue(...) && application != null` can never be true. - which implies that we'll have a race condition: `BuildConfidentialClientApplicationAsync to be executed multiple times concurrently for the same key. The pattern used here:` The propsed code with a semaphore per key released on all cases should prevents multiple concurrent builds while still allowing concurrent access to different applications. * Productize OIDC FIC Moves the xcloud fix code to a new assembly Fixes #3243 (Certr otation test bug due to SFI) Adds a TestOnly namespace to reset the TokenAcquirerFactory * Add back unit test * Update src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs Co-authored-by: Jean-Marc Prieur <jmprieur@microsoft.com> * More test fixes * Ignore integration test on GH build * Fix test common project not running on GH action * Try another fix for GH test build --------- Co-authored-by: Bogdan Gavril <bogavril@microsoft.com>
1 parent 3f12ed0 commit 6d5da9c

File tree

59 files changed

+698
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+698
-89
lines changed

Microsoft.Identity.Web.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorApp", "tests\DevApps\
158158
EndProject
159159
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "blazor", "blazor", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
160160
EndProject
161+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OidcIdpSignedAssertionProviderTests", "tests\E2E Tests\OidcIdPSignedAssertionProviderTests\OidcIdpSignedAssertionProviderTests.csproj", "{E927D215-A96C-626C-9A1A-CF99876FE7B4}"
162+
EndProject
163+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Identity.Web.OidcFIC", "src\Microsoft.Identity.Web.OidcFIC\Microsoft.Identity.Web.OidcFIC.csproj", "{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}"
164+
EndProject
161165
Global
162166
GlobalSection(SolutionConfigurationPlatforms) = preSolution
163167
Debug|Any CPU = Debug|Any CPU
@@ -369,6 +373,14 @@ Global
369373
{4D67BE6A-79CD-42E7-8748-C909FCC394DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
370374
{4D67BE6A-79CD-42E7-8748-C909FCC394DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
371375
{4D67BE6A-79CD-42E7-8748-C909FCC394DF}.Release|Any CPU.Build.0 = Release|Any CPU
376+
{E927D215-A96C-626C-9A1A-CF99876FE7B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
377+
{E927D215-A96C-626C-9A1A-CF99876FE7B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
378+
{E927D215-A96C-626C-9A1A-CF99876FE7B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
379+
{E927D215-A96C-626C-9A1A-CF99876FE7B4}.Release|Any CPU.Build.0 = Release|Any CPU
380+
{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
381+
{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
382+
{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
383+
{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}.Release|Any CPU.Build.0 = Release|Any CPU
372384
EndGlobalSection
373385
GlobalSection(SolutionProperties) = preSolution
374386
HideSolutionNode = FALSE
@@ -440,6 +452,8 @@ Global
440452
{A390650C-BCE1-4CB3-8C97-9EF9CFF5B7C5} = {45B20A78-91F8-4DD2-B9AD-F12D3A93536C}
441453
{4D67BE6A-79CD-42E7-8748-C909FCC394DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
442454
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {7786D2DD-9EE4-42E1-B587-740A2E15C41D}
455+
{E927D215-A96C-626C-9A1A-CF99876FE7B4} = {45B20A78-91F8-4DD2-B9AD-F12D3A93536C}
456+
{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A} = {1DDE1AAC-5AE6-4725-94B6-A26C58D3423F}
443457
EndGlobalSection
444458
GlobalSection(ExtensibilityGlobals) = postSolution
445459
SolutionGuid = {104367F1-CE75-4F40-B32F-F14853973187}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
4+
<Title>Microsoft Identity Web Token Cross Cloud Federation Identity Credential (FIC) support</Title>
5+
<Product>Microsoft Identity Web Cross Cloud FIC</Product>
6+
<Description>Implementation for a Cloud Federation Identity Credential (FIC) credential provider.</Description>
7+
<ProjectGuid>{8DA7A2C6-00D4-4CF1-8145-448D7B7B4E5A}</ProjectGuid>
8+
<PackageReadmeFile>README.md</PackageReadmeFile>
9+
10+
<!-- Until it releases-->
11+
<EnablePackageValidation>false</EnablePackageValidation>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<None Include="..\..\README.md">
16+
<Pack>True</Pack>
17+
<PackagePath>\</PackagePath>
18+
</None>
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\Microsoft.Identity.Web.TokenAcquisition\Microsoft.Identity.Web.TokenAcquisition.csproj" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
27+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
28+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/InternalAPI.Shipped.txt" />
29+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/InternalAPI.Unshipped.txt" />
30+
</ItemGroup>
31+
32+
</Project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Identity.Abstractions;
5+
using Microsoft.Identity.Web.OidcFic;
6+
7+
namespace Microsoft.Extensions.DependencyInjection
8+
{
9+
/// <summary>
10+
/// Extension class to add OIDC FIC signed assertion provider to the service collection
11+
///
12+
/// </summary>
13+
public static class OidcFicSignedAssertionProviderExtensions
14+
{
15+
/// <summary>
16+
/// Adds OIDC FIC signed assertion provider to the service collection
17+
/// </summary>
18+
/// <param name="services">service collection</param>
19+
/// <returns>the service collection for chaining.</returns>
20+
public static IServiceCollection AddOidcFic(this IServiceCollection services)
21+
{
22+
services.AddSingleton<ICustomSignedAssertionProvider, OidcIdpSignedAssertionLoader>();
23+
return services;
24+
}
25+
}
26+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.Logging;
8+
using Microsoft.Extensions.Options;
9+
using Microsoft.Identity.Abstractions;
10+
11+
namespace Microsoft.Identity.Web.OidcFic
12+
{
13+
internal class OidcIdpSignedAssertionLoader : ICustomSignedAssertionProvider
14+
{
15+
private readonly ILogger<OidcIdpSignedAssertionLoader> _logger;
16+
private readonly IOptionsMonitor<MicrosoftIdentityApplicationOptions> _options;
17+
private readonly IConfiguration _configuration;
18+
private readonly ITokenAcquirerFactory _tokenAcquirerFactory;
19+
20+
public OidcIdpSignedAssertionLoader(ILogger<OidcIdpSignedAssertionLoader> logger,
21+
IOptionsMonitor<MicrosoftIdentityApplicationOptions> options,
22+
IConfiguration configuration,
23+
ITokenAcquirerFactory tokenAcquirerFactory)
24+
{
25+
_logger = logger;
26+
_options = options;
27+
_configuration = configuration;
28+
_tokenAcquirerFactory = tokenAcquirerFactory;
29+
}
30+
31+
public CredentialSource CredentialSource => CredentialSource.CustomSignedAssertion;
32+
33+
public string Name => "OidcIdpSignedAssertion";
34+
35+
36+
public async Task LoadIfNeededAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters = null)
37+
{
38+
OidcIdpSignedAssertionProvider? signedAssertion = credentialDescription.CachedValue as OidcIdpSignedAssertionProvider;
39+
if (credentialDescription.CachedValue == null)
40+
{
41+
if (credentialDescription.CustomSignedAssertionProviderData == null)
42+
{
43+
if (_logger != null)
44+
{
45+
_logger.LogError(42, "CustomSignedAssertionProviderData is null");
46+
}
47+
throw new InvalidOperationException("CustomSignedAssertionProviderData is null");
48+
}
49+
50+
string? sectionName = credentialDescription.CustomSignedAssertionProviderData["ConfigurationSection"] as string;
51+
if (sectionName == null)
52+
{
53+
if (_logger != null)
54+
{
55+
_logger.LogError(42, "ConfigurationSection is null");
56+
}
57+
throw new InvalidOperationException("ConfigurationSection is null");
58+
}
59+
60+
MicrosoftIdentityApplicationOptions microsoftIdentityApplicationOptions = _options.Get(sectionName);
61+
62+
if (string.IsNullOrEmpty(microsoftIdentityApplicationOptions.Instance) && microsoftIdentityApplicationOptions.Authority == "//v2.0")
63+
{
64+
_configuration.GetSection(sectionName).Bind(microsoftIdentityApplicationOptions);
65+
}
66+
67+
signedAssertion = new OidcIdpSignedAssertionProvider(_tokenAcquirerFactory, microsoftIdentityApplicationOptions, credentialDescription.TokenExchangeUrl);
68+
}
69+
70+
try
71+
{
72+
// Try to get a signed assertion, and if it fails, move to the next credentials
73+
_ = await signedAssertion!.GetSignedAssertionAsync(null);
74+
credentialDescription.CachedValue = signedAssertion;
75+
}
76+
catch (Exception ex)
77+
{
78+
if (_logger != null)
79+
{
80+
_logger.LogError(42, "Failed to get signed assertion from {ProviderName}. exception occurred: {Message}. Setting skip to true.", credentialDescription.CustomSignedAssertionProviderName, ex.Message);
81+
}
82+
credentialDescription.Skip = true;
83+
throw;
84+
}
85+
}
86+
}
87+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Logging.Abstractions;
8+
using Microsoft.Identity.Abstractions;
9+
using Microsoft.Identity.Client;
10+
using Microsoft.Identity.Web;
11+
12+
namespace Microsoft.Identity.Web.OidcFic
13+
{
14+
internal class OidcIdpSignedAssertionProvider : ClientAssertionProviderBase
15+
{
16+
private ITokenAcquirer? _tokenAcquirer = null;
17+
private readonly ITokenAcquirerFactory _tokenAcquirerFactory;
18+
private readonly MicrosoftIdentityApplicationOptions _options;
19+
private readonly string? _tokenExchangeUrl;
20+
21+
public OidcIdpSignedAssertionProvider(ITokenAcquirerFactory tokenAcquirerFactory, MicrosoftIdentityApplicationOptions options, string? tokenExchangeUrl)
22+
{
23+
_tokenAcquirerFactory = tokenAcquirerFactory;
24+
_options = options;
25+
_tokenExchangeUrl = tokenExchangeUrl;
26+
}
27+
28+
protected override async Task<ClientAssertion> GetClientAssertionAsync(AssertionRequestOptions? assertionRequestOptions)
29+
{
30+
_tokenAcquirer ??= _tokenAcquirerFactory.GetTokenAcquirer(_options);
31+
32+
string tokenExchangeUrl = _tokenExchangeUrl ?? "api://AzureADTokenExchange";
33+
34+
AcquireTokenResult result = await _tokenAcquirer.GetTokenForAppAsync(tokenExchangeUrl + "/.default");
35+
ClientAssertion clientAssertion;
36+
if (result != null)
37+
{
38+
clientAssertion = new ClientAssertion(result.AccessToken!, result.ExpiresOn);
39+
}
40+
else
41+
{
42+
clientAssertion = null!;
43+
}
44+
return clientAssertion;
45+
}
46+
}
47+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#nullable enable
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#nullable enable
2+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader
3+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader.CredentialSource.get -> Microsoft.Identity.Abstractions.CredentialSource
4+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader.LoadIfNeededAsync(Microsoft.Identity.Abstractions.CredentialDescription! credentialDescription, Microsoft.Identity.Abstractions.CredentialSourceLoaderParameters? parameters = null) -> System.Threading.Tasks.Task!
5+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader.Name.get -> string!
6+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader.OidcIdpSignedAssertionLoader(Microsoft.Extensions.Logging.ILogger<Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionLoader!>! logger, Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Abstractions.MicrosoftIdentityApplicationOptions!>! options, Microsoft.Extensions.Configuration.IConfiguration! configuration, Microsoft.Identity.Abstractions.ITokenAcquirerFactory! tokenAcquirerFactory) -> void
7+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionProvider
8+
Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionProvider.OidcIdpSignedAssertionProvider(Microsoft.Identity.Abstractions.ITokenAcquirerFactory! tokenAcquirerFactory, Microsoft.Identity.Abstractions.MicrosoftIdentityApplicationOptions! options, string? tokenExchangeUrl) -> void
9+
override Microsoft.Identity.Web.OidcFic.OidcIdpSignedAssertionProvider.GetClientAssertionAsync(Microsoft.Identity.Client.AssertionRequestOptions? assertionRequestOptions) -> System.Threading.Tasks.Task<Microsoft.Identity.Web.ClientAssertion!>!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#nullable enable
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#nullable enable
2+
Microsoft.Extensions.DependencyInjection.OidcFicSignedAssertionProviderExtensions
3+
static Microsoft.Extensions.DependencyInjection.OidcFicSignedAssertionProviderExtensions.AddOidcFic(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#nullable enable

0 commit comments

Comments
 (0)