fix #5005 - adds versioning (ie. fingerprinting) for static assets - core, modules, and themes.
This commit is contained in:
@ -39,7 +39,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="stylesheet" href="css/app.css?v=@_hash" />
|
||||
@if (_scripts.Contains("PWA Manifest"))
|
||||
{
|
||||
<link id="app-manifest" rel="manifest" />
|
||||
@ -70,15 +70,15 @@
|
||||
}
|
||||
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/loadjs.min.js"></script>
|
||||
<script src="js/interop.js"></script>
|
||||
<script src="js/app.js?v=@_hash"></script>
|
||||
<script src="js/loadjs.min.js?v=@_hash"></script>
|
||||
<script src="js/interop.js?v=@_hash"></script>
|
||||
|
||||
@((MarkupString)_scripts)
|
||||
@((MarkupString)_bodyResources)
|
||||
@if (_renderMode == RenderModes.Static)
|
||||
{
|
||||
<page-script src="./js/reload.js"></page-script>
|
||||
<page-script src="./js/reload.js?v=@_hash"></page-script>
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -94,6 +94,7 @@
|
||||
private string _renderMode = RenderModes.Interactive;
|
||||
private string _runtime = Runtimes.Server;
|
||||
private bool _prerender = true;
|
||||
private string _hash = "";
|
||||
private int _visitorId = -1;
|
||||
private string _antiForgeryToken = "";
|
||||
private string _remoteIPAddress = "";
|
||||
@ -136,6 +137,8 @@
|
||||
_renderMode = site.RenderMode;
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender;
|
||||
_hash = site.Hash;
|
||||
|
||||
var modules = new List<Module>();
|
||||
|
||||
Route route = new Route(url, alias.Path);
|
||||
@ -605,13 +608,13 @@
|
||||
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
||||
if (theme != null)
|
||||
{
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Hash, site.RenderMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback to default Oqtane theme
|
||||
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Hash, site.RenderMode);
|
||||
}
|
||||
var type = Type.GetType(themeType);
|
||||
if (type != null)
|
||||
@ -619,7 +622,7 @@
|
||||
var obj = Activator.CreateInstance(type) as IThemeControl;
|
||||
if (obj != null)
|
||||
{
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, theme.Hash, site.RenderMode);
|
||||
}
|
||||
}
|
||||
// theme settings components are dynamically loaded within the framework Page Management module
|
||||
@ -629,7 +632,7 @@
|
||||
if (settingsType != null)
|
||||
{
|
||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Hash, site.RenderMode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -638,7 +641,7 @@
|
||||
var typename = "";
|
||||
if (module.ModuleDefinition != null)
|
||||
{
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Hash, site.RenderMode);
|
||||
|
||||
// handle default action
|
||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||
@ -684,7 +687,7 @@
|
||||
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
if (moduleobject != null)
|
||||
{
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash, site.RenderMode);
|
||||
|
||||
// settings components are dynamically loaded within the framework Settings module
|
||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||
@ -705,7 +708,7 @@
|
||||
if (moduletype != null)
|
||||
{
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash, site.RenderMode);
|
||||
}
|
||||
|
||||
// container settings component
|
||||
@ -715,7 +718,7 @@
|
||||
if (moduletype != null)
|
||||
{
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Hash, site.RenderMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -731,7 +734,7 @@
|
||||
{
|
||||
if (module.ModuleDefinition?.Resources != null)
|
||||
{
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Hash, site.RenderMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -739,7 +742,7 @@
|
||||
return resources;
|
||||
}
|
||||
|
||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
|
||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string version, string rendermode)
|
||||
{
|
||||
if (resources != null)
|
||||
{
|
||||
@ -759,7 +762,7 @@
|
||||
// ensure resource does not exist already
|
||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||
{
|
||||
pageresources.Add(resource.Clone(level, name));
|
||||
pageresources.Add(resource.Clone(level, name, version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ namespace Oqtane.Controllers
|
||||
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
||||
if (hashfilename)
|
||||
{
|
||||
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||
HashedName = Utilities.GenerateSimpleHash(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -297,25 +297,5 @@ namespace Oqtane.Controllers
|
||||
public string FilePath { get; private set; }
|
||||
public string HashedName { get; private set; }
|
||||
}
|
||||
|
||||
private static int GetDeterministicHashCode(string value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash1 = (5381 << 16) + 5381;
|
||||
int hash2 = hash1;
|
||||
|
||||
for (int i = 0; i < value.Length; i += 2)
|
||||
{
|
||||
hash1 = ((hash1 << 5) + hash1) ^ value[i];
|
||||
if (i == value.Length - 1)
|
||||
break;
|
||||
hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
|
||||
}
|
||||
|
||||
return hash1 + (hash2 * 1566083941);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace Oqtane.Controllers
|
||||
if (user != null)
|
||||
{
|
||||
email = user.Email;
|
||||
_configManager.AddOrUpdateSetting("PackageRegistryEmail", email, false);
|
||||
_configManager.AddOrUpdateSetting("PackageRegistryEmail", email, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +175,12 @@ namespace Oqtane.Infrastructure
|
||||
installationid = Guid.NewGuid().ToString();
|
||||
AddOrUpdateSetting("InstallationId", installationid, true);
|
||||
}
|
||||
var version = GetSetting("InstallationVersion", "");
|
||||
if (version != Constants.Version)
|
||||
{
|
||||
AddOrUpdateSetting("InstallationVersion", Constants.Version, true);
|
||||
AddOrUpdateSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm"), true);
|
||||
}
|
||||
return installationid;
|
||||
}
|
||||
}
|
||||
|
@ -265,6 +265,7 @@ namespace Oqtane.Infrastructure
|
||||
var installation = IsInstalled();
|
||||
try
|
||||
{
|
||||
UpdateInstallation();
|
||||
UpdateConnectionString(install.ConnectionString);
|
||||
UpdateDatabaseType(install.DatabaseType);
|
||||
|
||||
@ -491,6 +492,7 @@ namespace Oqtane.Infrastructure
|
||||
moduleDefinition.Categories = moduledef.Categories;
|
||||
// update version
|
||||
moduleDefinition.Version = versions[versions.Length - 1];
|
||||
moduleDefinition.ModifiedOn = DateTime.UtcNow;
|
||||
db.Entry(moduleDefinition).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
}
|
||||
@ -666,6 +668,11 @@ namespace Oqtane.Infrastructure
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
public void UpdateInstallation()
|
||||
{
|
||||
_config.GetInstallationId();
|
||||
}
|
||||
|
||||
public void UpdateConnectionString(string connectionString)
|
||||
{
|
||||
connectionString = DenormalizeConnectionString(connectionString);
|
||||
@ -677,7 +684,10 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
public void UpdateDatabaseType(string databaseType)
|
||||
{
|
||||
_configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true);
|
||||
if (_config.GetSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", "") != databaseType)
|
||||
{
|
||||
_configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEFMigrationsHistory(ISqlRepository sql, string connectionString, string databaseType, string version, bool isMaster)
|
||||
|
28
Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs
Normal file
28
Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Master
|
||||
{
|
||||
[DbContext(typeof(MasterDBContext))]
|
||||
[Migration("Master.06.00.02.01")]
|
||||
public class AddThemeVersion : MultiDatabaseMigration
|
||||
{
|
||||
public AddThemeVersion(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
themeEntityBuilder.AddStringColumn("Version", 50, true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
@ -101,6 +101,7 @@ namespace Oqtane.Repository
|
||||
ModuleDefinition.Resources = moduleDefinition.Resources;
|
||||
ModuleDefinition.IsEnabled = moduleDefinition.IsEnabled;
|
||||
ModuleDefinition.PackageName = moduleDefinition.PackageName;
|
||||
ModuleDefinition.Hash = Utilities.GenerateSimpleHash(moduleDefinition.ModifiedOn.ToString("yyyyMMddHHmm"));
|
||||
}
|
||||
|
||||
return ModuleDefinition;
|
||||
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Oqtane.Infrastructure;
|
||||
@ -87,6 +88,7 @@ namespace Oqtane.Repository
|
||||
Theme.ThemeSettingsType = theme.ThemeSettingsType;
|
||||
Theme.ContainerSettingsType = theme.ContainerSettingsType;
|
||||
Theme.PackageName = theme.PackageName;
|
||||
Theme.Hash = Utilities.GenerateSimpleHash(theme.ModifiedOn.ToString("yyyyMMddHHmm"));
|
||||
Themes.Add(Theme);
|
||||
}
|
||||
|
||||
@ -126,6 +128,13 @@ namespace Oqtane.Repository
|
||||
}
|
||||
else
|
||||
{
|
||||
if (theme.Version != Theme.Version)
|
||||
{
|
||||
// update theme version
|
||||
theme.Version = Theme.Version;
|
||||
_db.SaveChanges();
|
||||
}
|
||||
|
||||
// override user customizable property values
|
||||
Theme.Name = (!string.IsNullOrEmpty(theme.Name)) ? theme.Name : Theme.Name;
|
||||
|
||||
|
@ -29,12 +29,13 @@ namespace Oqtane.Services
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly ITenantManager _tenantManager;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly IConfigManager _configManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly string _private = "[PRIVATE]";
|
||||
|
||||
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
||||
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
||||
{
|
||||
_sites = sites;
|
||||
_pages = pages;
|
||||
@ -46,6 +47,7 @@ namespace Oqtane.Services
|
||||
_settings = settings;
|
||||
_tenantManager = tenantManager;
|
||||
_syncManager = syncManager;
|
||||
_configManager = configManager;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_accessor = accessor;
|
||||
@ -143,6 +145,9 @@ namespace Oqtane.Services
|
||||
|
||||
// themes
|
||||
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());
|
||||
|
||||
// installation date used for fingerprinting static assets
|
||||
site.Hash = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm")));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Reference in New Issue
Block a user