diff --git a/backend/src/Squidex.Web/UrlsOptions.cs b/backend/src/Squidex.Web/UrlsOptions.cs index 8ab432d47..b6149778b 100644 --- a/backend/src/Squidex.Web/UrlsOptions.cs +++ b/backend/src/Squidex.Web/UrlsOptions.cs @@ -5,17 +5,66 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.Collections.Generic; +using System.Linq; using Squidex.Infrastructure; namespace Squidex.Web { public sealed class UrlsOptions { + private readonly HashSet allTrustedHosts = new HashSet(StringComparer.OrdinalIgnoreCase); + private string baseUrl; + private string[] trustedHosts; + + public bool EnableXForwardedHost { get; set; } + public bool EnforceHTTPS { get; set; } - public string BaseUrl { get; set; } + public string BaseUrl + { + get + { + return baseUrl; + } + set + { + if (Uri.TryCreate(value, UriKind.Absolute, out var uri)) + { + allTrustedHosts.Add(uri.Host); + } + + baseUrl = value; + } + } - public bool EnableXForwardedHost { get; set; } + public string[] TrustedHosts + { + get + { + return trustedHosts; + } + set + { + foreach (var host in trustedHosts?.Where(x => !string.IsNullOrWhiteSpace(x)).OrEmpty()!) + { + allTrustedHosts.Add(host); + } + + trustedHosts = value; + } + } + + public bool IsAllowedHost(string? url) + { + return Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && IsAllowedHost(uri); + } + + public bool IsAllowedHost(Uri uri) + { + return !uri.IsAbsoluteUri || allTrustedHosts.Contains(uri.Host); + } public string BuildUrl(string path, bool trailingSlash = true) { diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs index 78e1b0281..eab17d6ff 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs @@ -37,6 +37,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account private readonly UserManager userManager; private readonly IUserFactory userFactory; private readonly IUserEvents userEvents; + private readonly UrlsOptions urlsOptions; private readonly MyIdentityOptions identityOptions; private readonly ISemanticLog log; private readonly IIdentityServerInteractionService interactions; @@ -46,17 +47,19 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account UserManager userManager, IUserFactory userFactory, IUserEvents userEvents, + IOptions urlsOptions, IOptions identityOptions, ISemanticLog log, IIdentityServerInteractionService interactions) { - this.log = log; - this.userEvents = userEvents; - this.userManager = userManager; - this.userFactory = userFactory; - this.interactions = interactions; this.identityOptions = identityOptions.Value; + this.interactions = interactions; this.signInManager = signInManager; + this.urlsOptions = urlsOptions.Value; + this.userEvents = userEvents; + this.userFactory = userFactory; + this.userManager = userManager; + this.log = log; } [HttpGet] @@ -404,7 +407,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account private IActionResult RedirectToReturnUrl(string? returnUrl) { - if (!string.IsNullOrWhiteSpace(returnUrl)) + if (urlsOptions.IsAllowedHost(returnUrl) || interactions.IsValidReturnUrl(returnUrl)) { return Redirect(returnUrl); }