From 9e04230d998590560a7f409316490558137cf18b Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 13 Jan 2022 07:18:37 -0500 Subject: [PATCH] added interop method for setting scroll position, persisted RemoteIPAddress in PageState so it is available on Blazor Server, added support for forwarded headers from load balancers and proxy servers, replaced DateTime.Now references DateTimeUtcNow for consistency, fixed issue where upgrade logic was being executed for prior version --- Oqtane.Client/App.razor | 36 ++++++++++--------- Oqtane.Client/Modules/Admin/Site/Index.razor | 1 + .../Modules/Admin/SystemInfo/Index.razor | 8 ++--- .../Modules/Admin/SystemInfo/Index.resx | 4 +-- Oqtane.Client/UI/Interop.cs | 17 +++++++++ Oqtane.Client/UI/PageState.cs | 1 + Oqtane.Client/UI/SiteRouter.razor | 3 +- Oqtane.Client/UI/ThemeBuilder.razor | 2 +- Oqtane.Server/Controllers/SystemController.cs | 2 +- .../Infrastructure/DatabaseManager.cs | 3 +- Oqtane.Server/Pages/_Host.cshtml.cs | 13 +++---- Oqtane.Server/Repository/LogRepository.cs | 2 +- Oqtane.Server/Repository/VisitorRepository.cs | 2 +- Oqtane.Server/Startup.cs | 12 +++++-- Oqtane.Server/wwwroot/js/interop.js | 7 ++++ Oqtane.Shared/Shared/SiteState.cs | 2 +- 16 files changed, 75 insertions(+), 40 deletions(-) diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 14f64da5..92b73433 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -30,28 +30,32 @@ } @code { - [Parameter] - public string AntiForgeryToken { get; set; } + [Parameter] + public string AntiForgeryToken { get; set; } - [Parameter] - public string Runtime { get; set; } + [Parameter] + public string Runtime { get; set; } - [Parameter] - public string RenderMode { get; set; } + [Parameter] + public string RenderMode { get; set; } - [Parameter] - public int VisitorId { get; set; } + [Parameter] + public int VisitorId { get; set; } - private bool _initialized = false; - private string _display = "display: none;"; - private Installation _installation = new Installation { Success = false, Message = "" }; + [Parameter] + public string RemoteIPAddress { get; set; } - private PageState PageState { get; set; } + private bool _initialized = false; + private string _display = "display: none;"; + private Installation _installation = new Installation { Success = false, Message = "" }; - protected override async Task OnParametersSetAsync() - { - SiteState.AntiForgeryToken = AntiForgeryToken; - InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); + private PageState PageState { get; set; } + + protected override async Task OnParametersSetAsync() + { + SiteState.RemoteIPAddress = RemoteIPAddress; + SiteState.AntiForgeryToken = AntiForgeryToken; + InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); _installation = await InstallationService.IsInstalled(); if (_installation.Alias != null) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 108c4b02..63ce2afc 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -533,6 +533,7 @@ else { AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); + await interop.ScrollTo(0, 0, "smooth"); } } } diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 61728e5f..ef09cd2a 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -33,7 +33,7 @@
- +
@@ -123,11 +123,7 @@ _clrversion = systeminfo["clrversion"]; _osversion = systeminfo["osversion"]; _serverpath = systeminfo["serverpath"]; - _servertime = systeminfo["servertime"]; - if (DateTime.TryParse(_servertime, out DateTime date)) - { - _servertime += " (" + date.ToUniversalTime().ToString() + " UTC)"; - } + _servertime = systeminfo["servertime"] + " UTC"; _installationid = systeminfo["installationid"]; _detailederrors = systeminfo["detailederrors"]; diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 724b58dd..e564fcf8 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -133,7 +133,7 @@ Server Path - Server Time + Server Date/Time (in UTC) Framework Version: @@ -148,7 +148,7 @@ Server Path: - Server Time: + Server Date/Time: Restart Application diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 2aa9fb9f..28b72307 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -262,5 +262,22 @@ namespace Oqtane.UI return Task.CompletedTask; } } + + public Task ScrollTo(int top, int left, string behavior) + { + try + { + if (string.IsNullOrEmpty(behavior)) behavior = "smooth"; + _jsRuntime.InvokeVoidAsync( + "Oqtane.Interop.scrollTo", + top, left, behavior); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + } } diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index bc0fdacf..5b14ad00 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -21,5 +21,6 @@ namespace Oqtane.UI public DateTime LastSyncDate { get; set; } public Oqtane.Shared.Runtime Runtime { get; set; } public int VisitorId { get; set; } + public string RemoteIPAddress { get; set; } } } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index a5339642..360ff6a1 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -229,7 +229,8 @@ EditMode = editmode, LastSyncDate = lastsyncdate, Runtime = runtime, - VisitorId = VisitorId + VisitorId = VisitorId, + RemoteIPAddress = SiteState.RemoteIPAddress }; OnStateChange?.Invoke(_pagestate); diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index a5437288..77f95a6c 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -31,7 +31,7 @@ var interop = new Interop(JsRuntime); // manage stylesheets for this page - string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); var links = new List(); foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global)) { diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index 7a95cc5c..6ec0fe11 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -31,7 +31,7 @@ namespace Oqtane.Controllers systeminfo.Add("osversion", Environment.OSVersion.ToString()); systeminfo.Add("machinename", Environment.MachineName); systeminfo.Add("serverpath", _environment.ContentRootPath); - systeminfo.Add("servertime", DateTime.Now.ToString()); + systeminfo.Add("servertime", DateTime.UtcNow.ToString()); systeminfo.Add("installationid", _configManager.GetInstallationId()); systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server")); diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index a4a94e61..cad82ac9 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -441,8 +441,7 @@ namespace Oqtane.Infrastructure var index = Array.FindIndex(versions, item => item == version); if (index != (versions.Length - 1)) { - if (index == -1) index = 0; - for (var i = index; i < versions.Length; i++) + for (var i = (index + 1); i < versions.Length; i++) { upgrades.Upgrade(tenant, versions[i]); } diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 66c4b0cc..852b23dd 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -56,6 +56,7 @@ namespace Oqtane.Pages public string Runtime = "Server"; public RenderMode RenderMode = RenderMode.Server; public int VisitorId = -1; + public string RemoteIPAddress = ""; public string HeadResources = ""; public string BodyResources = ""; public string Title = ""; @@ -66,6 +67,7 @@ namespace Oqtane.Pages public IActionResult OnGet() { AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken; + RemoteIPAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? ""; if (_configuration.GetSection("Runtime").Exists()) { @@ -194,12 +196,11 @@ namespace Oqtane.Pages private void TrackVisitor(int SiteId) { // get request attributes - string ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? ""; string useragent = (Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Request.Headers[HeaderNames.UserAgent] : ""; string language = (Request.Headers[HeaderNames.AcceptLanguage] != StringValues.Empty) ? Request.Headers[HeaderNames.AcceptLanguage] : ""; language = (language.Contains(",")) ? language.Substring(0, language.IndexOf(",")) : language; language = (language.Contains(";")) ? language.Substring(0, language.IndexOf(";")) : language; - language = (language.Trim().Length == 0) ? "*" : language; + language = (language.Trim().Length == 0) ? "??" : language; // filter var filter = _settings.GetSetting(EntityNames.Site, SiteId, "VisitorFilter"); @@ -207,7 +208,7 @@ namespace Oqtane.Pages { foreach (string term in filter.SettingValue.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray()) { - if (ip.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term)) + if (RemoteIPAddress.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term)) { return; } @@ -227,7 +228,7 @@ namespace Oqtane.Pages { var visitor = new Visitor(); visitor.SiteId = SiteId; - visitor.IPAddress = ip; + visitor.IPAddress = RemoteIPAddress; visitor.UserAgent = useragent; visitor.Language = language; visitor.Url = url; @@ -253,7 +254,7 @@ namespace Oqtane.Pages var visitor = _visitors.GetVisitor(VisitorId); if (visitor != null) { - visitor.IPAddress = ip; + visitor.IPAddress = RemoteIPAddress; visitor.UserAgent = useragent; visitor.Language = language; visitor.Url = url; @@ -380,7 +381,7 @@ namespace Oqtane.Pages case ResourceType.Stylesheet: if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) { - var id = (resource.Declaration == ResourceDeclaration.Global) ? "" : "id=\"app-stylesheet-" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; + var id = (resource.Declaration == ResourceDeclaration.Global) ? "" : "id=\"app-stylesheet-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; HeadResources += "" + Environment.NewLine; } break; diff --git a/Oqtane.Server/Repository/LogRepository.cs b/Oqtane.Server/Repository/LogRepository.cs index 1d67aaab..24920d42 100644 --- a/Oqtane.Server/Repository/LogRepository.cs +++ b/Oqtane.Server/Repository/LogRepository.cs @@ -53,7 +53,7 @@ namespace Oqtane.Repository { // delete logs in batches of 100 records int count = 0; - var purgedate = DateTime.Now.AddDays(-age); + var purgedate = DateTime.UtcNow.AddDays(-age); var logs = _db.Log.Where(item => item.Level != "Error" && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); while (logs.Count > 0) diff --git a/Oqtane.Server/Repository/VisitorRepository.cs b/Oqtane.Server/Repository/VisitorRepository.cs index 5feeaad9..6572d973 100644 --- a/Oqtane.Server/Repository/VisitorRepository.cs +++ b/Oqtane.Server/Repository/VisitorRepository.cs @@ -52,7 +52,7 @@ namespace Oqtane.Repository { // delete visitors in batches of 100 records int count = 0; - var purgedate = DateTime.Now.AddDays(-age); + var purgedate = DateTime.UtcNow.AddDays(-age); var visitors = _db.Visitor.Where(item => item.Visits <= 1 && item.VisitedOn < purgedate) .OrderBy(item => item.VisitedOn).Take(100).ToList(); while (visitors.Count > 0) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index cf8bf6a2..eb6a26a2 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -15,6 +15,7 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; +using Microsoft.AspNetCore.HttpOverrides; namespace Oqtane { @@ -49,7 +50,13 @@ namespace Oqtane // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - // Register localization services + // process forwarded headers on load balancers and proxy servers + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + }); + + // register localization services services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddOptions>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); @@ -111,7 +118,6 @@ namespace Oqtane services.AddOqtane(_runtime, _supportedCultures); services.AddOqtaneDbContext(); - services.AddMvc() .AddNewtonsoftJson() .AddOqtaneApplicationParts() // register any Controllers from custom modules @@ -133,9 +139,11 @@ namespace Oqtane { app.UseDeveloperExceptionPage(); app.UseWebAssemblyDebugging(); + app.UseForwardedHeaders(); } else { + app.UseForwardedHeaders(); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 0db5c13e..e4e07e8e 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -369,5 +369,12 @@ Oqtane.Interop = { if (element !== null) { element.setAttribute(attribute, value); } + }, + scrollTo: function (top, left, behavior) { + window.scrollTo({ + top: top, + left: left, + behavior: behavior + }); } }; diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 475a9d59..67d5cbd8 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -7,6 +7,6 @@ namespace Oqtane.Shared { public Alias Alias { get; set; } public string AntiForgeryToken { get; set; } // for use in client services - + public string RemoteIPAddress { get; set; } // captured in _host as cannot be reliably retrieved on Blazor Server } }