From d31893a7098e7790cbc870ac2459cbdec559dc27 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Tue, 19 Feb 2019 16:56:15 +0300 Subject: [PATCH] Resolved #826: Implement IdentityServer Cors Policy Service. --- .../Volo/Abp/Caching/CacheNameAttribute.cs | 15 +++++ .../Volo/Abp/Caching/DistributedCache.cs | 8 +-- .../Identity/AbpIdentityDomainSharedModule.cs | 3 +- .../Abp/Identity/AbpIdentityDomainModule.cs | 10 +++- ...lo.Abp.IdentityServer.Domain.Shared.csproj | 1 - .../AbpIdentityServerDomainSharedModule.cs | 7 ++- .../IdentityServer/AbpCorsPolicyService.cs | 57 +++++++++++++++++++ .../AbpIdentityServerDomainModule.cs | 1 - .../AllowedCorsOriginsCacheItem.cs | 9 +++ .../AllowedCorsOriginsCacheItemInvalidator.cs | 24 ++++++++ .../IdentityServerBuilderExtensions.cs | 3 +- .../AbpIdentityServerDomainTestBase.cs | 10 ++++ .../IdentityServer/CorsPolicyService_Tests.cs | 41 +++++++++++++ .../AbpIdentityServerMongoDbTestModule.cs | 3 +- .../AbpIdentityServerTestDataBuilder.cs | 2 +- 15 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpCorsPolicyService.cs create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItem.cs create mode 100644 modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItemInvalidator.cs create mode 100644 modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpIdentityServerDomainTestBase.cs create mode 100644 modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/CorsPolicyService_Tests.cs diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs index a9dbe670bb..d02bc76da5 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheNameAttribute.cs @@ -15,5 +15,20 @@ namespace Volo.Abp.Caching Name = name; } + + public static string GetCacheName(Type cacheItemType) + { + var cacheNameAttribute = cacheItemType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault(); + + if (cacheNameAttribute != null) + { + return cacheNameAttribute.Name; + } + + return cacheItemType.FullName.RemovePostFix("CacheItem"); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 708633b715..835ad16ca7 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -326,13 +326,7 @@ namespace Volo.Abp.Caching protected virtual void SetDefaultOptions() { - //CacheName - var cacheNameAttribute = typeof(TCacheItem) - .GetCustomAttributes(true) - .OfType() - .FirstOrDefault(); - - CacheName = cacheNameAttribute != null ? cacheNameAttribute.Name : typeof(TCacheItem).FullName; + CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem)); //IgnoreMultiTenancy IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs index bc45febd8d..efb8a7130a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Identity.Localization; +using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Users; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index ac989f8f4c..0b4c5b3fac 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -7,15 +7,19 @@ using Volo.Abp.EventBus.Distributed; using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.SettingManagement; using Volo.Abp.Settings; using Volo.Abp.Users; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Identity { - [DependsOn(typeof(AbpDddDomainModule))] - [DependsOn(typeof(AbpIdentityDomainSharedModule))] - [DependsOn(typeof(AbpUsersDomainModule))] + [DependsOn( + typeof(AbpDddDomainModule), + typeof(AbpIdentityDomainSharedModule), + typeof(AbpUsersDomainModule), + typeof(AbpSettingManagementDomainModule) + )] public class AbpIdentityDomainModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj index 8964af3fcb..6894c3f60b 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo.Abp.IdentityServer.Domain.Shared.csproj @@ -14,7 +14,6 @@ - diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs index 77ad2c698a..141bd1a5fb 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain.Shared/Volo/Abp/IdentityServer/AbpIdentityServerDomainSharedModule.cs @@ -1,13 +1,14 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.IdentityServer.Localization; +using Volo.Abp.IdentityServer.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; namespace Volo.Abp.IdentityServer { + [DependsOn( + typeof(AbpLocalizationModule) + )] public class AbpIdentityServerDomainSharedModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpCorsPolicyService.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpCorsPolicyService.cs new file mode 100644 index 0000000000..9113051a4a --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpCorsPolicyService.cs @@ -0,0 +1,57 @@ +using IdentityServer4.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IdentityServer.Clients; + +namespace Volo.Abp.IdentityServer +{ + public class AbpCorsPolicyService : ICorsPolicyService + { + public ILogger Logger { get; set; } + protected IHybridServiceScopeFactory HybridServiceScopeFactory { get; } + protected IDistributedCache Cache { get; } + + public AbpCorsPolicyService( + IDistributedCache cache, + IHybridServiceScopeFactory hybridServiceScopeFactory) + { + Cache = cache; + HybridServiceScopeFactory = hybridServiceScopeFactory; + Logger = NullLogger.Instance; + } + + public async Task IsOriginAllowedAsync(string origin) + { + var cacheItem = await Cache.GetOrAddAsync(AllowedCorsOriginsCacheItem.AllOrigins, CreateCacheItemAsync); + + var isAllowed = cacheItem.AllowedOrigins.Contains(origin, StringComparer.OrdinalIgnoreCase); + + if (!isAllowed) + { + Logger.LogWarning($"Origin is not allowed: {origin}"); + } + + return isAllowed; + } + + protected virtual async Task CreateCacheItemAsync() + { + // doing this here and not in the ctor because: https://github.com/aspnet/AspNetCore/issues/2377 + using (var scope = HybridServiceScopeFactory.CreateScope()) + { + var clientRepository = scope.ServiceProvider.GetRequiredService(); + + return new AllowedCorsOriginsCacheItem + { + AllowedOrigins = (await clientRepository.GetAllDistinctAllowedCorsOriginsAsync()).ToArray() + }; + } + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 68fd77e354..9b7ea50fc1 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -12,7 +12,6 @@ namespace Volo.Abp.IdentityServer { [DependsOn( typeof(AbpIdentityServerDomainSharedModule), - typeof(AbpDddDomainModule), typeof(AbpAutoMapperModule), typeof(AbpIdentityDomainModule), typeof(AbpSecurityModule) diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItem.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItem.cs new file mode 100644 index 0000000000..0f9babd1c2 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItem.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.IdentityServer +{ + public class AllowedCorsOriginsCacheItem + { + public const string AllOrigins = "AllOrigins"; + + public string[] AllowedOrigins { get; set; } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItemInvalidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItemInvalidator.cs new file mode 100644 index 0000000000..92583dc4df --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AllowedCorsOriginsCacheItemInvalidator.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.EventBus; +using Volo.Abp.IdentityServer.Clients; + +namespace Volo.Abp.IdentityServer +{ + public class AllowedCorsOriginsCacheItemInvalidator : ILocalEventHandler>, ITransientDependency + { + protected IDistributedCache Cache { get; } + + public AllowedCorsOriginsCacheItemInvalidator(IDistributedCache cache) + { + Cache = cache; + } + + public async Task HandleEventAsync(EntityChangedEventData eventData) + { + await Cache.RemoveAsync(AllowedCorsOriginsCacheItem.AllOrigins); + } + } +} \ No newline at end of file diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs index 04ef77e652..2168b2067e 100644 --- a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs @@ -13,7 +13,8 @@ namespace Volo.Abp.IdentityServer return builder .AddClientStore() - .AddResourceStore(); + .AddResourceStore() + .AddCorsPolicyService(); } } } \ No newline at end of file diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpIdentityServerDomainTestBase.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpIdentityServerDomainTestBase.cs new file mode 100644 index 0000000000..009c6ca468 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpIdentityServerDomainTestBase.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.IdentityServer +{ + public class AbpIdentityServerDomainTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/CorsPolicyService_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/CorsPolicyService_Tests.cs new file mode 100644 index 0000000000..0aa1e835ac --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/CorsPolicyService_Tests.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using IdentityServer4.Services; +using Shouldly; +using Volo.Abp.IdentityServer.Clients; +using Xunit; + +namespace Volo.Abp.IdentityServer +{ + public class CorsPolicyService_Tests : AbpIdentityServerDomainTestBase + { + private readonly ICorsPolicyService _corsPolicyService; + private readonly IClientRepository _clientRepository; + + public CorsPolicyService_Tests() + { + _corsPolicyService = GetRequiredService(); + _clientRepository = GetRequiredService(); + } + + [Fact] + public async Task IsOriginAllowedAsync() + { + (await _corsPolicyService.IsOriginAllowedAsync("https://client1-origin.com")).ShouldBeTrue(); + (await _corsPolicyService.IsOriginAllowedAsync("https://unknown-origin.com")).ShouldBeFalse(); + } + + [Fact] + public async Task IsOriginAllowedAsync_Should_Invalidate_Cache_On_Update() + { + //It does not exists before + (await _corsPolicyService.IsOriginAllowedAsync("https://new-origin.com")).ShouldBeFalse(); + + var client1 = await _clientRepository.FindByCliendIdAsync("ClientId1"); + client1.AddCorsOrigin("https://new-origin.com"); + await _clientRepository.UpdateAsync(client1); + + //It does exists now + (await _corsPolicyService.IsOriginAllowedAsync("https://new-origin.com")).ShouldBeTrue(); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs index cbaf71585e..b9da4bb039 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Mongo2Go; +using Mongo2Go; using Volo.Abp.Data; using Volo.Abp.IdentityServer.MongoDB; using Volo.Abp.Modularity; diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs index 42f76d44a0..0c18e85350 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs @@ -90,7 +90,7 @@ namespace Volo.Abp.IdentityServer FrontChannelLogoutUri = nameof(Client.FrontChannelLogoutUri) }; - client.AddCorsOrigin(nameof(ClientCorsOrigin.Origin)); + client.AddCorsOrigin("https://client1-origin.com"); client.AddClaim(nameof(ClientClaim.Value), nameof(ClientClaim.Type)); client.AddGrantType(nameof(ClientGrantType.GrantType)); client.AddIdentityProviderRestriction(nameof(ClientIdPRestriction.Provider));