diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs index 907e462526..2a6623863b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs @@ -122,40 +122,40 @@ public abstract class AbpPageModel : PageModel return localizer; } - protected RedirectResult RedirectSafely(string returnUrl, string? returnUrlHash = null) + protected virtual async Task RedirectSafelyAsync(string returnUrl, string? returnUrlHash = null) { - return Redirect(GetRedirectUrl(returnUrl, returnUrlHash)); + return Redirect(await GetRedirectUrlAsync(returnUrl, returnUrlHash)); } - protected virtual string GetRedirectUrl(string returnUrl, string? returnUrlHash = null) + protected virtual async Task GetRedirectUrlAsync(string returnUrl, string? returnUrlHash = null) { - returnUrl = NormalizeReturnUrl(returnUrl); + returnUrl = await NormalizeReturnUrlAsync(returnUrl); if (!returnUrlHash.IsNullOrWhiteSpace()) { - returnUrl = returnUrl + returnUrlHash; + returnUrl += returnUrlHash; } return returnUrl; } - private string NormalizeReturnUrl(string returnUrl) + protected virtual async Task NormalizeReturnUrlAsync(string returnUrl) { if (returnUrl.IsNullOrEmpty()) { - return GetAppHomeUrl(); + return await GetAppHomeUrlAsync(); } - if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl)) + if (Url.IsLocalUrl(returnUrl) || await AppUrlProvider.IsRedirectAllowedUrlAsync(returnUrl)) { return returnUrl; } - return GetAppHomeUrl(); + return await GetAppHomeUrlAsync(); } - protected virtual string GetAppHomeUrl() + protected virtual Task GetAppHomeUrlAsync() { - return "~/"; //TODO: ??? + return Task.FromResult("~/"); //TODO: ??? } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs index 0a3f042784..5513667648 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -104,14 +105,14 @@ public abstract class AbpController : Controller, IAvoidDuplicateCrossCuttingCon return localizer; } - protected virtual RedirectResult RedirectSafely(string returnUrl, string? returnUrlHash = null) + protected virtual async Task RedirectSafelyAsync(string returnUrl, string? returnUrlHash = null) { - return Redirect(GetRedirectUrl(returnUrl, returnUrlHash)); + return Redirect(await GetRedirectUrlAsync(returnUrl, returnUrlHash)); } - protected virtual string GetRedirectUrl(string returnUrl, string? returnUrlHash = null) + protected virtual async Task GetRedirectUrlAsync(string returnUrl, string? returnUrlHash = null) { - returnUrl = NormalizeReturnUrl(returnUrl); + returnUrl = await NormalizeReturnUrlAsync(returnUrl); if (!returnUrlHash.IsNullOrWhiteSpace()) { @@ -121,23 +122,23 @@ public abstract class AbpController : Controller, IAvoidDuplicateCrossCuttingCon return returnUrl; } - protected virtual string NormalizeReturnUrl(string returnUrl) + protected virtual async Task NormalizeReturnUrlAsync(string returnUrl) { if (returnUrl.IsNullOrEmpty()) { - return GetAppHomeUrl(); + return await GetAppHomeUrlAsync(); } - if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl)) + if (Url.IsLocalUrl(returnUrl) || await AppUrlProvider.IsRedirectAllowedUrlAsync(returnUrl)) { return returnUrl; } - return GetAppHomeUrl(); + return await GetAppHomeUrlAsync(); } - protected virtual string GetAppHomeUrl() + protected virtual Task GetAppHomeUrlAsync() { - return Url.Content("~/"); + return Task.FromResult(Url.Content("~/")); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs index 36b941cd4a..0c9efbc195 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs @@ -22,38 +22,38 @@ public abstract class ChallengeAccountController : AbpController } [HttpGet] - public virtual ActionResult Login(string returnUrl = "", string returnUrlHash = "") + public virtual async Task LoginAsync(string returnUrl = "", string returnUrlHash = "") { if (CurrentUser.IsAuthenticated) { - return RedirectSafely(returnUrl, returnUrlHash); + return await RedirectSafelyAsync(returnUrl, returnUrlHash); } - return Challenge(new AuthenticationProperties { RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); + return Challenge(new AuthenticationProperties { RedirectUri = await GetRedirectUrlAsync(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); } [HttpGet] - public virtual async Task Logout(string returnUrl = "", string returnUrlHash = "") + public virtual async Task LogoutAsync(string returnUrl = "", string returnUrlHash = "") { await HttpContext.SignOutAsync(); if (HttpContext.User.Identity?.AuthenticationType == AuthenticationType) { - return RedirectSafely(returnUrl, returnUrlHash); + return await RedirectSafelyAsync(returnUrl, returnUrlHash); } - return SignOut(new AuthenticationProperties { RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); + return SignOut(new AuthenticationProperties { RedirectUri = await GetRedirectUrlAsync(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); } [HttpGet] - public virtual async Task FrontChannelLogout(string sid) + public virtual async Task FrontChannelLogoutAsync(string sid) { if (User.Identity != null && User.Identity.IsAuthenticated) { var currentSid = User.FindFirst("sid")?.Value ?? string.Empty; if (string.Equals(currentSid, sid, StringComparison.Ordinal)) { - await Logout(); + await LogoutAsync(); } } @@ -61,7 +61,7 @@ public abstract class ChallengeAccountController : AbpController } [HttpGet] - public virtual Task AccessDenied() + public virtual Task AccessDeniedAsync() { return Task.FromResult(Challenge( new AuthenticationProperties @@ -78,9 +78,9 @@ public abstract class ChallengeAccountController : AbpController } [HttpGet] - public virtual async Task Challenge(string returnUrl = "", string returnUrlHash = "") + public virtual async Task ChallengeAsync(string returnUrl = "", string returnUrlHash = "") { await HttpContext.SignOutAsync(); - return Challenge(new AuthenticationProperties { RedirectUri = GetRedirectUrl(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); + return Challenge(new AuthenticationProperties { RedirectUri = await GetRedirectUrlAsync(returnUrl, returnUrlHash) }, ChallengeAuthenticationSchemas); } } 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 179eb6e9a1..e7b113c8b5 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 @@ -15,7 +15,6 @@ public class AppUrlProvider : IAppUrlProvider, ITransientDependency { protected AppUrlOptions Options { get; } protected IMultiTenantUrlProvider MultiTenantUrlProvider { get; } - public ILogger Logger { get; set; } public AppUrlProvider( @@ -37,9 +36,14 @@ public class AppUrlProvider : IAppUrlProvider, ITransientDependency ); } - public bool IsRedirectAllowedUrl(string url) + public virtual async Task IsRedirectAllowedUrlAsync(string url) { - var allow = Options.RedirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)); + var redirectAllowedUrls = new List(); + foreach (var redirectAllowedUrl in Options.RedirectAllowedUrls) + { + redirectAllowedUrls.Add((await NormalizeUrlAsync(redirectAllowedUrl))!); + } + var allow = redirectAllowedUrls.Any(x => url.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)); if (!allow) { Logger.LogError($"Invalid RedirectUrl: {url}, Use {nameof(AppUrlProvider)} to configure it!"); diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs index 4e800a524e..755cdfacaf 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs @@ -9,7 +9,7 @@ public interface IAppUrlProvider Task GetUrlOrNullAsync([NotNull] string appName, string? urlName = null); - bool IsRedirectAllowedUrl(string url); - + Task IsRedirectAllowedUrlAsync(string url); + Task NormalizeUrlAsync(string? url); } 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 53ca25e7c7..5b448aa683 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 @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.MultiTenancy; +using Volo.Abp.MultiTenancy.ConfigurationStore; using Volo.Abp.Testing; using Volo.Abp.UI.Navigation.Urls; using Xunit; @@ -15,6 +16,8 @@ public class AppUrlProvider_Tests : AbpIntegratedTest private readonly IAppUrlProvider _appUrlProvider; private readonly ICurrentTenant _currentTenant; + private readonly Guid _tenantAId = Guid.NewGuid(); + public AppUrlProvider_Tests() { _appUrlProvider = ServiceProvider.GetRequiredService(); @@ -35,12 +38,22 @@ public class AppUrlProvider_Tests : AbpIntegratedTest options.RedirectAllowedUrls.AddRange(new List() { "https://wwww.volosoft.com", - "https://wwww.aspnetzero.com" + "https://wwww.aspnetzero.com", + "https://{{tenantName}}.abp.io", + "https://{{tenantId}}.abp.io" }); options.Applications["BLAZOR"].RootUrl = "https://{{tenantId}}.abp.io"; options.Applications["BLAZOR"].Urls["PasswordReset"] = "account/reset-password"; }); + + services.Configure(options => + { + options.Tenants = new TenantConfiguration[] + { + new(_tenantAId, "community") + }; + }); } [Fact] @@ -64,14 +77,13 @@ public class AppUrlProvider_Tests : AbpIntegratedTest url.ShouldBe("https://community.abp.io/account/reset-password"); } - var tenantId = Guid.NewGuid(); - using (_currentTenant.Change(tenantId)) + using (_currentTenant.Change(_tenantAId)) { var url = await _appUrlProvider.GetUrlAsync("BLAZOR"); - url.ShouldBe($"https://{tenantId}.abp.io"); + url.ShouldBe($"https://{_tenantAId}.abp.io"); url = await _appUrlProvider.GetUrlAsync("BLAZOR", "PasswordReset"); - url.ShouldBe($"https://{tenantId}.abp.io/account/reset-password"); + url.ShouldBe($"https://{_tenantAId}.abp.io/account/reset-password"); } await Assert.ThrowsAsync(async () => @@ -87,9 +99,27 @@ public class AppUrlProvider_Tests : AbpIntegratedTest } [Fact] - public void IsRedirectAllowedUrl() + public async Task IsRedirectAllowedUrlAsync() { - _appUrlProvider.IsRedirectAllowedUrl("https://community.abp.io").ShouldBeFalse(); - _appUrlProvider.IsRedirectAllowedUrl("https://wwww.volosoft.com").ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://community.abp.io")).ShouldBeFalse(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://wwww.volosoft.com")).ShouldBeTrue(); + + using (_currentTenant.Change(null)) + { + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://www.abp.io")).ShouldBeFalse(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://abp.io")).ShouldBeTrue(); + } + + using (_currentTenant.Change(_tenantAId, "community")) + { + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://community.abp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync("https://community2.abp.io")).ShouldBeFalse(); + } + + using (_currentTenant.Change(_tenantAId)) + { + (await _appUrlProvider.IsRedirectAllowedUrlAsync($"https://{_tenantAId}.abp.io")).ShouldBeTrue(); + (await _appUrlProvider.IsRedirectAllowedUrlAsync($"https://{Guid.NewGuid()}.abp.io")).ShouldBeFalse(); + } } } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index d60c90ce60..0a2aadd64a 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -177,7 +177,7 @@ public class IdentityServerSupportedLoginModel : LoginModel // Clear the dynamic claims cache. await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); - return RedirectSafely(ReturnUrl, ReturnUrlHash); + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); } public override async Task OnPostExternalLogin(string provider) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs index efa1a7a618..cafa4fc1ea 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.cshtml.cs @@ -18,28 +18,28 @@ public class LoggedOutModel : AccountPageModel [BindProperty(SupportsGet = true)] public string PostLogoutRedirectUri { get; set; } - public virtual Task OnGetAsync() + public virtual async Task OnGetAsync() { - NormalizeUrl(); - return Task.FromResult(Page()); + await NormalizeUrlAsync(); + return Page(); } - public virtual Task OnPostAsync() + public virtual async Task OnPostAsync() { - NormalizeUrl(); - return Task.FromResult(Page()); + await NormalizeUrlAsync(); + return Page(); } - protected virtual void NormalizeUrl() + protected virtual async Task NormalizeUrlAsync() { if (!PostLogoutRedirectUri.IsNullOrWhiteSpace()) { - PostLogoutRedirectUri = Url.Content(GetRedirectUrl(PostLogoutRedirectUri)); + PostLogoutRedirectUri = Url.Content(await GetRedirectUrlAsync(PostLogoutRedirectUri)); } if(!SignOutIframeUrl.IsNullOrWhiteSpace()) { - SignOutIframeUrl = Url.Content(GetRedirectUrl(SignOutIframeUrl)); + SignOutIframeUrl = Url.Content(await GetRedirectUrlAsync(SignOutIframeUrl)); } } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 7b7d4a2b94..6ab8350b60 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -143,7 +143,7 @@ public class LoginModel : AccountPageModel // Clear the dynamic claims cache. await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); - return RedirectSafely(ReturnUrl, ReturnUrlHash); + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); } /// @@ -237,7 +237,7 @@ public class LoginModel : AccountPageModel await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); } - return RedirectSafely(returnUrl, returnUrlHash); + return await RedirectSafelyAsync(returnUrl, returnUrlHash); } //TODO: Handle other cases for result! @@ -279,7 +279,7 @@ public class LoginModel : AccountPageModel // Clear the dynamic claims cache. await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); - return RedirectSafely(returnUrl, returnUrlHash); + return await RedirectSafelyAsync(returnUrl, returnUrlHash); } protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds() diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index ce1ecd0e82..e8980cc99f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -27,7 +27,7 @@ public class LogoutModel : AccountPageModel await SignInManager.SignOutAsync(); if (ReturnUrl != null) { - return RedirectSafely(ReturnUrl, ReturnUrlHash); + return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); } if (await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin)) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs index d669ec41a1..415a6851b6 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs @@ -38,7 +38,7 @@ public class ManageModel : AccountPageModel { if (!Url.IsLocalUrl(ReturnUrl) && !ReturnUrl.StartsWith(UriHelper.BuildAbsolute(Request.Scheme, Request.Host, Request.PathBase).RemovePostFix("/")) && - !AppUrlProvider.IsRedirectAllowedUrl(ReturnUrl)) + !await AppUrlProvider.IsRedirectAllowedUrlAsync(ReturnUrl)) { ReturnUrl = null; } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/ResetPasswordConfirmation.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/ResetPasswordConfirmation.cshtml.cs index 47eff4080d..a65754b1c0 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/ResetPasswordConfirmation.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/ResetPasswordConfirmation.cshtml.cs @@ -14,10 +14,10 @@ public class ResetPasswordConfirmationModel : AccountPageModel [BindProperty(SupportsGet = true)] public string ReturnUrlHash { get; set; } - public virtual Task OnGetAsync() + public virtual async Task OnGetAsync() { - ReturnUrl = GetRedirectUrl(ReturnUrl, ReturnUrlHash); + ReturnUrl = await GetRedirectUrlAsync(ReturnUrl, ReturnUrlHash); - return Task.FromResult(Page()); + return Page(); } }