diff --git a/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index c7021e85f0..b522899251 100644 --- a/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -18,7 +18,7 @@ - + \ No newline at end of file diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 384a251721..012282784f 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.IdentityServer { services.Configure(options => { - options.AddProfile(/*TODO: validate: true*/); + options.AddProfile(validate: true); }); services.AddAssemblyOf(); diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs new file mode 100644 index 0000000000..759bbbdf02 --- /dev/null +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/ApiResources/IApiResourceRepository.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.ApiResources +{ + public interface IApiResourceRepository : IRepository + { + + } +} \ No newline at end of file diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs index fbec0430b8..b76f22e5af 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs @@ -1,4 +1,9 @@ -using AutoMapper; +using System.Collections.Generic; +using System.Security.Claims; +using AutoMapper; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Grants; +using Volo.Abp.IdentityServer.IdentityResources; namespace Volo.Abp.IdentityServer.Clients { @@ -12,6 +17,64 @@ namespace Volo.Abp.IdentityServer.Clients .ConstructUsing(src => src.Origin) .ReverseMap() .ForMember(dest => dest.Origin, opt => opt.MapFrom(src => src)); + + + CreateMap() + .ForMember(dest => dest.ApiSecrets, opt => opt.MapFrom(src => src.Secrets)); + + + CreateMap().ReverseMap(); + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap>() + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.ProtocolType, opt => opt.Condition(srs => srs != null)) + .ReverseMap(); + + CreateMap() + .ConstructUsing(src => src.Origin) + .ReverseMap() + .ForMember(dest => dest.Origin, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.Provider) + .ReverseMap() + .ForMember(dest => dest.Provider, opt => opt.MapFrom(src => src)); + + CreateMap(MemberList.None) + .ConstructUsing(src => new Claim(src.Type, src.Value)) + .ReverseMap(); + + CreateMap() + .ConstructUsing(src => src.Scope) + .ReverseMap() + .ForMember(dest => dest.Scope, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.PostLogoutRedirectUri) + .ReverseMap() + .ForMember(dest => dest.PostLogoutRedirectUri, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.RedirectUri) + .ReverseMap() + .ForMember(dest => dest.RedirectUri, opt => opt.MapFrom(src => src)); + + CreateMap() + .ConstructUsing(src => src.GrantType) + .ReverseMap() + .ForMember(dest => dest.GrantType, opt => opt.MapFrom(src => src)); + + CreateMap(MemberList.Destination) + .ForMember(dest => dest.Type, opt => opt.Condition(srs => srs != null)) + .ReverseMap(); } } } diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs new file mode 100644 index 0000000000..709fe00d62 --- /dev/null +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.IdentityServer.Grants +{ + public interface IPersistentGrantRepository : IRepository + { + Task FindByKeyAsync(string key); + + Task> GetListBySubjectIdAsync(string key); + + Task DeleteAsync(string subjectId, string clientId); + + Task DeleteAsync(string subjectId, string clientId, string type); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs new file mode 100644 index 0000000000..f312132900 --- /dev/null +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/PersistedGrantStore.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using IdentityServer4.Stores; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.IdentityServer.Grants +{ + public class PersistedGrantStore : IPersistedGrantStore, ITransientDependency + { + private readonly IPersistentGrantRepository _persistentGrantRepository; + private readonly IObjectMapper _objectMapper; + + public PersistedGrantStore(IPersistentGrantRepository persistentGrantRepository, IObjectMapper objectMapper) + { + _persistentGrantRepository = persistentGrantRepository; + _objectMapper = objectMapper; + } + + public virtual async Task StoreAsync(IdentityServer4.Models.PersistedGrant grant) + { + var entity = _objectMapper.Map(grant); + var existing = await _persistentGrantRepository.FindByKeyAsync(grant.Key); + if (existing == null) + { + await _persistentGrantRepository.InsertAsync(entity); + } + else + { + await _persistentGrantRepository.UpdateAsync(entity); + } + } + + public virtual async Task GetAsync(string key) + { + var persistedGrant = await _persistentGrantRepository.FindByKeyAsync(key); + return _objectMapper.Map(persistedGrant); + } + + public virtual async Task> GetAllAsync(string subjectId) + { + var persistedGrants = await _persistentGrantRepository.GetListBySubjectIdAsync(subjectId); + return persistedGrants.Select(x => _objectMapper.Map(x)); + } + + public virtual async Task RemoveAsync(string key) + { + var persistedGrant = await _persistentGrantRepository.FindByKeyAsync(key); + if (persistedGrant == null) + { + return; + } + + await _persistentGrantRepository.DeleteAsync(persistedGrant); + } + + public virtual async Task RemoveAllAsync(string subjectId, string clientId) + { + await _persistentGrantRepository.DeleteAsync(subjectId, clientId); + } + + public virtual async Task RemoveAllAsync(string subjectId, string clientId, string type) + { + await _persistentGrantRepository.DeleteAsync(subjectId, clientId, type); + } + } +} diff --git a/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResourceRepository.cs b/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResourceRepository.cs new file mode 100644 index 0000000000..bd62b96b6b --- /dev/null +++ b/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/ApiResourceRepository.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.EntityFrameworkCore; + +namespace Volo.Abp.IdentityServer +{ + public class ApiResourceRepository : EfCoreRepository, IApiResourceRepository + { + public ApiResourceRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/PersistedGrantRepository.cs b/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/PersistedGrantRepository.cs new file mode 100644 index 0000000000..0b1011992c --- /dev/null +++ b/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/PersistedGrantRepository.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.IdentityServer.Grants; + +namespace Volo.Abp.IdentityServer +{ + public class PersistentGrantRepository : EfCoreRepository, IPersistentGrantRepository + { + public PersistentGrantRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + + } + + public Task FindByKeyAsync(string key) + { + return DbSet.FirstOrDefaultAsync(x => x.Key == key); + } + + public Task> GetListBySubjectIdAsync(string subjectId) + { + return DbSet.Where(x => x.SubjectId == subjectId).ToListAsync(); + } + + public Task DeleteAsync(string subjectId, string clientId) + { + DbSet.RemoveRange( + DbSet.Where(x => x.SubjectId == subjectId && x.ClientId == clientId) + ); + + return Task.FromResult(0); + } + + public Task DeleteAsync(string subjectId, string clientId, string type) + { + DbSet.RemoveRange( + DbSet.Where(x => + x.SubjectId == subjectId && + x.ClientId == clientId && + x.Type == type) + ); + + return Task.FromResult(0); + } + } +} diff --git a/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs index 6fc4153dc8..b3f1fa420b 100644 --- a/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs +++ b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs @@ -1,26 +1,36 @@ using System.Collections.Generic; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; +using Volo.Abp.IdentityServer.ApiResources; using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.Grants; namespace Volo.Abp.IdentityServer { public class AbpIdentityServerTestDataBuilder : ITransientDependency { - private readonly IClientRepository _clientRepository; private readonly IGuidGenerator _guidGenerator; + private readonly IClientRepository _clientRepository; + private readonly IPersistentGrantRepository _persistentGrantRepository; + private readonly IApiResourceRepository _apiResourceRepository; public AbpIdentityServerTestDataBuilder( - IClientRepository clientRepository, - IGuidGenerator guidGenerator) + IClientRepository clientRepository, + IGuidGenerator guidGenerator, + IPersistentGrantRepository persistentGrantRepository, + IApiResourceRepository apiResourceRepository) { _clientRepository = clientRepository; _guidGenerator = guidGenerator; + _persistentGrantRepository = persistentGrantRepository; + _apiResourceRepository = apiResourceRepository; } public void Build() { AddClients(); + AddPersistentGrants(); + AddApiResources(); } private void AddClients() @@ -37,8 +47,77 @@ namespace Volo.Abp.IdentityServer Origin = "Origin1" } ); - + _clientRepository.Insert(client42); } + + private void AddPersistentGrants() + { + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "38", + ClientId = "TestClientId-38", + Type = "TestType-38", + SubjectId = "TestSubject", + Data = "TestData-38" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "37", + ClientId = "TestClientId-37", + Type = "TestType-37", + SubjectId = "TestSubject", + Data = "TestData-37" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "36", + ClientId = "TestClientId-X", + Type = "TestType-36", + SubjectId = "TestSubject-X", + Data = "TestData-36" + }); + + _persistentGrantRepository.Insert(new PersistedGrant(_guidGenerator.Create()) + { + Key = "35", + ClientId = "TestClientId-X", + Type = "TestType-35", + SubjectId = "TestSubject-X", + Data = "TestData-35" + }); + } + + private void AddApiResources() + { + _apiResourceRepository.Insert(new ApiResource(_guidGenerator.Create()) + { + Name = "Test-ApiResource-Name-1", + Enabled = true, + Description = "Test-ApiResource-Description-1", + DisplayName = "Test-ApiResource-DisplayName-1", + Secrets = new List + { + new ApiSecret(_guidGenerator.Create()) + }, + UserClaims = new List + { + new ApiResourceClaim(_guidGenerator.Create()) + { + Type = "Test-ApiResource-Claim-Type-1" + } + }, + Scopes = new List + { + new ApiScope(_guidGenerator.Create()) + { + Name = "Test-ApiResource-ApiScope-Name-1", + DisplayName = "Test-ApiResource-ApiScope-DisplayName-1" + } + } + }); + } } } diff --git a/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs new file mode 100644 index 0000000000..bd15fed89b --- /dev/null +++ b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/IdentityResourceStore_Tests.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class IdentityResourceStore_Tests : AbpIdentityServerTestBase + { + private readonly IResourceStore _resourceStore; + + public IdentityResourceStore_Tests() + { + _resourceStore = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindApiResourceAsync_Should_Return_Null_If_Not_Found() + { + //Act + var resource = await _resourceStore.FindApiResourceAsync("non-existing-name"); + + //Assert + resource.ShouldBeNull(); + } + + [Fact] + public async Task FindApiResourceAsync_Should_Return_If_Found() + { + //Act + var apiResource = await _resourceStore.FindApiResourceAsync("Test-ApiResource-Name-1"); + + //Assert + apiResource.ShouldNotBe(null); + apiResource.Name.ShouldBe("Test-ApiResource-Name-1"); + apiResource.Description.ShouldBe("Test-ApiResource-Description-1"); + apiResource.DisplayName.ShouldBe("Test-ApiResource-DisplayName-1"); + } + + [Fact] + public async Task FindApiResourcesByScopeAsync_Should_Return_If_Found() + { + //Act + var apiResourcesByScope = await _resourceStore.FindApiResourcesByScopeAsync(new List + { + "Test-ApiResource-ApiScope-Name-1" + }); + + //Assert + var apiResources = apiResourcesByScope as ApiResource[] ?? apiResourcesByScope.ToArray(); + apiResources.ShouldNotBe(null); + + apiResources[0].Scopes.GroupBy(x => x.Name).Count().ShouldBe(1); + apiResources[0].Scopes.GroupBy(x => x.Name).First().Key.ShouldBe("Test-ApiResource-ApiScope-Name-1"); + } + + [Fact] + public async Task FindIdentityResourcesByScopeAsync_Should_Return_For_Given_Scopes() + { + //Act + var identityResourcesByScope = await _resourceStore.FindIdentityResourcesByScopeAsync(new List + { + "Test-Identity-Resource-Name-1" + }); + + //Assert + var resourcesByScope = identityResourcesByScope as IdentityResource[] ?? identityResourcesByScope.ToArray(); + resourcesByScope.Length.ShouldBe(1); + resourcesByScope.First().DisplayName.ShouldBe("Test-Identity-Resource-DisplayName-1"); + resourcesByScope.First().Description.ShouldBe("Test-Identity-Resource-Description-1"); + resourcesByScope.First().Required.ShouldBe(true); + } + + [Fact] + public async Task GetAllResourcesAsync_Should_Return() + { + //Act + var resources = await _resourceStore.GetAllResourcesAsync(); + + //Assert + resources.ShouldNotBe(null); + resources.ApiResources.Count.ShouldBe(1); + resources.ApiResources.First().Name.ShouldBe("Test-ApiResource-Name-1"); + resources.IdentityResources.First().Name.ShouldBe("Test-Identity-Resource-Name-1"); + resources.IdentityResources.First().Required.ShouldBe(true); + } + } +} diff --git a/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs new file mode 100644 index 0000000000..9a87e30d64 --- /dev/null +++ b/test/Abp.IdentityServer.EntityFrameworkCore.Tests/Volo/Abp/IdentityServer/Clients/PersistentGrant_Tests.cs @@ -0,0 +1,130 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer.Clients +{ + public class PersistentGrantStore_Tests : AbpIdentityServerTestBase + { + private readonly IPersistedGrantStore _persistedGrantStore; + + public PersistentGrantStore_Tests() + { + _persistedGrantStore = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindClientByIdAsync_Should_Return_Null_If_Not_Found() + { + var persistentGrant = await _persistedGrantStore.GetAsync("not-existing-id"); + persistentGrant.ShouldBeNull(); + } + + [Fact] + public async Task FindPersistentGrantByIdAsync_Should_Return_The_PersistentGrant_If_Found() + { + //Act + var client = await _persistedGrantStore.GetAsync("38"); + + //Assert + client.ShouldNotBeNull(); + client.ClientId.ShouldBe("TestClientId-38"); + client.SubjectId.ShouldBe("TestSubject"); + client.Data.ShouldContain("TestData-38"); + client.Type.ShouldContain("TestType-38"); + } + + [Fact] + public async Task StoreAsync_Should_Store_PersistedGrant() + { + //Act + await _persistedGrantStore.StoreAsync(new PersistedGrant + { + Key = "39", + ClientId = "TestClientId-39", + Type = "TestType-39", + SubjectId = "TestSubject", + Data = "TestData-39", + Expiration = new DateTime(2018, 1, 6, 21, 22, 23), + CreationTime = new DateTime(2018, 1, 5, 19, 20, 21) + }); + + //Assert + var persistedGrant = await _persistedGrantStore.GetAsync("39"); + persistedGrant.Key.ShouldBe("39"); + persistedGrant.ClientId.ShouldBe("TestClientId-39"); + persistedGrant.Type.ShouldBe("TestType-39"); + persistedGrant.SubjectId.ShouldBe("TestSubject"); + persistedGrant.Data.ShouldBe("TestData-39"); + + persistedGrant.Expiration.HasValue.ShouldBe(true); + persistedGrant.Expiration.Value.Year.ShouldBe(2018); + persistedGrant.Expiration.Value.Month.ShouldBe(1); + persistedGrant.Expiration.Value.Day.ShouldBe(6); + persistedGrant.Expiration.Value.Hour.ShouldBe(21); + persistedGrant.Expiration.Value.Minute.ShouldBe(22); + persistedGrant.Expiration.Value.Second.ShouldBe(23); + + persistedGrant.CreationTime.Year.ShouldBe(2018); + persistedGrant.CreationTime.Month.ShouldBe(1); + persistedGrant.CreationTime.Day.ShouldBe(5); + persistedGrant.CreationTime.Hour.ShouldBe(19); + persistedGrant.CreationTime.Minute.ShouldBe(20); + persistedGrant.CreationTime.Second.ShouldBe(21); + } + + [Fact] + public async Task GetAllAsync_Should_Get_All_PersistedGrants_For_A_Given_SubjectId() + { + //Act + var persistentGrants = await _persistedGrantStore.GetAllAsync("TestSubject"); + + //Assert + var persistedGrants = persistentGrants as PersistedGrant[] ?? persistentGrants.ToArray(); + persistedGrants.ShouldNotBe(null); + persistedGrants.Length.ShouldBe(2); + persistedGrants[0].SubjectId.ShouldBe("TestSubject"); + persistedGrants[1].SubjectId.ShouldBe("TestSubject"); + } + + [Fact] + public async Task RemoveAsync_Should_Remove_PeristedGrant() + { + //Arrange + await _persistedGrantStore.StoreAsync(new PersistedGrant + { + Key = "#1P3R" + }); + + //Act + await _persistedGrantStore.RemoveAsync("#1P3R"); + + //Assert + var persistedGrant = await _persistedGrantStore.GetAsync("#1P3R"); + persistedGrant.ShouldBe(null); + } + + [Fact] + public async Task RemoveAllAsync_Should_RemoveAll_PeristedGrants_For_A_Given_Subject_And_ClientId() + { + //Arrange + var persistedGrantsWithTestSubjectX = await _persistedGrantStore.GetAllAsync("TestSubject-X"); + var persistedGrantsWithTestSubjectXBeforeLength = persistedGrantsWithTestSubjectX.ToArray().Length; + + //Act + await _persistedGrantStore.RemoveAllAsync("TestSubject-X", "TestClientId-X"); + + //Assert + persistedGrantsWithTestSubjectXBeforeLength.ShouldBe(2); + + var persistedGrants = (await _persistedGrantStore.GetAllAsync("TestClientId-37")).ToArray(); + persistedGrants.ShouldNotBe(null); + persistedGrants.Length.ShouldBe(0); + } + } +}