Merge pull request #1936 from sbwalker/dev

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
This commit is contained in:
Shaun Walker 2022-01-13 07:10:15 -05:00 committed by GitHub
commit 21304db7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 75 additions and 40 deletions

View File

@ -42,6 +42,9 @@
[Parameter] [Parameter]
public int VisitorId { get; set; } public int VisitorId { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
private bool _initialized = false; private bool _initialized = false;
private string _display = "display: none;"; private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" }; private Installation _installation = new Installation { Success = false, Message = "" };
@ -50,6 +53,7 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken; SiteState.AntiForgeryToken = AntiForgeryToken;
InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken);

View File

@ -533,6 +533,7 @@
else else
{ {
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth");
} }
} }
} }

View File

@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label> <Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="servertime" class="form-control" @bind="@_servertime" readonly /> <input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div> </div>
@ -123,11 +123,7 @@
_clrversion = systeminfo["clrversion"]; _clrversion = systeminfo["clrversion"];
_osversion = systeminfo["osversion"]; _osversion = systeminfo["osversion"];
_serverpath = systeminfo["serverpath"]; _serverpath = systeminfo["serverpath"];
_servertime = systeminfo["servertime"]; _servertime = systeminfo["servertime"] + " UTC";
if (DateTime.TryParse(_servertime, out DateTime date))
{
_servertime += " (" + date.ToUniversalTime().ToString() + " UTC)";
}
_installationid = systeminfo["installationid"]; _installationid = systeminfo["installationid"];
_detailederrors = systeminfo["detailederrors"]; _detailederrors = systeminfo["detailederrors"];

View File

@ -133,7 +133,7 @@
<value>Server Path</value> <value>Server Path</value>
</data> </data>
<data name="ServerTime.HelpText" xml:space="preserve"> <data name="ServerTime.HelpText" xml:space="preserve">
<value>Server Time</value> <value>Server Date/Time (in UTC)</value>
</data> </data>
<data name="FrameworkVersion.Text" xml:space="preserve"> <data name="FrameworkVersion.Text" xml:space="preserve">
<value>Framework Version: </value> <value>Framework Version: </value>
@ -148,7 +148,7 @@
<value>Server Path: </value> <value>Server Path: </value>
</data> </data>
<data name="ServerTime.Text" xml:space="preserve"> <data name="ServerTime.Text" xml:space="preserve">
<value>Server Time: </value> <value>Server Date/Time: </value>
</data> </data>
<data name="RestartApplication.Header" xml:space="preserve"> <data name="RestartApplication.Header" xml:space="preserve">
<value>Restart Application</value> <value>Restart Application</value>

View File

@ -262,5 +262,22 @@ namespace Oqtane.UI
return Task.CompletedTask; 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;
}
}
} }
} }

View File

@ -21,5 +21,6 @@ namespace Oqtane.UI
public DateTime LastSyncDate { get; set; } public DateTime LastSyncDate { get; set; }
public Oqtane.Shared.Runtime Runtime { get; set; } public Oqtane.Shared.Runtime Runtime { get; set; }
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; }
} }
} }

View File

@ -229,7 +229,8 @@
EditMode = editmode, EditMode = editmode,
LastSyncDate = lastsyncdate, LastSyncDate = lastsyncdate,
Runtime = runtime, Runtime = runtime,
VisitorId = VisitorId VisitorId = VisitorId,
RemoteIPAddress = SiteState.RemoteIPAddress
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);

View File

@ -31,7 +31,7 @@
var interop = new Interop(JsRuntime); var interop = new Interop(JsRuntime);
// manage stylesheets for this page // manage stylesheets for this page
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff"); string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>(); var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global)) foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
{ {

View File

@ -31,7 +31,7 @@ namespace Oqtane.Controllers
systeminfo.Add("osversion", Environment.OSVersion.ToString()); systeminfo.Add("osversion", Environment.OSVersion.ToString());
systeminfo.Add("machinename", Environment.MachineName); systeminfo.Add("machinename", Environment.MachineName);
systeminfo.Add("serverpath", _environment.ContentRootPath); systeminfo.Add("serverpath", _environment.ContentRootPath);
systeminfo.Add("servertime", DateTime.Now.ToString()); systeminfo.Add("servertime", DateTime.UtcNow.ToString());
systeminfo.Add("installationid", _configManager.GetInstallationId()); systeminfo.Add("installationid", _configManager.GetInstallationId());
systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server")); systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server"));

View File

@ -441,8 +441,7 @@ namespace Oqtane.Infrastructure
var index = Array.FindIndex(versions, item => item == version); var index = Array.FindIndex(versions, item => item == version);
if (index != (versions.Length - 1)) if (index != (versions.Length - 1))
{ {
if (index == -1) index = 0; for (var i = (index + 1); i < versions.Length; i++)
for (var i = index; i < versions.Length; i++)
{ {
upgrades.Upgrade(tenant, versions[i]); upgrades.Upgrade(tenant, versions[i]);
} }

View File

@ -56,6 +56,7 @@ namespace Oqtane.Pages
public string Runtime = "Server"; public string Runtime = "Server";
public RenderMode RenderMode = RenderMode.Server; public RenderMode RenderMode = RenderMode.Server;
public int VisitorId = -1; public int VisitorId = -1;
public string RemoteIPAddress = "";
public string HeadResources = ""; public string HeadResources = "";
public string BodyResources = ""; public string BodyResources = "";
public string Title = ""; public string Title = "";
@ -66,6 +67,7 @@ namespace Oqtane.Pages
public IActionResult OnGet() public IActionResult OnGet()
{ {
AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken; AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
RemoteIPAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "";
if (_configuration.GetSection("Runtime").Exists()) if (_configuration.GetSection("Runtime").Exists())
{ {
@ -194,12 +196,11 @@ namespace Oqtane.Pages
private void TrackVisitor(int SiteId) private void TrackVisitor(int SiteId)
{ {
// get request attributes // get request attributes
string ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "";
string useragent = (Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Request.Headers[HeaderNames.UserAgent] : ""; string useragent = (Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Request.Headers[HeaderNames.UserAgent] : "";
string language = (Request.Headers[HeaderNames.AcceptLanguage] != StringValues.Empty) ? Request.Headers[HeaderNames.AcceptLanguage] : ""; 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.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 // filter
var filter = _settings.GetSetting(EntityNames.Site, SiteId, "VisitorFilter"); 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()) 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; return;
} }
@ -227,7 +228,7 @@ namespace Oqtane.Pages
{ {
var visitor = new Visitor(); var visitor = new Visitor();
visitor.SiteId = SiteId; visitor.SiteId = SiteId;
visitor.IPAddress = ip; visitor.IPAddress = RemoteIPAddress;
visitor.UserAgent = useragent; visitor.UserAgent = useragent;
visitor.Language = language; visitor.Language = language;
visitor.Url = url; visitor.Url = url;
@ -253,7 +254,7 @@ namespace Oqtane.Pages
var visitor = _visitors.GetVisitor(VisitorId); var visitor = _visitors.GetVisitor(VisitorId);
if (visitor != null) if (visitor != null)
{ {
visitor.IPAddress = ip; visitor.IPAddress = RemoteIPAddress;
visitor.UserAgent = useragent; visitor.UserAgent = useragent;
visitor.Language = language; visitor.Language = language;
visitor.Url = url; visitor.Url = url;
@ -380,7 +381,7 @@ namespace Oqtane.Pages
case ResourceType.Stylesheet: case ResourceType.Stylesheet:
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) 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 += "<link " + id + "rel=\"stylesheet\" href=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " />" + Environment.NewLine; HeadResources += "<link " + id + "rel=\"stylesheet\" href=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " />" + Environment.NewLine;
} }
break; break;

View File

@ -53,7 +53,7 @@ namespace Oqtane.Repository
{ {
// delete logs in batches of 100 records // delete logs in batches of 100 records
int count = 0; 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) var logs = _db.Log.Where(item => item.Level != "Error" && item.LogDate < purgedate)
.OrderBy(item => item.LogDate).Take(100).ToList(); .OrderBy(item => item.LogDate).Take(100).ToList();
while (logs.Count > 0) while (logs.Count > 0)

View File

@ -52,7 +52,7 @@ namespace Oqtane.Repository
{ {
// delete visitors in batches of 100 records // delete visitors in batches of 100 records
int count = 0; 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) var visitors = _db.Visitor.Where(item => item.Visits <= 1 && item.VisitedOn < purgedate)
.OrderBy(item => item.VisitedOn).Take(100).ToList(); .OrderBy(item => item.VisitedOn).Take(100).ToList();
while (visitors.Count > 0) while (visitors.Count > 0)

View File

@ -15,6 +15,7 @@ using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Shared; using Oqtane.Shared;
using Microsoft.AspNetCore.HttpOverrides;
namespace Oqtane 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 // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
// Register localization services // process forwarded headers on load balancers and proxy servers
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// register localization services
services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection));
@ -111,7 +118,6 @@ namespace Oqtane
services.AddOqtane(_runtime, _supportedCultures); services.AddOqtane(_runtime, _supportedCultures);
services.AddOqtaneDbContext(); services.AddOqtaneDbContext();
services.AddMvc() services.AddMvc()
.AddNewtonsoftJson() .AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
@ -133,9 +139,11 @@ namespace Oqtane
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging(); app.UseWebAssemblyDebugging();
app.UseForwardedHeaders();
} }
else 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. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }

View File

@ -369,5 +369,12 @@ Oqtane.Interop = {
if (element !== null) { if (element !== null) {
element.setAttribute(attribute, value); element.setAttribute(attribute, value);
} }
},
scrollTo: function (top, left, behavior) {
window.scrollTo({
top: top,
left: left,
behavior: behavior
});
} }
}; };

View File

@ -7,6 +7,6 @@ namespace Oqtane.Shared
{ {
public Alias Alias { get; set; } public Alias Alias { get; set; }
public string AntiForgeryToken { get; set; } // for use in client services 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
} }
} }