diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs deleted file mode 100644 index d6128e68d3..0000000000 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.Modularity; - -namespace Volo.Abp.Http.Client.IdentityModel -{ - [DependsOn( - typeof(AbpHttpClientModule) - )] - public class AbpHttpClientIdentityModelModule : AbpModule - { - - } -} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs deleted file mode 100644 index e45cfbfae3..0000000000 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel/NewFolder/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Http.Client.Authentication; - -namespace NewFolder.Abp.Http.Client.IdentityModel -{ - [Dependency(ReplaceServices = true)] - public class IdentityModelHttpClientAuthenticator : IHttpClientAuthenticator, ITransientDependency - { - public IHttpContextAccessor HttpContextAccessor { get; set; } - - public async Task Authenticate(HttpClientAuthenticateContext context) - { - var accessToken = await GetAccessTokenFromHttpContextOrNullAsync() ?? - await GetAccessTokenFromServerOrNullAsync(context); - - if (accessToken != null) - { - //TODO: "Bearer" should be configurable - context.Client.DefaultRequestHeaders.Authorization - = new AuthenticationHeaderValue("Bearer", accessToken); - } - } - - protected virtual Task GetAccessTokenFromServerOrNullAsync(HttpClientAuthenticateContext context) - { - return Task.FromResult((string) null); - } - - protected virtual async Task GetAccessTokenFromHttpContextOrNullAsync() - { - var httpContext = HttpContextAccessor?.HttpContext; - if (httpContext == null) - { - return null; - } - - return await httpContext.GetTokenAsync("access_token"); - } - } -} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo.Abp.Http.Client.IdentityModel.csproj b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo.Abp.Http.Client.IdentityModel.csproj index fe60970058..c19bd29eb4 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo.Abp.Http.Client.IdentityModel.csproj +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo.Abp.Http.Client.IdentityModel.csproj @@ -14,6 +14,7 @@ + diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs new file mode 100644 index 0000000000..9946083fb8 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/AbpHttpClientIdentityModelModule.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Http.Client.IdentityModel +{ + [DependsOn( + typeof(AbpHttpClientModule) + )] + public class AbpHttpClientIdentityModelModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration); + } + } +} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfiguration.cs new file mode 100644 index 0000000000..9512328222 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfiguration.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using IdentityModel; + +namespace Volo.Abp.Http.Client.IdentityModel +{ + public class IdentityClientConfiguration : Dictionary + { + /// + /// Possible values: "client_credentials" or "password". + /// Default value: "client_credentials". + /// + public string GrantType + { + get => this.GetOrDefault(nameof(GrantType)); + set => this[GrantType] = value; + } + + /// + /// Client Id. + /// + public string ClientId + { + get => this.GetOrDefault(nameof(ClientId)); + set => this[ClientId] = value; + } + + /// + /// Client secret (as plain text - without hashed). + /// + public string ClientSecret + { + get => this.GetOrDefault(nameof(ClientSecret)); + set => this[ClientSecret] = value; + } + + /// + /// User name. + /// Valid only if is "password". + /// + public string UserName + { + get => this.GetOrDefault(nameof(UserName)); + set => this[UserName] = value; + } + + /// + /// Password of the . + /// Valid only if is "password". + /// + public string UserPassword + { + get => this.GetOrDefault(nameof(UserPassword)); + set => this[UserPassword] = value; + } + + /// + /// Authority. + /// + public string Authority + { + get => this.GetOrDefault(nameof(Authority)); + set => this[Authority] = value; + } + + /// + /// Scope. + /// + public string Scope + { + get => this.GetOrDefault(nameof(Scope)); + set => this[Scope] = value; + } + + public IdentityClientConfiguration() + { + + } + + public IdentityClientConfiguration( + string clientId, + string clientSecret, + string grantType = OidcConstants.GrantTypes.ClientCredentials, + string userName = null, + string userPassword = null) + { + this[nameof(ClientId)] = clientId; + this[nameof(ClientSecret)] = clientSecret; + this[nameof(GrantType)] = grantType; + this[nameof(UserName)] = userName; + this[nameof(UserPassword)] = userPassword; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfigurationDictionary.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfigurationDictionary.cs new file mode 100644 index 0000000000..7f79df0b37 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientConfigurationDictionary.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Http.Client.IdentityModel +{ + public class IdentityClientConfigurationDictionary : Dictionary + { + public const string DefaultName = "Default"; + + public IdentityClientConfiguration Default + { + get => this.GetOrDefault(DefaultName); + set => this[DefaultName] = value; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientOptions.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientOptions.cs new file mode 100644 index 0000000000..e9b1175bcb --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityClientOptions.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.Http.Client.IdentityModel +{ + public class IdentityClientOptions + { + public IdentityClientConfigurationDictionary IdentityClients { get; set; } + + public IdentityClientOptions() + { + IdentityClients = new IdentityClientConfigurationDictionary(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs new file mode 100644 index 0000000000..c3aaf4d93c --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelHttpClientAuthenticator.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using IdentityModel; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Authentication; + +namespace Volo.Abp.Http.Client.IdentityModel +{ + //TODO: This class should be optimized and improved: + + [Dependency(ReplaceServices = true)] + public class IdentityModelHttpClientAuthenticator : IHttpClientAuthenticator, ITransientDependency + { + public IHttpContextAccessor HttpContextAccessor { get; set; } + + protected IdentityClientOptions ClientOptions { get; } + + public IdentityModelHttpClientAuthenticator( + IOptions options) + { + ClientOptions = options.Value; + } + + public async Task Authenticate(HttpClientAuthenticateContext context) + { + var accessToken = await GetAccessTokenFromHttpContextOrNullAsync() ?? + await GetAccessTokenFromServerOrNullAsync(context); + + if (accessToken != null) + { + //TODO: "Bearer" should be configurable + context.Client.DefaultRequestHeaders.Authorization + = new AuthenticationHeaderValue("Bearer", accessToken); + } + } + + protected virtual async Task GetAccessTokenFromHttpContextOrNullAsync() + { + var httpContext = HttpContextAccessor?.HttpContext; + if (httpContext == null) + { + return null; + } + + return await httpContext.GetTokenAsync("access_token"); + } + + protected virtual async Task GetAccessTokenFromServerOrNullAsync(HttpClientAuthenticateContext context) + { + var configuration = GetClientConfiguration(context); + + if (configuration == null) + { + return null; + } + + var discoveryResponse = await GetDiscoveryResponse(configuration); + if (discoveryResponse.IsError) + { + return null; + } + + var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + if (tokenResponse.IsError) + { + return null; + } + + return tokenResponse.AccessToken; + } + + private IdentityClientConfiguration GetClientConfiguration(HttpClientAuthenticateContext context) + { + var identityClientName = context.RemoteService.GetIdentityClient(); + if (identityClientName.IsNullOrEmpty()) + { + return ClientOptions.IdentityClients.Default; + } + + return ClientOptions.IdentityClients.GetOrDefault(identityClientName) ?? + ClientOptions.IdentityClients.Default; + } + + protected virtual async Task GetDiscoveryResponse(IdentityClientConfiguration configuration) + { + return await DiscoveryClient.GetAsync(configuration.Authority); + } + + protected virtual async Task GetTokenResponse(DiscoveryResponse discoveryResponse, IdentityClientConfiguration configuration) + { + var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, configuration.ClientId, configuration.ClientSecret); + + switch (configuration.GrantType) + { + case OidcConstants.GrantTypes.ClientCredentials: + return await tokenClient.RequestClientCredentialsAsync( + configuration.Scope + ); + case OidcConstants.GrantTypes.Password: + return await tokenClient.RequestResourceOwnerPasswordAsync( + configuration.UserName, + configuration.UserPassword, + configuration.Scope + ); + default: + throw new AbpException("Grant type was not implemented: " + configuration.GrantType); + } + } + } +} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/RemoteServiceConfigurationExtensions.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/RemoteServiceConfigurationExtensions.cs new file mode 100644 index 0000000000..b42920442f --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/RemoteServiceConfigurationExtensions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Http.Client +{ + public static class RemoteServiceConfigurationExtensions + { + [CanBeNull] + public static string GetIdentityClient([NotNull] this RemoteServiceConfiguration configuration) + { + Check.NotNullOrEmpty(configuration, nameof(configuration)); + + return configuration.GetOrDefault("IdentityClient"); + } + + public static RemoteServiceConfiguration SetIdentityClient([NotNull] this RemoteServiceConfiguration configuration, [CanBeNull] string value) + { + configuration["IdentityClient"] = value; + return configuration; + } + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/HttpClientAuthenticateContext.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/HttpClientAuthenticateContext.cs index 4bf660ff42..7374b79694 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/HttpClientAuthenticateContext.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Authentication/HttpClientAuthenticateContext.cs @@ -8,16 +8,16 @@ namespace Volo.Abp.Http.Client.Authentication public HttpRequestMessage Request { get; } - public string RemoteServiceName { get; } + public RemoteServiceConfiguration RemoteService { get; } public HttpClientAuthenticateContext( HttpClient client, - HttpRequestMessage request, - string remoteServiceName) + HttpRequestMessage request, + RemoteServiceConfiguration remoteService) { Client = client; Request = request; - RemoteServiceName = remoteServiceName; + RemoteService = remoteService; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index f265f5c3c8..f3f6e8eae6 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -109,11 +109,11 @@ namespace Volo.Abp.Http.Client.DynamicProxying using (var client = _httpClientFactory.Create()) { var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); + var remoteServiceConfig = _remoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName); - var baseUrl = GetBaseUrl(clientConfig); - var action = await _apiDescriptionFinder.FindActionAsync(baseUrl, typeof(TService), invocation.Method); + var action = await _apiDescriptionFinder.FindActionAsync(remoteServiceConfig.BaseUrl, typeof(TService), invocation.Method); var apiVersion = GetApiVersionInfo(action); - var url = baseUrl + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion); + var url = remoteServiceConfig.BaseUrl + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion); var requestMessage = new HttpRequestMessage(action.GetHttpMethod(), url) { @@ -126,7 +126,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying new HttpClientAuthenticateContext( client, requestMessage, - clientConfig.RemoteServiceName + remoteServiceConfig ) ); @@ -191,13 +191,6 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private string GetBaseUrl(DynamicHttpClientProxyConfig config) - { - return _remoteServiceOptions.RemoteServices.GetOrDefault(config.RemoteServiceName)?.BaseUrl - ?? _remoteServiceOptions.RemoteServices.Default?.BaseUrl - ?? throw new AbpException($"Could not find Base URL for {typeof(TService).FullName}."); - } - private string GetConfiguredApiVersion() { var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs index 200e549c3e..d6e5021f78 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs @@ -1,10 +1,26 @@ -namespace Volo.Abp.Http.Client +using System.Collections.Generic; + +namespace Volo.Abp.Http.Client { - public class RemoteServiceConfiguration + public class RemoteServiceConfiguration : Dictionary { - public string BaseUrl { get; set; } + /// + /// Base Url. + /// + public string BaseUrl + { + get => this.GetOrDefault(nameof(BaseUrl)); + set => this[BaseUrl] = value; + } - public string Version { get; set; } + /// + /// Version. + /// + public string Version + { + get => this.GetOrDefault(nameof(Version)); + set => this[Version] = value; + } public RemoteServiceConfiguration() { @@ -13,8 +29,8 @@ public RemoteServiceConfiguration(string baseUrl, string version = null) { - BaseUrl = baseUrl; - Version = version; + this[nameof(BaseUrl)] = baseUrl; + this[nameof(Version)] = version; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs index d59470fa57..248285d748 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs @@ -8,8 +8,15 @@ namespace Volo.Abp.Http.Client public RemoteServiceConfiguration Default { - get { return this.GetOrDefault(DefaultName); } - set { this[DefaultName] = value; } + get => this.GetOrDefault(DefaultName); + set => this[DefaultName] = value; + } + + public RemoteServiceConfiguration GetConfigurationOrDefault(string name) + { + return this.GetOrDefault(name) + ?? Default + ?? throw new AbpException($"Remote service '{name}' was not found and there is no default configuration."); } } } \ No newline at end of file