Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/service/API/Microsoft.FeatureFlighting.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
<PackageReference Include="Microsoft.FeatureManagement" Version="2.6.1" />
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.4.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.1" />
<PackageReference Include="Microsoft.Identity.ServiceEssentials.AspNetCore" Version="1.31.1" />
<PackageReference Include="Microsoft.IdentityModel.Validators" Version="8.3.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.72.1" />
<PackageReference Include="Microsoft.Identity.ServiceEssentials.AspNetCore" Version="1.35.0" />
<PackageReference Include="Microsoft.Identity.ServiceEssentials.Caching" Version="1.35.0" />
<PackageReference Include="Microsoft.Identity.ServiceEssentials.Extensions.AspNetCoreMiddleware" Version="1.35.0" />
<PackageReference Include="Microsoft.IdentityModel.Validators" Version="8.11.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
25 changes: 20 additions & 5 deletions src/service/API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
using Autofac.Extensions.DependencyInjection;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Azure.Core;

namespace Microsoft.PS.Services.FlightingService.Api
{
[ExcludeFromCodeCoverage]
public static class Program
{
public static void Main(string[] args)
{
{
CreateHostBuilder(args).Build().Run();
}

Expand All @@ -37,12 +38,19 @@ public static IHostBuilder CreateHostBuilder(string[] args)

private static void AddKeyVault(IConfigurationBuilder config)
{
var builtConfig = config.Build();
var builtConfig = config.Build();
TokenCredential credential;
#if DEBUG
credential = new VisualStudioCredential();
#else
credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(builtConfig["UserAssignedClientId"]));
#endif

config.AddAzureKeyVault(
new SecretClient(
new Uri(builtConfig["KeyVault:EndpointUrl"]),
credential: new DefaultAzureCredential()
credential
),
new AzureKeyVaultConfigurationOptions()
{
Expand All @@ -58,12 +66,19 @@ private static void AddAzureAppConfiguration(IConfigurationBuilder config)
string appConfigurationUri = builtConfig["AzureAppConfigurationUri"];
string flightingAppConfigLabel = builtConfig["AppConfiguration:FeatureFlightsLabel"];
string configurationCommonLabel = builtConfig["AppConfiguration:ConfigurationCommonLabel"];
string configurationEnvLabel = builtConfig["AppConfiguration:ConfigurationEnvLabel"];
string configurationEnvLabel = builtConfig["AppConfiguration:ConfigurationEnvLabel"];
TokenCredential credential;
#if DEBUG
credential = new VisualStudioCredential();
#else
credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(builtConfig["UserAssignedClientId"]));
#endif

config.AddAzureAppConfiguration(options =>
{
options
.Connect(new Uri(appConfigurationUri),new DefaultAzureCredential())
.Connect(new Uri(appConfigurationUri), credential)
.UseFeatureFlags(configure =>
{
configure.Label = flightingAppConfigLabel;
Expand Down
3 changes: 2 additions & 1 deletion src/service/API/appsettings.fxp-preprod.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"Name": "kv-exp-ppe-eus",
"EndpointUrl": "https://kv-exp-ppe-eus.vault.azure.net/",
"PollingIntervalInHours": 1
}
},
"UserAssignedClientId": "493d2364-979a-49e8-a67a-6db0446d079b"
}
3 changes: 2 additions & 1 deletion src/service/API/appsettings.fxp-prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"Name": "kv-exp-ppe-eus",
"EndpointUrl": "https://kv-exp-prod-eus.vault.azure.net/",
"PollingIntervalInHours": 1
}
},
"UserAssignedClientId": "78d78b7f-4c24-44d7-96f2-b1b73852068f"
}
3 changes: 2 additions & 1 deletion src/service/Common/Authentication/ITokenGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public interface ITokenGenerator
/// <param name="authority">Authority to generate the token</param>
/// <param name="clientId">ID of the application for generating the token</param>
/// <param name="resourceId">Resource ID for which the token is generated</param>
/// <param name="userAssignedClientId">user Assigned Client Id</param>
/// <returns>Bearer token</returns>
Task<string> GenerateToken(string authority, string clientId, string resourceId);
Task<string> GenerateToken(string authority, string clientId, string resourceId, string userAssignedClientId);
}
}
5 changes: 3 additions & 2 deletions src/service/Common/Authorization/IAuthorizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ public interface IAuthorizationService
/// <param name="appName">Tenant name</param>
/// <returns>True if the required claims are present</returns>
bool IsAuthorized(string appName);

/// <summary>
/// Creates the bearer token
/// </summary>
/// <param name="authority">IDP authority</param>
/// <param name="clientId">AAD Client ID</param>
/// <param name="resourceId">AAD Client ID against which the token is acquired</param>
/// <param name="userAssignedClientId">user Assigned Client Id</param>
/// <returns>Bearer token</returns>
Task<string> GetAuthenticationToken(string authority, string clientId, string resourceId);
Task<string> GetAuthenticationToken(string authority, string clientId, string resourceId,string userAssignedClientId);

/// <summary>
/// Augments the user identity with the required claims
Expand Down
2 changes: 1 addition & 1 deletion src/service/Domain/Microsoft.FeatureFlighting.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PackageReference Include="Azure.Data.AppConfiguration" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.FeatureManagement" Version="2.6.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.72.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RulesEngine" Version="5.0.3" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
using Azure.Data.AppConfiguration;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;

namespace Microsoft.FeatureFlighting.Infrastructure.AppConfig
{
{
// <inheritdoc/>
internal class AzureConfigurationClientProvider : IAzureConfigurationClientProvider
{
Expand All @@ -31,9 +32,15 @@ public ConfigurationClient GetConfigurationClient()
options.Retry.Mode = RetryMode.Exponential;
options.Retry.MaxRetries = 10;
options.Retry.Delay = TimeSpan.FromSeconds(1);

TokenCredential credential;
#if DEBUG
credential = new VisualStudioCredential();
#else
credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(_configuration["UserAssignedClientId"]));
#endif
string appConfigUri = _configuration["AzureAppConfigurationUri"];
_configurationClient = new ConfigurationClient(new Uri(appConfigUri), new DefaultAzureCredential(), options);
_configurationClient = new ConfigurationClient(new Uri(appConfigUri), credential, options);
return _configurationClient;

}
Expand Down
10 changes: 6 additions & 4 deletions src/service/Infrastructure/Authentication/AadTokenGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ public AadTokenGenerator()
}

// <inheritdoc/>
public async Task<string> GenerateToken(string authority, string clientId, string resourceId)
public async Task<string> GenerateToken(string authority, string clientId, string resourceId, string userAssignedClientId)
{
IConfidentialClientApplication client = GetOrCreateConfidentialApp(authority, clientId);
IConfidentialClientApplication client = GetOrCreateConfidentialApp(authority, clientId, userAssignedClientId);
var scopes = new string[] { resourceId };
AuthenticationResult authenticationResult = await client
.AcquireTokenForClient(scopes)
.ExecuteAsync();
return authenticationResult.AccessToken;
}

private IConfidentialClientApplication GetOrCreateConfidentialApp(string authority, string clientId)
private IConfidentialClientApplication GetOrCreateConfidentialApp(string authority, string clientId, string userAssignedClientId)
{
string confidentialAppCacheKey = CreateConfidentialAppCacheKey(authority, clientId);
if (_cache.ContainsKey(confidentialAppCacheKey))
Expand All @@ -58,13 +58,15 @@ private IConfidentialClientApplication GetOrCreateConfidentialApp(string authori
return client;

#else
var credential = new ManagedIdentityCredential(userAssignedClientId);

IConfidentialClientApplication client =
ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(new Uri(authority))
.WithClientAssertion((AssertionRequestOptions options) =>
{
var accessToken = new DefaultAzureCredential().GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
var accessToken = credential.GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
return Task.FromResult(accessToken.Token);
})
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ public bool IsAuthorized(string appName)
return false;
}

public async Task<string> GetAuthenticationToken(string authority, string clientId, string resourceId)
public async Task<string> GetAuthenticationToken(string authority, string clientId, string resourceId,string userAssignedClientId)
{
AuthenticationResult authenticationResult;
const string MsalScopeSuffix = "/.default";
string bearerToken = null;
try
{
IConfidentialClientApplication app = GetOrCreateConfidentialApp(authority, clientId);
IConfidentialClientApplication app = GetOrCreateConfidentialApp(authority, clientId, userAssignedClientId);
if (app != null)
{
var scopes = new[] { resourceId + MsalScopeSuffix };
Expand All @@ -97,7 +97,7 @@ public async Task<string> GetAuthenticationToken(string authority, string client
return bearerToken;
}

private IConfidentialClientApplication GetOrCreateConfidentialApp(string authority, string clientId)
private IConfidentialClientApplication GetOrCreateConfidentialApp(string authority, string clientId,string userAssignedClientId)
{
string confidentialAppCacheKey = $"{authority}-{clientId}";
if (_confidentialApps.ContainsKey(confidentialAppCacheKey))
Expand All @@ -115,13 +115,14 @@ private IConfidentialClientApplication GetOrCreateConfidentialApp(string authori
_confidentialApps.TryAdd(confidentialAppCacheKey, app);
return app;
#else
var credential = new ManagedIdentityCredential(userAssignedClientId);
IConfidentialClientApplication app =
ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(new Uri(authority))
.WithClientAssertion((AssertionRequestOptions options) =>
{
var accessToken = new DefaultAzureCredential().GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
var accessToken = credential.GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
return Task.FromResult(accessToken.Token);
})
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,14 @@ private IGraphServiceClient CreateGraphClient(IConfiguration configuration)
_cache.Add(confidentialAppCacheKey, client);

#else
IConfidentialClientApplication client =
var credential = new ManagedIdentityCredential(configuration["UserAssignedClientId"]);
IConfidentialClientApplication client =
ConfidentialClientApplicationBuilder
.Create(configuration["Graph:ClientId"])
.WithAuthority(new Uri(authority))
.WithClientAssertion((AssertionRequestOptions options) =>
{
var accessToken = new DefaultAzureCredential().GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
var accessToken = credential.GetToken(new TokenRequestContext(new string[] { $"api://AzureADTokenExchange/.default" }), CancellationToken.None);
return Task.FromResult(accessToken.Token);
})
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.23.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Graph" Version="3.0.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.72.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="UnifiedRedisPlatform" Version="1.0.2" />
</ItemGroup>
Expand Down
20 changes: 15 additions & 5 deletions src/service/Infrastructure/Storage/BlobProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using Microsoft.FeatureFlighting.Common.Storage;
using Azure.Identity;
using Microsoft.FeatureFlighting.Common;
using Azure.Core;
using Microsoft.Extensions.Configuration;
using System.Reflection.PortableExecutable;

namespace Microsoft.FeatureFlighting.Infrastructure.Storage
{
Expand All @@ -17,16 +20,23 @@ internal class BlobProviderFactory : IBlobProviderFactory
private readonly ITenantConfigurationProvider _tenantConfigurationProvider;
private readonly ILogger _logger;
private readonly IDictionary<string, IBlobProvider> _blobProviderCache;
private readonly DefaultAzureCredential _defaultAzureCredential;

public BlobProviderFactory(ITenantConfigurationProvider tenantConfigurationProvider, ILogger logger)
private readonly TokenCredential _defaultAzureCredential;
private readonly IConfiguration _configuration;
public BlobProviderFactory(ITenantConfigurationProvider tenantConfigurationProvider, ILogger logger, IConfiguration configuration)
{
_tenantConfigurationProvider = tenantConfigurationProvider ?? throw new ArgumentNullException(nameof(tenantConfigurationProvider));
_blobProviderCache = new ConcurrentDictionary<string, IBlobProvider>(StringComparer.InvariantCultureIgnoreCase);
_logger = logger;
_configuration = configuration;
if (_defaultAzureCredential == null)
_defaultAzureCredential = new DefaultAzureCredential();

{
#if DEBUG
_defaultAzureCredential = new VisualStudioCredential();
#else
_defaultAzureCredential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(_configuration["UserAssignedClientId"]));
#endif
}
}

/// <inheritdoc/>
Expand Down
13 changes: 11 additions & 2 deletions src/service/Infrastructure/Storage/CosmosDbRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
using Microsoft.FeatureFlighting.Common.AppExceptions;
using Microsoft.FeatureFlighting.Common.Config;
using Azure.Identity;
using Azure.Core;

namespace Microsoft.FeatureFlighting.Infrastructure.Storage
{
/// <summary>
/// Azure Cosmos DB Document repository
/// </summary>
internal class CosmosDbRepository<TDoc>: IDocumentRepository<TDoc> where TDoc : class, new()
internal class CosmosDbRepository<TDoc> : IDocumentRepository<TDoc> where TDoc : class, new()
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
Expand All @@ -40,7 +41,15 @@ public CosmosDbRepository(CosmosDbConfiguration cosmosConfiguration, IConfigurat
MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(int.Parse(_configuration["CosmosDb:MaxRetryWaitTimeOnRateLimitedRequests"])),
MaxRetryAttemptsOnRateLimitedRequests = int.Parse(_configuration["CosmosDb:MaxRetryAttemptsOnRateLimitedRequests"])
};
CosmosClient client = new(cosmosConfiguration.Endpoint, new DefaultAzureCredential(), options);
TokenCredential credential;
#if DEBUG
credential = new VisualStudioCredential();
#else
credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(_configuration["UserAssignedClientId"]));
#endif

CosmosClient client = new(cosmosConfiguration.Endpoint, credential, options);
Database database = client.GetDatabase(cosmosConfiguration.DatabaseId);
_container = database.GetContainer(cosmosConfiguration.ContainerId);
_logger = logger;
Expand Down
Loading
Loading