From fa4a7853b07991ff7f8d1fe084a4ca9be6a24f6f Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 26 Aug 2024 17:04:23 +0800 Subject: [PATCH 1/3] Support wildcard domain in `AppUrlProvider `. --- .../Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs | 28 +++++++++++++ .../Abp/Ui/Navigation/Urls/AppUrlProvider.cs | 4 +- .../Volo/Abp/Http/UriHelpers_Tests.cs | 42 +++++++++++++++++++ .../Abp/Ui/Navigation/AppUrlProvider_Tests.cs | 11 +++-- 4 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs create mode 100644 framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs new file mode 100644 index 00000000000..27d8b224b74 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs @@ -0,0 +1,28 @@ +using System; + +namespace Volo.Abp.Http; + +public static class UriHelpers +{ + private const string WildcardSubdomain = "*."; + + public static bool IsSubdomainOf(string subdomain, string domain) + { + if (Uri.TryCreate(subdomain, UriKind.Absolute, out var subdomainUri) && + Uri.TryCreate(domain.Replace(WildcardSubdomain, string.Empty), UriKind.Absolute, out var domainUri)) + { + return domainUri == subdomainUri || IsSubdomainOf(subdomainUri, domainUri); + } + + return false; + } + + public static bool IsSubdomainOf(Uri subdomain, Uri domain) + { + return subdomain.IsAbsoluteUri + && domain.IsAbsoluteUri + && subdomain.Scheme == domain.Scheme + && subdomain.Port == domain.Port + && subdomain.Host.EndsWith($".{domain.Host}", StringComparison.Ordinal); + } +} diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs index e7b113c8b54..05bcec23f56 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; using Volo.Abp.MultiTenancy; namespace Volo.Abp.UI.Navigation.Urls; @@ -43,7 +44,8 @@ public virtual async Task IsRedirectAllowedUrlAsync(string url) { redirectAllowedUrls.Add((await NormalizeUrlAsync(redirectAllowedUrl))!); } - var allow = redirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)); + var allow = redirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase) || + UriHelpers.IsSubdomainOf(url, x)); if (!allow) { Logger.LogError($"Invalid RedirectUrl: {url}, Use {nameof(AppUrlProvider)} to configure it!"); diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs new file mode 100644 index 00000000000..2a917cf7657 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs @@ -0,0 +1,42 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.Http; + +public class UriHelpers_Tests +{ + [Theory] + [InlineData(null)] + [InlineData("null")] + [InlineData("http://")] + [InlineData("http://*")] + [InlineData("http://.domain")] + [InlineData("http://.domain/hello")] + public void IsSubdomainOf_ReturnsFalseIfDomainIsMalformedUri(string domain) + { + var actual = UriHelpers.IsSubdomainOf("http://*.domain", domain); + actual.ShouldBeFalse(); + } + + [Theory] + [InlineData("http://sub.domain", "http://*.domain")] + [InlineData("http://sub.sub.domain", "http://*.domain")] + [InlineData("http://sub.sub.domain", "http://*.sub.domain")] + [InlineData("http://sub.domain:4567", "http://*.domain:4567")] + public void IsSubdomainOf_ReturnsTrue_WhenASubdomain(string subdomain, string domain) + { + var actual = UriHelpers.IsSubdomainOf(subdomain, domain); + actual.ShouldBeTrue(); + } + + [Theory] + [InlineData("http://sub.domain:1234", "http://*.domain:5678")] + [InlineData("http://sub.domain", "http://domain.*")] + [InlineData("http://sub.domain.hacker", "http://*.domain")] + [InlineData("https://sub.domain", "http://*.domain")] + public void IsSubdomainOf_ReturnsFalse_WhenNotASubdomain(string subdomain, string domain) + { + var actual = UriHelpers.IsSubdomainOf(subdomain, domain); + actual.ShouldBeFalse(); + } +} diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AppUrlProvider_Tests.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AppUrlProvider_Tests.cs index 5b448aa683d..eb3698d23bb 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AppUrlProvider_Tests.cs +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AppUrlProvider_Tests.cs @@ -40,7 +40,8 @@ protected override void AfterAddApplication(IServiceCollection services) "https://wwww.volosoft.com", "https://wwww.aspnetzero.com", "https://{{tenantName}}.abp.io", - "https://{{tenantId}}.abp.io" + "https://{{tenantId}}.abp.io", + "https://*.demo.myabp.io" }); options.Applications["BLAZOR"].RootUrl = "https://{{tenantId}}.abp.io"; @@ -101,12 +102,16 @@ public async Task GetUrlOrNullAsync() [Fact] public async Task IsRedirectAllowedUrlAsync() { - (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://community.abp.io")).ShouldBeFalse(); (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://wwww.volosoft.com")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://wwww.demo.myabp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://demo.myabp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://api.demo.myabp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://test.api.demo.myabp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://volosoft.com/demo.myabp.io")).ShouldBeFalse(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://wwww.myabp.io")).ShouldBeFalse(); using (_currentTenant.Change(null)) { - (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://www.abp.io")).ShouldBeFalse(); (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://abp.io")).ShouldBeTrue(); } From 270afea410e7174aa955747b361f4d60b8df1f95 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 26 Aug 2024 17:39:06 +0800 Subject: [PATCH 2/3] Rename to `UrlHelper`. --- .../Volo/Abp/Http/{UriHelpers.cs => UrlHelpers.cs} | 2 +- .../Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs | 2 +- .../Abp/Http/{UriHelpers_Tests.cs => UrlHelpers_Tests.cs} | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename framework/src/Volo.Abp.Core/Volo/Abp/Http/{UriHelpers.cs => UrlHelpers.cs} (96%) rename framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/{UriHelpers_Tests.cs => UrlHelpers_Tests.cs} (84%) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs similarity index 96% rename from framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs rename to framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs index 27d8b224b74..30e5e81be2c 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Http/UriHelpers.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Http; -public static class UriHelpers +public static class UrlHelpers { private const string WildcardSubdomain = "*."; diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs index 05bcec23f56..2991e843317 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs @@ -45,7 +45,7 @@ public virtual async Task IsRedirectAllowedUrlAsync(string url) redirectAllowedUrls.Add((await NormalizeUrlAsync(redirectAllowedUrl))!); } var allow = redirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase) || - UriHelpers.IsSubdomainOf(url, x)); + UrlHelpers.IsSubdomainOf(url, x)); if (!allow) { Logger.LogError($"Invalid RedirectUrl: {url}, Use {nameof(AppUrlProvider)} to configure it!"); diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs similarity index 84% rename from framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs rename to framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs index 2a917cf7657..19efd9a7840 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UriHelpers_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs @@ -3,7 +3,7 @@ namespace Volo.Abp.Http; -public class UriHelpers_Tests +public class UrlHelpers_Tests { [Theory] [InlineData(null)] @@ -14,7 +14,7 @@ public class UriHelpers_Tests [InlineData("http://.domain/hello")] public void IsSubdomainOf_ReturnsFalseIfDomainIsMalformedUri(string domain) { - var actual = UriHelpers.IsSubdomainOf("http://*.domain", domain); + var actual = UrlHelpers.IsSubdomainOf("http://*.domain", domain); actual.ShouldBeFalse(); } @@ -25,7 +25,7 @@ public void IsSubdomainOf_ReturnsFalseIfDomainIsMalformedUri(string domain) [InlineData("http://sub.domain:4567", "http://*.domain:4567")] public void IsSubdomainOf_ReturnsTrue_WhenASubdomain(string subdomain, string domain) { - var actual = UriHelpers.IsSubdomainOf(subdomain, domain); + var actual = UrlHelpers.IsSubdomainOf(subdomain, domain); actual.ShouldBeTrue(); } @@ -36,7 +36,7 @@ public void IsSubdomainOf_ReturnsTrue_WhenASubdomain(string subdomain, string do [InlineData("https://sub.domain", "http://*.domain")] public void IsSubdomainOf_ReturnsFalse_WhenNotASubdomain(string subdomain, string domain) { - var actual = UriHelpers.IsSubdomainOf(subdomain, domain); + var actual = UrlHelpers.IsSubdomainOf(subdomain, domain); actual.ShouldBeFalse(); } } From 52c95546ff42d3aaaf9e4e26bc44d3df9907efcc Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 27 Aug 2024 09:00:40 +0800 Subject: [PATCH 3/3] Update UrlHelpers_Tests.cs --- .../test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs index 19efd9a7840..51251f84d48 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Http/UrlHelpers_Tests.cs @@ -19,6 +19,7 @@ public void IsSubdomainOf_ReturnsFalseIfDomainIsMalformedUri(string domain) } [Theory] + [InlineData("http://sub.domain", "http://domain")] [InlineData("http://sub.domain", "http://*.domain")] [InlineData("http://sub.sub.domain", "http://*.domain")] [InlineData("http://sub.sub.domain", "http://*.sub.domain")]