diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerServiceCollectionExtensions.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerServiceCollectionExtensions.cs new file mode 100644 index 0000000000..ec45ee32b6 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerServiceCollectionExtensions.cs @@ -0,0 +1,14 @@ +using IdentityServer4.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Volo.Abp.IdentityServer +{ + public static class AbpIdentityServerServiceCollectionExtensions + { + public static void AddAbpStrictRedirectUriValidator(this IServiceCollection services) + { + services.Replace(ServiceDescriptor.Transient()); + } + } +} diff --git a/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator.cs b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator.cs new file mode 100644 index 0000000000..e67740f7f1 --- /dev/null +++ b/modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Validation; +using Microsoft.Extensions.Options; +using Volo.Abp.Text.Formatting; + +namespace Volo.Abp.IdentityServer +{ + public class AbpStrictRedirectUriValidator : StrictRedirectUriValidator + { + public override async Task IsRedirectUriValidAsync(string requestedUri, Client client) + { + var isAllowed = await base.IsRedirectUriValidAsync(requestedUri, client); + return isAllowed || await IsRedirectUriValidWithDomainFormatsAsync(client.RedirectUris, requestedUri); + } + + public override async Task IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client) + { + var isAllowed = await base.IsPostLogoutRedirectUriValidAsync(requestedUri, client); + return isAllowed || await IsRedirectUriValidWithDomainFormatsAsync(client.PostLogoutRedirectUris, requestedUri); + } + + protected virtual Task IsRedirectUriValidWithDomainFormatsAsync(IEnumerable uris, string requestedUri) + { + if (uris == null) + { + return Task.FromResult(false); + } + + foreach (var url in uris) + { + var extractResult = FormattedStringValueExtracter.Extract(requestedUri, url, ignoreCase: true); + if (extractResult.IsMatch) + { + return Task.FromResult(true); + } + } + + return Task.FromResult(false); + } + } +} diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator_Tests.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator_Tests.cs new file mode 100644 index 0000000000..447b0bd008 --- /dev/null +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/AbpStrictRedirectUriValidator_Tests.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Validation; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.IdentityServer +{ + public class AbpStrictRedirectUriValidator_Tests : AbpIdentityServerTestBase + { + private readonly IRedirectUriValidator _abpStrictRedirectUriValidator; + + private readonly Client _testClient = new Client + { + RedirectUris = new List + { + "https://{0}.api.abp.io:8080/signin-oidc", + "http://{0}.ng.abp.io/index.html" + }, + PostLogoutRedirectUris = new List + { + "https://{0}.api.abp.io:8080/signin-oidc", + "http://{0}.ng.abp.io/index.html" + } + }; + + public AbpStrictRedirectUriValidator_Tests() + { + _abpStrictRedirectUriValidator = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.AddAbpStrictRedirectUriValidator(); + } + + [Fact] + public void Should_Register_AbpStrictRedirectUriValidator() + { + _abpStrictRedirectUriValidator.GetType().ShouldBe(typeof(AbpStrictRedirectUriValidator)); + } + + [Fact] + public async Task IsRedirectUriValidAsync() + { + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://t1.api.abp.io:8080/signin-oidc", _testClient)).ShouldBeTrue(); + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://t2.ng.abp.io/index.html", _testClient)).ShouldBeTrue(); + + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://api.abp:8080/", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://ng.abp.io", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("https://api.t1.abp:8080/", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsRedirectUriValidAsync("http://ng.t1.abp.io", _testClient)).ShouldBeFalse(); + } + + [Fact] + public async Task IsPostLogoutRedirectUriValidAsync() + { + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://t1.api.abp.io:8080/signin-oidc", _testClient)).ShouldBeTrue(); + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://t2.ng.abp.io/index.html", _testClient)).ShouldBeTrue(); + + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://api.abp:8080/", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://ng.abp.io", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("https://api.t1.abp:8080/", _testClient)).ShouldBeFalse(); + (await _abpStrictRedirectUriValidator.IsPostLogoutRedirectUriValidAsync("http://ng.t1.abp.io", _testClient)).ShouldBeFalse(); + } + } +}