diff --git a/eng/Directory.Build.Data.props b/eng/Directory.Build.Data.props index 350f0c2993238..2dc8a3bb049c7 100644 --- a/eng/Directory.Build.Data.props +++ b/eng/Directory.Build.Data.props @@ -41,9 +41,10 @@ true + true true - true - true + true + true @@ -112,7 +113,7 @@ $(IntermediateOutputPath)$(TargetFramework)\$(MSBuildProjectName).xml - + false false diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 28b3bffd87552..1825607ad303d 100755 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -36,14 +36,10 @@ - - + + - - - - @@ -104,6 +100,10 @@ + + + + diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AppConfigurationAzureClientBuilderExtensions.cs b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AppConfigurationAzureClientBuilderExtensions.cs deleted file mode 100644 index e9574825b4a78..0000000000000 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AppConfigurationAzureClientBuilderExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Azure.Core; - -namespace Azure.ApplicationModel.Configuration -{ - /// - /// App Configuration client builder - /// - public static class AppConfigurationAzureClientBuilderExtensions - { - public static TBuilder AddAppConfiguration(this TBuilder builder, - string name, - string connectionString, - Action configureOptions = null) - where TBuilder: IAzureClientsBuilder - { - builder.RegisterClient(name, options => new ConfigurationClient(connectionString, options), configureOptions); - return builder; - } - - public static TBuilder AddAppConfiguration(this TBuilder builder, string name, TConfiguration configuration) - where TBuilder: IAzureClientsBuilderWithConfiguration - { - builder.RegisterClient(name, configuration); - return builder; - } - } -} diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj index fd2276c6a487c..92019df4b546f 100644 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj @@ -13,9 +13,6 @@ $(RequiredTargetFrameworks) $(NoWarn);3021 true - - - $(NoWarn);1591 diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AzureClientBuilderExtensions.cs b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AzureClientBuilderExtensions.cs new file mode 100644 index 0000000000000..f4e147a6d9630 --- /dev/null +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/AzureClientBuilderExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Extensions; + +namespace Azure.ApplicationModel.Configuration +{ + /// + /// Extension methods to add client to clients builder + /// + public static class AzureClientBuilderExtensions + { + /// + /// Registers a instance with the provided + /// + public static IAzureClientBuilder AddConfigurationClient(this TBuilder builder, string connectionString) + where TBuilder: IAzureClientFactoryBuilder + { + return builder.RegisterClientFactory(options => new ConfigurationClient(connectionString, options)); + } + + /// + /// Registers a instance with connection options loaded from the provided instance. + /// + public static IAzureClientBuilder AddConfigurationClient(this TBuilder builder, TConfiguration configuration) + where TBuilder: IAzureClientFactoryBuilderWithConfiguration + { + return builder.RegisterClientFactory(configuration); + } + } +} diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClientOptions.cs b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClientOptions.cs index 8b9b4bb75c7f1..6f8f6de9849f0 100644 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClientOptions.cs +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClientOptions.cs @@ -20,6 +20,9 @@ public class ConfigurationClientOptions: ClientOptions /// public enum ServiceVersion { + /// + /// Uses the latest service version + /// Default = 0 } @@ -35,7 +38,7 @@ public enum ServiceVersion /// /// /// The of the service API used when - /// making requests. + /// making requests. /// public ConfigurationClientOptions(ServiceVersion version = ServiceVersion.Default) { diff --git a/sdk/core/Azure.Core.Extensions/samples/Azure.Core.Extensions.Samples.csproj b/sdk/core/Azure.Core.Extensions/samples/Azure.Core.Extensions.Samples.csproj new file mode 100644 index 0000000000000..9cc1af65dbc5c --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/samples/Azure.Core.Extensions.Samples.csproj @@ -0,0 +1,12 @@ + + + netcoreapp2.1 + $(RequiredTargetFrameworks) + false + + + + + + + diff --git a/sdk/core/Azure.Core.Extensions/samples/CustomPolicy.cs b/sdk/core/Azure.Core.Extensions/samples/CustomPolicy.cs new file mode 100644 index 0000000000000..68bfaa8617a46 --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/samples/CustomPolicy.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Pipeline; +using Microsoft.AspNetCore.Hosting; + +namespace Azure.Core.Extensions.Samples +{ + internal class DependencyInjectionEnabledPolicy : SynchronousHttpPipelinePolicy + { + private readonly IHostingEnvironment _environment; + + public DependencyInjectionEnabledPolicy(IHostingEnvironment environment) + { + this._environment = environment; + } + + public override void OnSendingRequest(HttpPipelineMessage message) + { + message.Request.Headers.Add("application-name", _environment.ApplicationName); + base.OnSendingRequest(message); + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/samples/Program.cs b/sdk/core/Azure.Core.Extensions/samples/Program.cs new file mode 100644 index 0000000000000..8ad09e68bc66a --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/samples/Program.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace Azure.Core.Extensions.Samples +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/sdk/core/Azure.Core.Extensions/samples/Startup.cs b/sdk/core/Azure.Core.Extensions/samples/Startup.cs new file mode 100644 index 0000000000000..48089fdb38dc6 --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/samples/Startup.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Pipeline; +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Azure.Core.Extensions.Samples +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + // Registering policy to use in ConfigureDefaults later + services.AddSingleton(); + + services.AddAzureClients(builder => { + + builder.AddSecretClient(Configuration.GetSection("KeyVault")) + .WithName("Default") + .WithCredential(new DefaultAzureCredential()) + .ConfigureOptions(options => options.Retry.MaxRetries = 10); + + builder.AddSecretClient(new Uri("http://my.keyvault.com")); + + builder.UseCredential(new DefaultAzureCredential()); + + // This would use configuration for auth and client settings + builder.ConfigureDefaults(Configuration.GetSection("Default")); + + // Configure global defaults + builder.ConfigureDefaults(options => options.Retry.Mode = RetryMode.Exponential); + + // Advanced configure global defaults + builder.ConfigureDefaults((options, provider) => options.AddPolicy(HttpPipelinePosition.PerCall, provider.GetService())); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, SecretClient secretClient) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.Run(async context => { + context.Response.ContentType = "text"; + foreach (var secret in secretClient.GetSecrets()) + { + await context.Response.WriteAsync(secret.Value.Name + Environment.NewLine); + } + }); + } + } +} diff --git a/sdk/core/Azure.Core.Extensions/samples/appsettings.json b/sdk/core/Azure.Core.Extensions/samples/appsettings.json new file mode 100644 index 0000000000000..10d2bfca313c7 --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/samples/appsettings.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug" + } + }, + "AllowedHosts": "*", + "Default": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + + "TelemetryPolicy": { + "ApplicationId": "AppId" + } + }, + "KeyVault": { + "VaultUri": "" + } +} diff --git a/sdk/core/Azure.Core.Extensions/src/Azure.Core.Extensions.csproj b/sdk/core/Azure.Core.Extensions/src/Azure.Core.Extensions.csproj index b04fa080696c2..0c26e74acae89 100644 --- a/sdk/core/Azure.Core.Extensions/src/Azure.Core.Extensions.csproj +++ b/sdk/core/Azure.Core.Extensions/src/Azure.Core.Extensions.csproj @@ -30,6 +30,7 @@ + diff --git a/sdk/core/Azure.Core.Extensions/src/AzureClientBuilderExtensions.cs b/sdk/core/Azure.Core.Extensions/src/AzureClientBuilderExtensions.cs new file mode 100644 index 0000000000000..f50e870d0aa8b --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/AzureClientBuilderExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + public static class AzureClientBuilderExtensions + { + public static IAzureClientBuilder WithName(this IAzureClientBuilder builder, string name) where TOptions : class + { + builder.ToBuilder().Registration.Name = name; + return builder; + } + + public static IAzureClientBuilder WithCredential(this IAzureClientBuilder builder, TokenCredential credential) where TOptions : class + { + return builder.WithCredential(_ => credential); + } + + public static IAzureClientBuilder WithCredential(this IAzureClientBuilder builder, Func credentialFactory) where TOptions : class + { + var impl = builder.ToBuilder(); + impl.ServiceCollection.AddSingleton>>(new ConfigureClientCredentials(impl.Registration, credentialFactory)); + return builder; + } + + public static IAzureClientBuilder ConfigureOptions(this IAzureClientBuilder builder, Action configureOptions) where TOptions : class + { + return builder.ConfigureOptions((options, _) => configureOptions(options)); + } + + public static IAzureClientBuilder ConfigureOptions(this IAzureClientBuilder builder, IConfiguration configuration) where TOptions : class + { + return builder.ConfigureOptions(options => configuration.Bind(options)); + } + + public static IAzureClientBuilder ConfigureOptions(this IAzureClientBuilder builder, Action configureOptions) where TOptions : class + { + var impl = builder.ToBuilder(); + impl.ServiceCollection.AddSingleton>(provider => new ConfigureClientOptions(provider, impl.Registration, configureOptions));; + return builder; + } + + private static AzureClientBuilder ToBuilder(this IAzureClientBuilder builder) where TOptions : class + { + return (AzureClientBuilder)builder; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/AzureClientFactoryBuilder.cs b/sdk/core/Azure.Core.Extensions/src/AzureClientFactoryBuilder.cs new file mode 100644 index 0000000000000..e9c98a17c5a8b --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/AzureClientFactoryBuilder.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Pipeline; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + public sealed class AzureClientFactoryBuilder : IAzureClientFactoryBuilderWithConfiguration, IAzureClientsBuilderWithCredential + { + private readonly IServiceCollection _serviceCollection; + + internal const string DefaultClientName = "Default"; + + internal AzureClientFactoryBuilder(IServiceCollection serviceCollection) + { + _serviceCollection = serviceCollection; + _serviceCollection.AddOptions(); + _serviceCollection.TryAddSingleton(); + } + + IAzureClientBuilder IAzureClientFactoryBuilder.RegisterClientFactory(Func clientFactory) + { + return ((IAzureClientsBuilderWithCredential)this).RegisterClientFactory((options, _) => clientFactory(options)); + } + + IAzureClientBuilder IAzureClientFactoryBuilderWithConfiguration.RegisterClientFactory(IConfiguration configuration) + { + return ((IAzureClientsBuilderWithCredential)this).RegisterClientFactory( + (options, credentials) => (TClient)ClientFactory.CreateClient(typeof(TClient), typeof(TOptions), options, configuration, credentials)) + .ConfigureOptions(configuration) + .WithCredential(ClientFactory.CreateCredential(configuration)); + } + + public AzureClientFactoryBuilder ConfigureDefaults(Action configureOptions) + { + ConfigureDefaults((options, provider) => configureOptions(options)); + return this; + } + + public AzureClientFactoryBuilder ConfigureDefaults(Action configureOptions) + { + _serviceCollection.Configure(options => options.ConfigureOptionDelegates.Add(configureOptions)); + + return this; + } + + public AzureClientFactoryBuilder ConfigureDefaults(IConfiguration configuration) + { + ConfigureDefaults(options => configuration.Bind(options)); + + var credentialsFromConfig = ClientFactory.CreateCredential(configuration); + + if (credentialsFromConfig != null) + { + UseCredential(credentialsFromConfig); + } + + return this; + } + + IAzureClientBuilder IAzureClientsBuilderWithCredential.RegisterClientFactory(Func clientFactory) + { + var clientRegistration = new ClientRegistration(DefaultClientName, clientFactory); + _serviceCollection.AddSingleton(clientRegistration); + + _serviceCollection.TryAddSingleton(typeof(IConfigureOptions>), typeof(DefaultCredentialClientOptionsSetup)); + _serviceCollection.TryAddSingleton(typeof(IConfigureOptions), typeof(DefaultClientOptionsSetup)); + _serviceCollection.TryAddSingleton(typeof(IAzureClientFactory), typeof(AzureClientFactory)); + _serviceCollection.TryAddSingleton( + typeof(TClient), + provider => provider.GetService>().CreateClient(DefaultClientName)); + + return new AzureClientBuilder(clientRegistration, _serviceCollection); + } + + + public AzureClientFactoryBuilder UseCredential(TokenCredential tokenCredential) + { + return UseCredential(_ => tokenCredential); + } + + public AzureClientFactoryBuilder UseCredential(Func tokenCredentialFactory) + { + _serviceCollection.Configure(options => options.CredentialFactory = tokenCredentialFactory); + return this; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/AzureClientsBuilder.cs b/sdk/core/Azure.Core.Extensions/src/AzureClientsBuilder.cs deleted file mode 100644 index 862c9f8bfba33..0000000000000 --- a/sdk/core/Azure.Core.Extensions/src/AzureClientsBuilder.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Azure.Core.Extensions -{ - public sealed class AzureClientsBuilder : IAzureClientsBuilderWithConfiguration - { - private readonly IServiceCollection _serviceCollection; - - internal AzureClientsBuilder(IServiceCollection serviceCollection) - { - _serviceCollection = serviceCollection; - _serviceCollection.AddOptions(); - _serviceCollection.TryAddSingleton(); - } - - void IAzureClientsBuilder.RegisterClient(string name, Func clientFactory, Action configureOptions) - { - _serviceCollection.AddSingleton(new ClientRegistration(name, clientFactory)); - - _serviceCollection.TryAddSingleton(typeof(IAzureClientFactory), typeof(AzureClientFactory)); - - if (configureOptions != null) - { - _serviceCollection.Configure(name, configureOptions); - } - } - - void IAzureClientsBuilderWithConfiguration.RegisterClient(string name, IConfiguration configuration) - { - ((IAzureClientsBuilder)this).RegisterClient( - name, - options => (TClient)ConfigurationClientFactory.CreateClient(typeof(TClient), typeof(TOptions), options, configuration), - options => configuration.Bind(options)); - } - } -} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientBuilder.cs b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientBuilder.cs new file mode 100644 index 0000000000000..0608d8c7dbabf --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientBuilder.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.DependencyInjection; + +namespace Azure.Core.Extensions +{ + internal sealed class AzureClientBuilder: IAzureClientBuilder where TOptions : class + { + public ClientRegistration Registration { get; } + public IServiceCollection ServiceCollection { get; } + + internal AzureClientBuilder(ClientRegistration clientRegistration, IServiceCollection serviceCollection) + { + Registration = clientRegistration; + ServiceCollection = serviceCollection; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/AzureClientFactory.cs b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientFactory.cs similarity index 64% rename from sdk/core/Azure.Core.Extensions/src/AzureClientFactory.cs rename to sdk/core/Azure.Core.Extensions/src/Internal/AzureClientFactory.cs index 7518dd431396c..63ae6845a34d9 100644 --- a/sdk/core/Azure.Core.Extensions/src/AzureClientFactory.cs +++ b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientFactory.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Options; namespace Azure.Core.Extensions @@ -12,11 +11,19 @@ internal class AzureClientFactory: IAzureClientFactory> _clientRegistrations; + private readonly IServiceProvider _serviceProvider; + + private readonly IOptionsMonitor> _clientsOptions; + private readonly IOptionsMonitor _monitor; private readonly EventSourceLogForwarder _logForwarder; - public AzureClientFactory(IEnumerable> clientRegistrations, IOptionsMonitor monitor, EventSourceLogForwarder logForwarder) + public AzureClientFactory( + IServiceProvider serviceProvider, + IOptionsMonitor> clientsOptions, + IEnumerable> clientRegistrations, IOptionsMonitor monitor, + EventSourceLogForwarder logForwarder) { _clientRegistrations = new Dictionary>(); foreach (var registration in clientRegistrations) @@ -24,6 +31,8 @@ public AzureClientFactory(IEnumerable> cli _clientRegistrations[registration.Name] = registration; } + _serviceProvider = serviceProvider; + _clientsOptions = clientsOptions; _monitor = monitor; _logForwarder = logForwarder; } @@ -35,7 +44,7 @@ public TClient CreateClient(string name) throw new InvalidOperationException($"Unable to find client registration with type '{typeof(TClient).Name}' and name '{name}'."); } - return registration.GetClient(_monitor.Get(name)); + return registration.GetClient(_monitor.Get(name), _clientsOptions.Get(name).CredentialFactory(_serviceProvider)); } } } \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientOptions.cs b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientOptions.cs new file mode 100644 index 0000000000000..e844e5a81ac9e --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Core.Extensions +{ + internal class AzureClientCredentialOptions + { + public Func CredentialFactory { get; set; } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/AzureClientServiceCollectionExtensions.cs b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientServiceCollectionExtensions.cs similarity index 71% rename from sdk/core/Azure.Core.Extensions/src/AzureClientServiceCollectionExtensions.cs rename to sdk/core/Azure.Core.Extensions/src/Internal/AzureClientServiceCollectionExtensions.cs index afb7ea165a2aa..26c9ccef16e0c 100644 --- a/sdk/core/Azure.Core.Extensions/src/AzureClientServiceCollectionExtensions.cs +++ b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientServiceCollectionExtensions.cs @@ -8,9 +8,9 @@ namespace Azure.Core.Extensions { public static class AzureClientServiceCollectionExtensions { - public static void AddAzureClients(this IServiceCollection collection, Action configureClients) + public static void AddAzureClients(this IServiceCollection collection, Action configureClients) { - configureClients(new AzureClientsBuilder(collection)); + configureClients(new AzureClientFactoryBuilder(collection)); } } } \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientsGlobalOptions.cs b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientsGlobalOptions.cs new file mode 100644 index 0000000000000..cf21a19ee8144 --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/AzureClientsGlobalOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Azure.Core.Pipeline; +using Azure.Identity; + +namespace Azure.Core.Extensions +{ + internal class AzureClientsGlobalOptions + { + public Func CredentialFactory { get; set; } = _ => new DefaultAzureCredential(); + public List> ConfigureOptionDelegates { get; } = new List>(); + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/ClientInformation.cs b/sdk/core/Azure.Core.Extensions/src/Internal/ClientInformation.cs similarity index 75% rename from sdk/core/Azure.Core.Extensions/src/ClientInformation.cs rename to sdk/core/Azure.Core.Extensions/src/Internal/ClientInformation.cs index f086e78d327bc..e898c737d6ef7 100644 --- a/sdk/core/Azure.Core.Extensions/src/ClientInformation.cs +++ b/sdk/core/Azure.Core.Extensions/src/Internal/ClientInformation.cs @@ -8,9 +8,9 @@ namespace Azure.Core.Extensions { internal class ClientRegistration { - public string Name { get; } + public string Name { get; set; } - private readonly Func _factory; + private readonly Func _factory; private readonly object _cacheLock = new object(); @@ -18,13 +18,13 @@ internal class ClientRegistration private ExceptionDispatchInfo _cachedException; - public ClientRegistration(string name, Func factory) + public ClientRegistration(string name, Func factory) { Name = name; _factory = factory; } - public TClient GetClient(TOptions options) + public TClient GetClient(TOptions options, TokenCredential tokenCredential) { _cachedException?.Throw(); @@ -44,7 +44,7 @@ public TClient GetClient(TOptions options) try { - _cachedClient = _factory(options); + _cachedClient = _factory(options, tokenCredential); } catch (Exception e) { diff --git a/sdk/core/Azure.Core.Extensions/src/ConfigurationClientFactory.cs b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigurationClientFactory.cs similarity index 58% rename from sdk/core/Azure.Core.Extensions/src/ConfigurationClientFactory.cs rename to sdk/core/Azure.Core.Extensions/src/Internal/ConfigurationClientFactory.cs index f648877698c78..0c25bdf673b93 100644 --- a/sdk/core/Azure.Core.Extensions/src/ConfigurationClientFactory.cs +++ b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigurationClientFactory.cs @@ -4,14 +4,16 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; +using Azure.Identity; using Microsoft.Extensions.Configuration; namespace Azure.Core.Extensions { - internal class ConfigurationClientFactory + internal class ClientFactory { - public static object CreateClient(Type clientType, Type optionsType, object options, IConfiguration configuration) + public static object CreateClient(Type clientType, Type optionsType, object options, IConfiguration configuration, TokenCredential credential) { List arguments = new List(); foreach (var constructor in clientType.GetConstructors()) @@ -26,6 +28,12 @@ public static object CreateClient(Type clientType, Type optionsType, object opti bool match = true; foreach (var parameter in constructor.GetParameters()) { + if (IsCredentialParameter(parameter)) + { + arguments.Add(credential); + continue; + } + if (IsOptionsParameter(parameter, optionsType)) { break; @@ -54,6 +62,62 @@ public static object CreateClient(Type clientType, Type optionsType, object opti throw new InvalidOperationException(BuildErrorMessage(clientType, optionsType)); } + internal static TokenCredential CreateCredential(IConfiguration configuration, IdentityClientOptions identityClientOptions = null) + { + var clientId = configuration["clientId"]; + var tenantId = configuration["tenantId"]; + var clientSecret = configuration["clientSecret"]; + var certificate = configuration["clientCertificate"]; + var certificateStoreName = configuration["clientCertificateStoreName"]; + var certificateStoreLocation = configuration["clientCertificateStoreLocation"]; + + if (!string.IsNullOrWhiteSpace(tenantId) && + !string.IsNullOrWhiteSpace(clientId) && + !string.IsNullOrWhiteSpace(clientSecret)) + { + return new ClientSecretCredential(tenantId, clientId, clientSecret, identityClientOptions); + } + + if (!string.IsNullOrWhiteSpace(tenantId) && + !string.IsNullOrWhiteSpace(clientId) && + !string.IsNullOrWhiteSpace(certificate)) + { + StoreLocation storeLocation = StoreLocation.CurrentUser; + + if (!string.IsNullOrWhiteSpace(certificateStoreLocation)) + { + storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), certificateStoreLocation, true); + } + + if (string.IsNullOrWhiteSpace(certificateStoreName)) + { + certificateStoreName = "MY"; // MY is the default used in X509Store + } + + using var store = new X509Store(certificateStoreName, storeLocation); + store.Open(OpenFlags.ReadOnly); + X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint, certificate, false); + + if (certs.Count == 0) + { + throw new InvalidOperationException($"Unable to find a certificate with thumbprint '{certificate}'"); + } + + var credential = new ClientCertificateCredential(tenantId, clientId, certs[0], identityClientOptions); + store.Close(); + + return credential; + } + + // TODO: More logging + return null; + } + + private static bool IsCredentialParameter(ParameterInfo parameter) + { + return parameter.ParameterType == typeof(TokenCredential); + } + private static bool IsOptionsParameter(ParameterInfo parameter, Type optionsType) { return parameter.ParameterType.IsAssignableFrom(optionsType) && diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientCredentials.cs b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientCredentials.cs new file mode 100644 index 0000000000000..88293b31b802c --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientCredentials.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + internal class ConfigureClientCredentials : IConfigureNamedOptions> + { + private readonly ClientRegistration _registration; + private readonly Func _credentialFactory; + + public ConfigureClientCredentials( + ClientRegistration registration, + Func credentialFactory) + { + _registration = registration; + _credentialFactory = credentialFactory; + } + + public void Configure(AzureClientCredentialOptions options) + { + } + + public void Configure(string name, AzureClientCredentialOptions options) + { + if (name == _registration.Name) + { + options.CredentialFactory = _credentialFactory; + } + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientOptions.cs b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientOptions.cs new file mode 100644 index 0000000000000..b3f97312b5f8d --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/ConfigureClientOptions.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + internal class ConfigureClientOptions : IConfigureNamedOptions where TOptions : class + { + private readonly IServiceProvider _serviceProvider; + private readonly ClientRegistration _registration; + private readonly Action _configureOptions; + + public ConfigureClientOptions(IServiceProvider serviceProvider, ClientRegistration registration, Action configureOptions) + { + _serviceProvider = serviceProvider; + _registration = registration; + _configureOptions = configureOptions; + } + + public void Configure(TOptions options) + { + } + + public void Configure(string name, TOptions options) + { + if (name == _registration.Name) + { + _configureOptions(options, _serviceProvider); + } + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/DefaultClientOptionsSetup.cs b/sdk/core/Azure.Core.Extensions/src/Internal/DefaultClientOptionsSetup.cs new file mode 100644 index 0000000000000..fa993bd919526 --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/DefaultClientOptionsSetup.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Pipeline; +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + internal class DefaultClientOptionsSetup : IConfigureNamedOptions where T : ClientOptions + { + private readonly IOptions _defaultOptions; + private readonly IServiceProvider _serviceProvider; + + public DefaultClientOptionsSetup(IOptions defaultOptions, IServiceProvider serviceProvider) + { + _defaultOptions = defaultOptions; + _serviceProvider = serviceProvider; + } + + public void Configure(T options) + { + foreach (var globalConfigureOption in _defaultOptions.Value.ConfigureOptionDelegates) + { + globalConfigureOption(options, _serviceProvider); + } + } + + public void Configure(string name, T options) + { + Configure(options); + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/Internal/DefaultCredentialClientOptionsSetup.cs b/sdk/core/Azure.Core.Extensions/src/Internal/DefaultCredentialClientOptionsSetup.cs new file mode 100644 index 0000000000000..e69027639a12f --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/src/Internal/DefaultCredentialClientOptionsSetup.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.Options; + +namespace Azure.Core.Extensions +{ + internal class DefaultCredentialClientOptionsSetup : IConfigureNamedOptions> + { + private readonly IOptions _defaultOptions; + + public DefaultCredentialClientOptionsSetup(IOptions defaultOptions) + { + _defaultOptions = defaultOptions; + } + + public void Configure(AzureClientCredentialOptions options) + { + if (options.CredentialFactory == null) + { + options.CredentialFactory = _defaultOptions.Value.CredentialFactory; + } + } + + public void Configure(string name, AzureClientCredentialOptions options) + { + Configure(options); + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/src/EventSourceLogForwarder.cs b/sdk/core/Azure.Core.Extensions/src/Internal/EventSourceLogForwarder.cs similarity index 97% rename from sdk/core/Azure.Core.Extensions/src/EventSourceLogForwarder.cs rename to sdk/core/Azure.Core.Extensions/src/Internal/EventSourceLogForwarder.cs index e070224be3ad5..33b8252dd8e89 100644 --- a/sdk/core/Azure.Core.Extensions/src/EventSourceLogForwarder.cs +++ b/sdk/core/Azure.Core.Extensions/src/Internal/EventSourceLogForwarder.cs @@ -39,18 +39,13 @@ public EventSourceLogForwarder(ILoggerFactory loggerFactory = null) protected override void OnEventSourceCreated(EventSource eventSource) { - if (_loggerFactory == null) - { - return; - } - base.OnEventSourceCreated(eventSource); if (_filter == null) { _eventSources.Add(eventSource); } - else if (_filter(eventSource)) + else if (_filter(eventSource) && _loggerFactory != null) { var logger = _loggerFactory.CreateLogger(eventSource.Name); _loggers[eventSource.Name] = logger; diff --git a/sdk/core/Azure.Core.Extensions/tests/AzureClientFactoryTests.cs b/sdk/core/Azure.Core.Extensions/tests/AzureClientFactoryTests.cs index 037ac8b411ead..10d06d725afa0 100644 --- a/sdk/core/Azure.Core.Extensions/tests/AzureClientFactoryTests.cs +++ b/sdk/core/Azure.Core.Extensions/tests/AzureClientFactoryTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Azure.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -15,7 +16,7 @@ public class AzureClientFactoryTests public void AllowsResolvingFactoryAndCreatingClientInstance() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://localhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://localhost/"))); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -30,7 +31,7 @@ public void AllowsResolvingFactoryAndCreatingClientInstance() public void ReturnsSameInstanceWhenResolvedMultipleTimes() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://localhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://localhost/"))); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -46,7 +47,7 @@ public void ExecutesConfigurationDelegateOnOptions() { var serviceCollection = new ServiceCollection(); serviceCollection.AddAzureClients(builder => - builder.AddTestClient("Default", new Uri("http://localhost/"), options => options.Property = "Value")); + builder.AddTestClient(new Uri("http://localhost/")).ConfigureOptions(options => options.Property = "Value")); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -61,7 +62,7 @@ public void ExecutesConfigurationDelegateOnOptions() public void ExecutesConfigureDelegateOnOptions() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://localhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://localhost/"))); serviceCollection.Configure("Default", options => options.Property = "Value"); ServiceProvider provider = serviceCollection.BuildServiceProvider(); @@ -77,8 +78,8 @@ public void ExecutesConfigureDelegateOnOptions() public void SubsequentRegistrationOverrides() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://localhost/"))); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://otherhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://localhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://otherhost/"))); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -92,9 +93,10 @@ public void SubsequentRegistrationOverrides() public void CanRegisterMultipleClients() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder - .AddTestClient("Default", new Uri("http://localhost/"), options => options.Property = "Value1") - .AddTestClient("OtherClient", new Uri("http://otherhost/"), options => options.Property = "Value2")); + serviceCollection.AddAzureClients(builder => { + builder.AddTestClient(new Uri("http://localhost/")).ConfigureOptions(options => options.Property = "Value1"); + builder.AddTestClient(new Uri("http://otherhost/")).WithName("OtherClient").ConfigureOptions(options => options.Property = "Value2"); + }); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -116,7 +118,7 @@ public void CanCreateClientFromConfiguration() { var configuration = GetConfiguration(new KeyValuePair("uri", "http://localhost/")); var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", configuration)); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(configuration)); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -138,7 +140,7 @@ public void SetsOptionsPropertiesFromConfiguration() ); var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", configuration)); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(configuration)); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -155,7 +157,7 @@ public void SetsOptionsPropertiesFromConfiguration() public void CreateClientThrowsWhenClientIsNotRegistered() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", new Uri("http://localhost/"))); + serviceCollection.AddAzureClients(builder => builder.AddTestClient(new Uri("http://localhost/"))); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -168,7 +170,7 @@ public void CreateClientThrowsWhenClientIsNotRegistered() public void RetrhowsExceptionFromClientCreation() { var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.AddTestClient("Default", "throw")); + serviceCollection.AddAzureClients(builder => builder.AddTestClient("throw")); ServiceProvider provider = serviceCollection.BuildServiceProvider(); IAzureClientFactory factory = provider.GetService>(); @@ -179,6 +181,133 @@ public void RetrhowsExceptionFromClientCreation() Assert.AreEqual(exception.Message, "Throwing"); } + [Test] + public void CanSetGlobalOptions() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => { + builder.AddTestClient("TestClient1"); + builder.AddTestClientWithCredentials(new Uri("http://localhost")); + builder.ConfigureDefaults(options => options.Diagnostics.ApplicationId = "GlobalAppId"); + }); + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + + TestClient testClient = provider.GetService>().CreateClient("Default"); + TestClientWithCredentials testClientWithCredentials = provider.GetService>().CreateClient("Default"); + + Assert.AreEqual("GlobalAppId", testClient.Options.Diagnostics.ApplicationId); + Assert.AreEqual("GlobalAppId", testClientWithCredentials.Options.Diagnostics.ApplicationId); + } + + [Test] + public void CanSetGlobalOptionsUsingConfiguration() + { + var configuration = GetConfiguration(new KeyValuePair("Diagnostics:ApplicationId", "GlobalAppId")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => { + builder.AddTestClient("TestClient1"); + builder.AddTestClientWithCredentials(new Uri("http://localhost")); + builder.ConfigureDefaults(configuration); + }); + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + + TestClient testClient = provider.GetService>().CreateClient("Default"); + TestClientWithCredentials testClientWithCredentials = provider.GetService>().CreateClient("Default"); + + Assert.AreEqual("GlobalAppId", testClient.Options.Diagnostics.ApplicationId); + Assert.AreEqual("GlobalAppId", testClientWithCredentials.Options.Diagnostics.ApplicationId); + } + + [Test] + public void ResolvesDefaultClientByDefault() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => builder.AddTestClient("Connection string")); + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + var client = provider.GetService(); + + Assert.AreEqual("Connection string", client.ConnectionString); + } + + [Test] + public void UsesProvidedCredentialIfOverGlobal() + { + var serviceCollection = new ServiceCollection(); + var defaultAzureCredential = new ManagedIdentityCredential(); + serviceCollection.AddAzureClients(builder => builder.AddTestClientWithCredentials(new Uri("http://localhost")).WithCredential(defaultAzureCredential)); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + TestClientWithCredentials client = provider.GetService(); + + Assert.AreSame(defaultAzureCredential, client.Credential); + } + + [Test] + public void UsesGlobalCredential() + { + var serviceCollection = new ServiceCollection(); + var defaultAzureCredential = new ManagedIdentityCredential(); + serviceCollection.AddAzureClients(builder => { + builder.AddTestClientWithCredentials(new Uri("http://localhost")); + builder.UseCredential(defaultAzureCredential); + }); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + TestClientWithCredentials client = provider.GetService(); + + Assert.AreSame(defaultAzureCredential, client.Credential); + } + + [Test] + public void UsesCredentialFromGlobalConfiguration() + { + var configuration = GetConfiguration(new KeyValuePair("clientId", "ConfigurationClientId"), + new KeyValuePair("clientSecret", "ConfigurationClientSecret"), + new KeyValuePair("tenantId", "ConfigurationTenantId")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => { + builder.AddTestClientWithCredentials(new Uri("http://localhost")); + builder.ConfigureDefaults(configuration); + }); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + TestClientWithCredentials client = provider.GetService(); + + Assert.IsInstanceOf(client.Credential); + var clientSecretCredential = (ClientSecretCredential)client.Credential; + + Assert.AreEqual("ConfigurationClientId", clientSecretCredential.ClientId); + Assert.AreEqual("ConfigurationClientSecret", clientSecretCredential.ClientSecret); + Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); + } + + [Test] + public void UsesCredentialFromConfiguration() + { + var configuration = GetConfiguration( + new KeyValuePair("uri", "http://localhost/"), + new KeyValuePair("clientId", "ConfigurationClientId"), + new KeyValuePair("clientSecret", "ConfigurationClientSecret"), + new KeyValuePair("tenantId", "ConfigurationTenantId")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => builder + .AddTestClientWithCredentials(configuration)); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + TestClientWithCredentials client = provider.GetService(); + + Assert.IsInstanceOf(client.Credential); + var clientSecretCredential = (ClientSecretCredential)client.Credential; + + Assert.AreEqual("http://localhost/", client.Uri.ToString()); + Assert.AreEqual("ConfigurationClientId", clientSecretCredential.ClientId); + Assert.AreEqual("ConfigurationClientSecret", clientSecretCredential.ClientSecret); + Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); + } + private IConfiguration GetConfiguration(params KeyValuePair[] items) { return new ConfigurationBuilder().AddInMemoryCollection(items).Build(); diff --git a/sdk/core/Azure.Core.Extensions/tests/ConfigurationClientFactoryTests.cs b/sdk/core/Azure.Core.Extensions/tests/ConfigurationClientFactoryTests.cs index 1ef3bc702c25d..a6a537bd2ad6c 100644 --- a/sdk/core/Azure.Core.Extensions/tests/ConfigurationClientFactoryTests.cs +++ b/sdk/core/Azure.Core.Extensions/tests/ConfigurationClientFactoryTests.cs @@ -3,21 +3,23 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Azure.Identity; using Microsoft.Extensions.Configuration; using NUnit.Framework; namespace Azure.Core.Extensions.Tests { - public class ConfigurationClientFactoryTests + public class ClientFactoryTests { [Test] public void SelectsConstructorBaseOnConfiguration() { IConfiguration configuration = GetConfiguration(new KeyValuePair("connectionstring", "CS")); - var factory = new ConfigurationClientFactory(); + var factory = new ClientFactory(); var clientOptions = new TestClientOptions(); - var client = (TestClient)ConfigurationClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration); + var client = (TestClient)ClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration, null); Assert.AreEqual("CS", client.ConnectionString); Assert.AreSame(clientOptions, client.Options); @@ -28,9 +30,9 @@ public void ConvertsUriConstructorParameters() { IConfiguration configuration = GetConfiguration(new KeyValuePair("uri", "http://localhost")); - var factory = new ConfigurationClientFactory(); + var factory = new ClientFactory(); var clientOptions = new TestClientOptions(); - var client = (TestClient)ConfigurationClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration); + var client = (TestClient)ClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration, null); Assert.AreEqual("http://localhost/", client.Uri.ToString()); Assert.AreSame(clientOptions, client.Options); @@ -42,13 +44,61 @@ public void ThrowsExceptionWithInformationAboutArguments() IConfiguration configuration = GetConfiguration(); var clientOptions = new TestClientOptions(); - var exception = Assert.Throws(() => ConfigurationClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration)); + var exception = Assert.Throws(() => ClientFactory.CreateClient(typeof(TestClient), typeof(TestClientOptions), clientOptions, configuration, null)); Assert.AreEqual("Unable to find matching constructor. Define one of the follow sets of configuration parameters:" + Environment.NewLine + "1. connectionString" + Environment.NewLine + "2. uri" + Environment.NewLine, exception.Message); } + [Theory] + [TestCase("currentUser", StoreLocation.CurrentUser, "my", StoreName.My)] + [TestCase("localMachine", StoreLocation.LocalMachine, "root", StoreName.Root)] + [TestCase(null, StoreLocation.CurrentUser, null, StoreName.My)] + public void CreatesCertificateCredentials(string storeLocation, StoreLocation expectedStore, string storeName, StoreName expectedName) + { + var localCert = new X509Store(expectedName, expectedStore); + localCert.Open(OpenFlags.ReadOnly); + var someLocalCert = localCert.Certificates[0].Thumbprint; + localCert.Close(); + + IConfiguration configuration = GetConfiguration( + new KeyValuePair("clientId", "ConfigurationClientId"), + new KeyValuePair("clientCertificate", someLocalCert), + new KeyValuePair("clientCertificateStoreLocation", storeLocation), + new KeyValuePair("clientCertificateStoreName", storeName), + new KeyValuePair("tenantId", "ConfigurationTenantId") + ); + + var credential = ClientFactory.CreateCredential(configuration); + + Assert.IsInstanceOf(credential); + var clientCertificateCredential = (ClientCertificateCredential)credential; + + Assert.AreEqual("ConfigurationClientId", clientCertificateCredential.ClientId); + Assert.AreEqual(someLocalCert, clientCertificateCredential.ClientCertificate.Thumbprint); + Assert.AreEqual("ConfigurationTenantId", clientCertificateCredential.TenantId); + } + + [Test] + public void CreatesClientSecretCredentials() + { + IConfiguration configuration = GetConfiguration( + new KeyValuePair("clientId", "ConfigurationClientId"), + new KeyValuePair("clientSecret", "ConfigurationClientSecret"), + new KeyValuePair("tenantId", "ConfigurationTenantId") + ); + + var credential = ClientFactory.CreateCredential(configuration); + + Assert.IsInstanceOf(credential); + var clientSecretCredential = (ClientSecretCredential)credential; + + Assert.AreEqual("ConfigurationClientId", clientSecretCredential.ClientId); + Assert.AreEqual("ConfigurationClientSecret", clientSecretCredential.ClientSecret); + Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); + } + private IConfiguration GetConfiguration(params KeyValuePair[] items) { return new ConfigurationBuilder().AddInMemoryCollection(items).Build(); diff --git a/sdk/core/Azure.Core.Extensions/tests/TestClientOptions.cs b/sdk/core/Azure.Core.Extensions/tests/TestClientOptions.cs index b992506bb3b32..8a24e5c271448 100644 --- a/sdk/core/Azure.Core.Extensions/tests/TestClientOptions.cs +++ b/sdk/core/Azure.Core.Extensions/tests/TestClientOptions.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +using Azure.Core.Pipeline; + namespace Azure.Core.Extensions.Tests { - internal class TestClientOptions + internal class TestClientOptions: ClientOptions { public string Property { get; set; } public int IntProperty { get; set; } diff --git a/sdk/core/Azure.Core.Extensions/tests/TestClientWithCredentials.cs b/sdk/core/Azure.Core.Extensions/tests/TestClientWithCredentials.cs new file mode 100644 index 0000000000000..413b355d0ac3d --- /dev/null +++ b/sdk/core/Azure.Core.Extensions/tests/TestClientWithCredentials.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Core.Extensions.Tests +{ + internal class TestClientWithCredentials : TestClient + { + public TokenCredential Credential { get; } + + public TestClientWithCredentials(Uri uri, TokenCredential credential, TestClientOptions options) : base(uri, options) + { + Credential = credential; + } + } +} \ No newline at end of file diff --git a/sdk/core/Azure.Core.Extensions/tests/TestClientsBuilderExtensions.cs b/sdk/core/Azure.Core.Extensions/tests/TestClientsBuilderExtensions.cs index 9abd25c4cbb59..aec5710d92999 100644 --- a/sdk/core/Azure.Core.Extensions/tests/TestClientsBuilderExtensions.cs +++ b/sdk/core/Azure.Core.Extensions/tests/TestClientsBuilderExtensions.cs @@ -7,25 +7,35 @@ namespace Azure.Core.Extensions.Tests { internal static class TestClientsBuilderExtensions { - public static TBuilder AddTestClient(this TBuilder builder, string name, string connectionString, Action configureOptions = null) - where TBuilder: IAzureClientsBuilder + public static IAzureClientBuilder AddTestClient(this TBuilder builder, string connectionString) + where TBuilder: IAzureClientFactoryBuilder { - builder.RegisterClient(name, options => new TestClient(connectionString, options), configureOptions); - return builder; + return builder.RegisterClientFactory(options => new TestClient(connectionString, options)); } - public static TBuilder AddTestClient(this TBuilder builder, string name, Uri uri, Action configureOptions = null) - where TBuilder: IAzureClientsBuilder + public static IAzureClientBuilder AddTestClient(this TBuilder builder, Uri uri) + where TBuilder: IAzureClientFactoryBuilder { - builder.RegisterClient(name, options => new TestClient(uri, options), configureOptions); - return builder; + return builder.RegisterClientFactory(options => new TestClient(uri, options)); } - public static TBuilder AddTestClient(this TBuilder builder, string name, TConfiguration configuration) - where TBuilder: IAzureClientsBuilderWithConfiguration + public static IAzureClientBuilder AddTestClient(this TBuilder builder, TConfiguration configuration) + where TBuilder: IAzureClientFactoryBuilderWithConfiguration { - builder.RegisterClient(name, configuration); - return builder; + return builder.RegisterClientFactory(configuration); } + + public static IAzureClientBuilder AddTestClientWithCredentials(this TBuilder builder, TConfiguration configuration) + where TBuilder: IAzureClientFactoryBuilderWithConfiguration + { + return builder.RegisterClientFactory(configuration); + } + + public static IAzureClientBuilder AddTestClientWithCredentials(this TBuilder builder, Uri uri) + where TBuilder: IAzureClientsBuilderWithCredential + { + return builder.RegisterClientFactory((options, cred) => new TestClientWithCredentials(uri, cred, options)); + } + } } \ No newline at end of file diff --git a/sdk/core/Azure.Core/Azure.Core.All.sln b/sdk/core/Azure.Core/Azure.Core.All.sln index a4201ce2863d8..e7de07bb211db 100644 --- a/sdk/core/Azure.Core/Azure.Core.All.sln +++ b/sdk/core/Azure.Core/Azure.Core.All.sln @@ -1,63 +1,67 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.462 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.ApplicationModel.Configuration", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\src\Azure.ApplicationModel.Configuration.csproj", "{79FCA629-424A-4E32-A523-8A04DB14C4B5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ApplicationModel.Configuration", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\src\Azure.ApplicationModel.Configuration.csproj", "{79FCA629-424A-4E32-A523-8A04DB14C4B5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.ApplicationModel.Configuration.Performance", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\tests\Performance\Azure.ApplicationModel.Configuration.Performance.csproj", "{EFE3808B-54A7-4417-8288-389F5065CD87}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ApplicationModel.Configuration.Performance", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\tests\Performance\Azure.ApplicationModel.Configuration.Performance.csproj", "{EFE3808B-54A7-4417-8288-389F5065CD87}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.ApplicationModel.Configuration.Samples.Tests", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\samples\Azure.ApplicationModel.Configuration.Samples.Tests.csproj", "{729F33CF-8A69-4BD7-85C5-54A64DDDEEA3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ApplicationModel.Configuration.Samples.Tests", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\samples\Azure.ApplicationModel.Configuration.Samples.Tests.csproj", "{729F33CF-8A69-4BD7-85C5-54A64DDDEEA3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.ApplicationModel.Configuration.Tests", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\tests\Azure.ApplicationModel.Configuration.Tests.csproj", "{E7AB65A7-2FA6-4648-81C5-6176E3302F11}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ApplicationModel.Configuration.Tests", "..\..\appconfiguration\Azure.ApplicationModel.Configuration\tests\Azure.ApplicationModel.Configuration.Tests.csproj", "{E7AB65A7-2FA6-4648-81C5-6176E3302F11}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core", "src\Azure.Core.csproj", "{CA90A9FA-F955-49DC-A191-6DEE6FA2E493}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "src\Azure.Core.csproj", "{CA90A9FA-F955-49DC-A191-6DEE6FA2E493}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.Extensions", "..\Azure.Core.Extensions\src\Azure.Core.Extensions.csproj", "{F8B2A9A5-1730-4EC6-B2E5-9029F1066D4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Extensions", "..\Azure.Core.Extensions\src\Azure.Core.Extensions.csproj", "{F8B2A9A5-1730-4EC6-B2E5-9029F1066D4D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.Extensions.Tests", "..\Azure.Core.Extensions\tests\Azure.Core.Extensions.Tests.csproj", "{30AAFF15-4204-493D-9E61-407194FF2330}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Extensions.Tests", "..\Azure.Core.Extensions\tests\Azure.Core.Extensions.Tests.csproj", "{30AAFF15-4204-493D-9E61-407194FF2330}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.Tests", "tests\Azure.Core.Tests.csproj", "{84491222-6C36-4FA7-BBAE-1FA804129151}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Tests", "tests\Azure.Core.Tests.csproj", "{84491222-6C36-4FA7-BBAE-1FA804129151}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Identity", "..\..\identity\Azure.Identity\src\Azure.Identity.csproj", "{B6BC0994-65AF-49C3-B6DE-485BAF478A81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Identity", "..\..\identity\Azure.Identity\src\Azure.Identity.csproj", "{B6BC0994-65AF-49C3-B6DE-485BAF478A81}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Identity.Tests", "..\..\identity\Azure.Identity\tests\Azure.Identity.Tests.csproj", "{8D5CC7C7-A578-4EE7-AE15-49607A8B0DA5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Identity.Tests", "..\..\identity\Azure.Identity\tests\Azure.Identity.Tests.csproj", "{8D5CC7C7-A578-4EE7-AE15-49607A8B0DA5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Messaging.EventHubs", "..\..\eventhub\Azure.Messaging.EventHubs\src\Azure.Messaging.EventHubs.csproj", "{E503DD93-D9FE-4DD1-943F-05F489FE53C8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Messaging.EventHubs", "..\..\eventhub\Azure.Messaging.EventHubs\src\Azure.Messaging.EventHubs.csproj", "{E503DD93-D9FE-4DD1-943F-05F489FE53C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Messaging.EventHubs.Samples", "..\..\eventhub\Azure.Messaging.EventHubs\samples\Azure.Messaging.EventHubs.Samples.csproj", "{B8EA98EB-3F74-4736-9ABF-47229423C2EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Messaging.EventHubs.Samples", "..\..\eventhub\Azure.Messaging.EventHubs\samples\Azure.Messaging.EventHubs.Samples.csproj", "{B8EA98EB-3F74-4736-9ABF-47229423C2EF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Messaging.EventHubs.Tests", "..\..\eventhub\Azure.Messaging.EventHubs\tests\Azure.Messaging.EventHubs.Tests.csproj", "{A15C624A-26D0-45DE-8554-C8A6C1B5E78F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Messaging.EventHubs.Tests", "..\..\eventhub\Azure.Messaging.EventHubs\tests\Azure.Messaging.EventHubs.Tests.csproj", "{A15C624A-26D0-45DE-8554-C8A6C1B5E78F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Certificates", "..\..\keyvault\Azure.Security.KeyVault.Certificates\src\Azure.Security.KeyVault.Certificates.csproj", "{5C132576-95B2-464B-B255-157C488023D3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Certificates", "..\..\keyvault\Azure.Security.KeyVault.Certificates\src\Azure.Security.KeyVault.Certificates.csproj", "{5C132576-95B2-464B-B255-157C488023D3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Keys", "..\..\keyvault\Azure.Security.KeyVault.Keys\src\Azure.Security.KeyVault.Keys.csproj", "{4EE7C497-BA39-4AA7-80B5-7170D3017AB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Keys", "..\..\keyvault\Azure.Security.KeyVault.Keys\src\Azure.Security.KeyVault.Keys.csproj", "{4EE7C497-BA39-4AA7-80B5-7170D3017AB4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Keys.Samples", "..\..\keyvault\Azure.Security.KeyVault.Keys\samples\Azure.Security.KeyVault.Keys.Samples.csproj", "{FBA9C9BF-4493-409E-9DC1-1724A4926F83}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Keys.Samples", "..\..\keyvault\Azure.Security.KeyVault.Keys\samples\Azure.Security.KeyVault.Keys.Samples.csproj", "{FBA9C9BF-4493-409E-9DC1-1724A4926F83}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Keys.Tests", "..\..\keyvault\Azure.Security.KeyVault.Keys\tests\Azure.Security.KeyVault.Keys.Tests.csproj", "{4E7A6C5C-76B5-457C-9227-4C06F037F35F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Keys.Tests", "..\..\keyvault\Azure.Security.KeyVault.Keys\tests\Azure.Security.KeyVault.Keys.Tests.csproj", "{4E7A6C5C-76B5-457C-9227-4C06F037F35F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Secrets", "..\..\keyvault\Azure.Security.KeyVault.Secrets\src\Azure.Security.KeyVault.Secrets.csproj", "{12807134-361A-4CD9-B0AB-D47304961146}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Secrets", "..\..\keyvault\Azure.Security.KeyVault.Secrets\src\Azure.Security.KeyVault.Secrets.csproj", "{12807134-361A-4CD9-B0AB-D47304961146}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Secrets.Samples", "..\..\keyvault\Azure.Security.KeyVault.Secrets\samples\Azure.Security.KeyVault.Secrets.Samples.csproj", "{0C0000AF-01E1-446E-B537-AE83B000977D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Secrets.Samples", "..\..\keyvault\Azure.Security.KeyVault.Secrets\samples\Azure.Security.KeyVault.Secrets.Samples.csproj", "{0C0000AF-01E1-446E-B537-AE83B000977D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Security.KeyVault.Secrets.Tests", "..\..\keyvault\Azure.Security.KeyVault.Secrets\tests\Azure.Security.KeyVault.Secrets.Tests.csproj", "{A802522D-1246-4D91-A2A9-DE98F043F526}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Security.KeyVault.Secrets.Tests", "..\..\keyvault\Azure.Security.KeyVault.Secrets\tests\Azure.Security.KeyVault.Secrets.Tests.csproj", "{A802522D-1246-4D91-A2A9-DE98F043F526}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Blobs", "..\..\storage\Azure.Storage.Blobs\src\Azure.Storage.Blobs.csproj", "{96D80C1F-8555-4C62-A4E8-54CCF8D902AD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Blobs", "..\..\storage\Azure.Storage.Blobs\src\Azure.Storage.Blobs.csproj", "{96D80C1F-8555-4C62-A4E8-54CCF8D902AD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Blobs.Tests", "..\..\storage\Azure.Storage.Blobs\tests\Azure.Storage.Blobs.Tests.csproj", "{87983ABE-928B-48F7-8C96-4CA67E278334}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Blobs.Tests", "..\..\storage\Azure.Storage.Blobs\tests\Azure.Storage.Blobs.Tests.csproj", "{87983ABE-928B-48F7-8C96-4CA67E278334}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Common", "..\..\storage\Azure.Storage.Common\src\Azure.Storage.Common.csproj", "{2DD2B13D-726C-47B5-AD0A-23D282DABA62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Common", "..\..\storage\Azure.Storage.Common\src\Azure.Storage.Common.csproj", "{2DD2B13D-726C-47B5-AD0A-23D282DABA62}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Common.Tests", "..\..\storage\Azure.Storage.Common\tests\Azure.Storage.Common.Tests.csproj", "{D76C7E8D-44CE-4BEB-8549-64040E858935}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Common.Tests", "..\..\storage\Azure.Storage.Common\tests\Azure.Storage.Common.Tests.csproj", "{D76C7E8D-44CE-4BEB-8549-64040E858935}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Files", "..\..\storage\Azure.Storage.Files\src\Azure.Storage.Files.csproj", "{76B37AE9-B9A5-4152-9C67-CD8867F71537}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Files", "..\..\storage\Azure.Storage.Files\src\Azure.Storage.Files.csproj", "{76B37AE9-B9A5-4152-9C67-CD8867F71537}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Files.Tests", "..\..\storage\Azure.Storage.Files\tests\Azure.Storage.Files.Tests.csproj", "{829EE42D-07D5-4AD5-969A-20563A9129C4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Files.Tests", "..\..\storage\Azure.Storage.Files\tests\Azure.Storage.Files.Tests.csproj", "{829EE42D-07D5-4AD5-969A-20563A9129C4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Queues", "..\..\storage\Azure.Storage.Queues\src\Azure.Storage.Queues.csproj", "{69B8D748-0443-4D44-82BB-65F09DC0F79F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Queues", "..\..\storage\Azure.Storage.Queues\src\Azure.Storage.Queues.csproj", "{69B8D748-0443-4D44-82BB-65F09DC0F79F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Storage.Queues.Tests", "..\..\storage\Azure.Storage.Queues\tests\Azure.Storage.Queues.Tests.csproj", "{5FF035D1-F5CC-4CD5-9F4F-E04EE2E65A31}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Storage.Queues.Tests", "..\..\storage\Azure.Storage.Queues\tests\Azure.Storage.Queues.Tests.csproj", "{5FF035D1-F5CC-4CD5-9F4F-E04EE2E65A31}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BC0D7C7-2E71-4CAA-830C-856E9E2D71A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Extensions.Samples", "..\Azure.Core.Extensions\samples\Azure.Core.Extensions.Samples.csproj", "{C772E341-1EC6-451A-BDAB-647B02BF262B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -68,9 +72,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {79FCA629-424A-4E32-A523-8A04DB14C4B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79FCA629-424A-4E32-A523-8A04DB14C4B5}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -408,5 +409,23 @@ Global {5FF035D1-F5CC-4CD5-9F4F-E04EE2E65A31}.Release|x64.Build.0 = Release|Any CPU {5FF035D1-F5CC-4CD5-9F4F-E04EE2E65A31}.Release|x86.ActiveCfg = Release|Any CPU {5FF035D1-F5CC-4CD5-9F4F-E04EE2E65A31}.Release|x86.Build.0 = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|x64.ActiveCfg = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|x64.Build.0 = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|x86.ActiveCfg = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Debug|x86.Build.0 = Debug|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|Any CPU.Build.0 = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|x64.ActiveCfg = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|x64.Build.0 = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|x86.ActiveCfg = Release|Any CPU + {C772E341-1EC6-451A-BDAB-647B02BF262B}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ED6A6A69-8AD2-4DC8-95FB-4FF2162A3BDD} EndGlobalSection EndGlobal diff --git a/sdk/core/Azure.Core/src/Extensions/IAzureClientBuilder.cs b/sdk/core/Azure.Core/src/Extensions/IAzureClientBuilder.cs new file mode 100644 index 0000000000000..bc20cc1e7076d --- /dev/null +++ b/sdk/core/Azure.Core/src/Extensions/IAzureClientBuilder.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core.Extensions +{ +#pragma warning disable CA1040 // Avoid empty interfaces + public interface IAzureClientBuilder where TOptions : class +#pragma warning restore CA1040 // Avoid empty interfaces + { + } +} diff --git a/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilder.cs b/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilder.cs new file mode 100644 index 0000000000000..2d1b2a5b3e4b1 --- /dev/null +++ b/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilder.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Pipeline; + +namespace Azure.Core.Extensions +{ + public interface IAzureClientFactoryBuilder + { + IAzureClientBuilder RegisterClientFactory(Func clientFactory) where TOptions : ClientOptions; + } +} diff --git a/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilderWithConfiguration.cs b/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilderWithConfiguration.cs new file mode 100644 index 0000000000000..1d50768977a34 --- /dev/null +++ b/sdk/core/Azure.Core/src/Extensions/IAzureClientFactoryBuilderWithConfiguration.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Pipeline; + +namespace Azure.Core.Extensions +{ + + public interface IAzureClientFactoryBuilderWithConfiguration: IAzureClientFactoryBuilder + { + IAzureClientBuilder RegisterClientFactory(TConfiguration configuration) where TOptions : ClientOptions; + } +} diff --git a/sdk/core/Azure.Core/src/Extensions/IAzureClientsBuilderWithCredential.cs b/sdk/core/Azure.Core/src/Extensions/IAzureClientsBuilderWithCredential.cs new file mode 100644 index 0000000000000..328bee5e68f3d --- /dev/null +++ b/sdk/core/Azure.Core/src/Extensions/IAzureClientsBuilderWithCredential.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Pipeline; + +namespace Azure.Core.Extensions +{ + public interface IAzureClientsBuilderWithCredential + { + IAzureClientBuilder RegisterClientFactory(Func clientFactory) where TOptions : ClientOptions; + } +} diff --git a/sdk/core/Azure.Core/src/IAzureClientsBuilder.cs b/sdk/core/Azure.Core/src/IAzureClientsBuilder.cs deleted file mode 100644 index 0adbc3b185863..0000000000000 --- a/sdk/core/Azure.Core/src/IAzureClientsBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; - -namespace Azure.Core -{ - public interface IAzureClientsBuilder - { - void RegisterClient(string name, Func clientFactory, Action configureOptions) where TOptions : class; - } -} diff --git a/sdk/core/Azure.Core/src/IAzureClientsBuilderWithConfiguration.cs b/sdk/core/Azure.Core/src/IAzureClientsBuilderWithConfiguration.cs deleted file mode 100644 index 2c48380a5fcfb..0000000000000 --- a/sdk/core/Azure.Core/src/IAzureClientsBuilderWithConfiguration.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Core -{ - public interface IAzureClientsBuilderWithConfiguration: IAzureClientsBuilder - { - void RegisterClient(string name, TConfiguration configuration) where TOptions : class; - } -} diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index 98b6502a864d0..2cd4336a37a85 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs index 1733baeea2797..77dca35d86bcc 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs @@ -17,10 +17,22 @@ namespace Azure.Identity /// public class ClientCertificateCredential : TokenCredential { - private string _tenantId; - private string _clientId; - private X509Certificate2 _clientCertificate; - private AadIdentityClient _client; + /// + /// Gets the Azure Active Directory tenant (directory) Id of the service principal + /// + public string TenantId { get; } + + /// + /// Gets the client (application) ID of the service principal + /// + public string ClientId { get; } + + /// + /// Gets the authentication X509 Certificate of the service principal + /// + public X509Certificate2 ClientCertificate { get; } + + private readonly AadIdentityClient _client; /// /// Creates an instance of the ClientCertificateCredential with the details needed to authenticate against Azure Active Directory with the specified certificate. @@ -42,11 +54,11 @@ public ClientCertificateCredential(string tenantId, string clientId, X509Certifi /// Options that allow to configure the management of the requests sent to the Azure Active Directory service. public ClientCertificateCredential(string tenantId, string clientId, X509Certificate2 clientCertificate, IdentityClientOptions options) { - _tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId)); + TenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId)); - _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - _clientCertificate = clientCertificate ?? throw new ArgumentNullException(nameof(clientCertificate)); + ClientCertificate = clientCertificate ?? throw new ArgumentNullException(nameof(clientCertificate)); _client = (options != null) ? new AadIdentityClient(options) : AadIdentityClient.SharedClient; } @@ -59,7 +71,7 @@ public ClientCertificateCredential(string tenantId, string clientId, X509Certifi /// An which can be used to authenticate service client calls. public override AccessToken GetToken(string[] scopes, CancellationToken cancellationToken = default) { - return _client.Authenticate(_tenantId, _clientId, _clientCertificate, scopes, cancellationToken); + return _client.Authenticate(TenantId, ClientId, ClientCertificate, scopes, cancellationToken); } /// @@ -70,7 +82,7 @@ public override AccessToken GetToken(string[] scopes, CancellationToken cancella /// An which can be used to authenticate service client calls. public override async Task GetTokenAsync(string[] scopes, CancellationToken cancellationToken = default) { - return await _client.AuthenticateAsync(_tenantId, _clientId, _clientCertificate, scopes, cancellationToken).ConfigureAwait(false); + return await _client.AuthenticateAsync(TenantId, ClientId, ClientCertificate, scopes, cancellationToken).ConfigureAwait(false); } } } diff --git a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs index 2b4beb640d3f5..2e7f138af9356 100644 --- a/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientSecretCredential.cs @@ -10,16 +10,27 @@ namespace Azure.Identity { /// /// Enables authentication to Azure Active Directory using a client secret that was generated for an App Registration. More information on how - /// to configure a client secret can be found here: + /// to configure a client secret can be found here: /// https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application /// public class ClientSecretCredential : TokenCredential { - private string _tenantId; - private string _clientId; - private string _clientSecret; - private AadIdentityClient _client; + private readonly AadIdentityClient _client; + /// + /// Gets the Azure Active Directory tenant (directory) Id of the service principal + /// + public string TenantId { get; } + + /// + /// Gets the client (application) ID of the service principal + /// + public string ClientId { get; } + + /// + /// Gets the client secret that was generated for the App Registration used to authenticate the client. + /// + public string ClientSecret { get; } /// /// Creates an instance of the ClientSecretCredential with the details needed to authenticate against Azure Active Directory with a client secret. @@ -41,9 +52,9 @@ public ClientSecretCredential(string tenantId, string clientId, string clientSec /// Options that allow to configure the management of the requests sent to the Azure Active Directory service. public ClientSecretCredential(string tenantId, string clientId, string clientSecret, IdentityClientOptions options) { - _tenantId = tenantId; - _clientId = clientId; - _clientSecret = clientSecret; + TenantId = tenantId; + ClientId = clientId; + ClientSecret = clientSecret; _client = (options != null) ? new AadIdentityClient(options) : AadIdentityClient.SharedClient; } @@ -56,7 +67,7 @@ public ClientSecretCredential(string tenantId, string clientId, string clientSec /// An which can be used to authenticate service client calls. public override async Task GetTokenAsync(string[] scopes, CancellationToken cancellationToken = default) { - return await this._client.AuthenticateAsync(_tenantId, _clientId, _clientSecret, scopes, cancellationToken).ConfigureAwait(false); + return await this._client.AuthenticateAsync(TenantId, ClientId, ClientSecret, scopes, cancellationToken).ConfigureAwait(false); } /// @@ -67,7 +78,7 @@ public override async Task GetTokenAsync(string[] scopes, Cancellat /// An which can be used to authenticate service client calls. public override AccessToken GetToken(string[] scopes, CancellationToken cancellationToken = default) { - return this._client.Authenticate(_tenantId, _clientId, _clientSecret, scopes, cancellationToken); + return this._client.Authenticate(TenantId, ClientId, ClientSecret, scopes, cancellationToken); } } } diff --git a/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs b/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs index 5e73dffb725ed..6040fd11782ca 100644 --- a/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs +++ b/sdk/identity/Azure.Identity/tests/EnvironmentCredentialProviderTests.cs @@ -43,11 +43,11 @@ public void CredentialConstruction() Assert.NotNull(cred); - Assert.AreEqual("mockclientid", cred._clientId()); + Assert.AreEqual("mockclientid", cred.ClientId); - Assert.AreEqual("mocktenantid", cred._tenantId()); + Assert.AreEqual("mocktenantid", cred.TenantId); - Assert.AreEqual("mockclientsecret", cred._clientSecret()); + Assert.AreEqual("mockclientsecret", cred.ClientSecret); } finally { diff --git a/sdk/identity/Azure.Identity/tests/TestAccessorExtensions.cs b/sdk/identity/Azure.Identity/tests/TestAccessorExtensions.cs index 79be2c6840e29..a253f88079765 100644 --- a/sdk/identity/Azure.Identity/tests/TestAccessorExtensions.cs +++ b/sdk/identity/Azure.Identity/tests/TestAccessorExtensions.cs @@ -10,21 +10,6 @@ namespace Azure.Identity.Tests { internal static class TestAccessorExtensions { - public static string _clientId(this ClientSecretCredential credential) - { - return typeof(ClientSecretCredential).GetField("_clientId", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(credential) as string; - } - - public static string _tenantId(this ClientSecretCredential credential) - { - return typeof(ClientSecretCredential).GetField("_tenantId", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(credential) as string; - } - - public static string _clientSecret(this ClientSecretCredential credential) - { - return typeof(ClientSecretCredential).GetField("_clientSecret", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(credential) as string; - } - public static string _client(this ClientSecretCredential credential) { return typeof(ClientSecretCredential).GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(credential) as string; diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj index 8e6542ef5b984..bdab447e61224 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj @@ -16,7 +16,7 @@ - + diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClientBuilderExtensions.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClientBuilderExtensions.cs new file mode 100644 index 0000000000000..7de41f4b11208 --- /dev/null +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClientBuilderExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Extensions; + +namespace Azure.Security.KeyVault.Secrets +{ + /// + /// Extension methods to add secret client to clients builder + /// + public static class AzureClientBuilderExtensions + { + /// + /// Registers a instance with the provided + /// + public static IAzureClientBuilder AddSecretClient(this TBuilder builder, Uri vaultUri) + where TBuilder: IAzureClientsBuilderWithCredential + { + return builder.RegisterClientFactory((options, cred) => new SecretClient(vaultUri, cred, options)); + } + + /// + /// Registers a instance with connection options loaded from the provided instance. + /// + public static IAzureClientBuilder AddSecretClient(this TBuilder builder, TConfiguration configuration) + where TBuilder: IAzureClientFactoryBuilderWithConfiguration + { + return builder.RegisterClientFactory(configuration); + } + } +}