oqtane.framework/Oqtane.Server/Repository/ModuleDefinitionRepository.cs
2025-01-28 08:56:05 -05:00

448 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Shared;
namespace Oqtane.Repository
{
public class ModuleDefinitionRepository : IModuleDefinitionRepository
{
private MasterDBContext _db;
private readonly IMemoryCache _cache;
private readonly IPermissionRepository _permissions;
private readonly ITenantManager _tenants;
private readonly ISettingRepository _settings;
private readonly IServerStateManager _serverState;
private readonly string settingprefix = "SiteEnabled:";
public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState)
{
_db = context;
_cache = cache;
_permissions = permissions;
_tenants = tenants;
_settings = settings;
_serverState = serverState;
}
public IEnumerable<ModuleDefinition> GetModuleDefinitions()
{
return LoadModuleDefinitions(-1); // used only during startup
}
public IEnumerable<ModuleDefinition> GetModuleDefinitions(int siteId)
{
return LoadModuleDefinitions(siteId);
}
public ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int siteId)
{
List<ModuleDefinition> moduledefinitions = LoadModuleDefinitions(siteId);
return moduledefinitions.Find(item => item.ModuleDefinitionId == moduleDefinitionId);
}
public void UpdateModuleDefinition(ModuleDefinition moduleDefinition)
{
_db.Entry(moduleDefinition).State = EntityState.Modified;
_db.SaveChanges();
_permissions.UpdatePermissions(moduleDefinition.SiteId, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, moduleDefinition.PermissionList);
var settingname = $"{settingprefix}{_tenants.GetAlias().SiteKey}";
var setting = _settings.GetSetting(EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, settingname);
if (setting == null)
{
_settings.AddSetting(new Setting { EntityName = EntityNames.ModuleDefinition, EntityId = moduleDefinition.ModuleDefinitionId, SettingName = settingname, SettingValue = moduleDefinition.IsEnabled.ToString(), IsPrivate = true });
}
else
{
setting.SettingValue = moduleDefinition.IsEnabled.ToString();
_settings.UpdateSetting(setting);
}
_cache.Remove($"moduledefinitions:{_tenants.GetAlias().SiteKey}");
}
public void DeleteModuleDefinition(int moduleDefinitionId)
{
ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId);
_settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId);
_db.ModuleDefinition.Remove(moduleDefinition);
_db.SaveChanges();
_cache.Remove($"moduledefinitions:{_tenants.GetAlias().SiteKey}");
}
public ModuleDefinition FilterModuleDefinition(ModuleDefinition moduleDefinition)
{
ModuleDefinition ModuleDefinition = null;
if (moduleDefinition != null)
{
// only include required client-side properties
ModuleDefinition = new ModuleDefinition();
ModuleDefinition.ModuleDefinitionId = moduleDefinition.ModuleDefinitionId;
ModuleDefinition.SiteId = moduleDefinition.SiteId;
ModuleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName;
ModuleDefinition.Name = moduleDefinition.Name;
ModuleDefinition.Runtimes = moduleDefinition.Runtimes;
ModuleDefinition.PermissionNames = moduleDefinition.PermissionNames;
ModuleDefinition.ControlTypeRoutes = moduleDefinition.ControlTypeRoutes;
ModuleDefinition.DefaultAction = moduleDefinition.DefaultAction;
ModuleDefinition.SettingsType = moduleDefinition.SettingsType;
ModuleDefinition.ControlTypeTemplate = moduleDefinition.ControlTypeTemplate;
ModuleDefinition.IsPortable = moduleDefinition.IsPortable;
ModuleDefinition.Resources = moduleDefinition.Resources;
ModuleDefinition.IsEnabled = moduleDefinition.IsEnabled;
ModuleDefinition.PackageName = moduleDefinition.PackageName;
ModuleDefinition.Fingerprint = Utilities.GenerateSimpleHash(moduleDefinition.ModifiedOn.ToString("yyyyMMddHHmm"));
}
return ModuleDefinition;
}
public List<ModuleDefinition> LoadModuleDefinitions(int siteId)
{
// get module definitions
List<ModuleDefinition> moduleDefinitions;
if (siteId != -1)
{
moduleDefinitions = _cache.GetOrCreate($"moduledefinitions:{_tenants.GetAlias().SiteKey}", entry =>
{
entry.Priority = CacheItemPriority.NeverRemove;
return ProcessModuleDefinitions(siteId);
});
}
else // called during startup
{
return ProcessModuleDefinitions(-1);
}
return moduleDefinitions;
}
private List<ModuleDefinition> ProcessModuleDefinitions(int siteId)
{
// get module assemblies
List<ModuleDefinition> ModuleDefinitions = LoadModuleDefinitionsFromAssemblies();
// get module definitions in database
List<ModuleDefinition> moduledefinitions = _db.ModuleDefinition.ToList();
// sync module assemblies with database
foreach (ModuleDefinition ModuleDefinition in ModuleDefinitions)
{
// manage releaseversions in cases where it was not provided or is lower than the module version
if (string.IsNullOrEmpty(ModuleDefinition.ReleaseVersions) || (!string.IsNullOrEmpty(ModuleDefinition.Version) && Version.Parse(ModuleDefinition.Version).CompareTo(Version.Parse(ModuleDefinition.ReleaseVersions.Split(',').Last())) > 0))
{
ModuleDefinition.ReleaseVersions = ModuleDefinition.Version;
}
ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionName == ModuleDefinition.ModuleDefinitionName).FirstOrDefault();
if (moduledefinition == null)
{
// new module definition
moduledefinition = new ModuleDefinition { ModuleDefinitionName = ModuleDefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledefinition);
_db.SaveChanges();
// version is explicitly not set because it is updated as part of module migrations at startup
ModuleDefinition.Version = "";
}
else
{
// override user customizable property values
ModuleDefinition.Name = (!string.IsNullOrEmpty(moduledefinition.Name)) ? moduledefinition.Name : ModuleDefinition.Name;
ModuleDefinition.Description = (!string.IsNullOrEmpty(moduledefinition.Description)) ? moduledefinition.Description : ModuleDefinition.Description;
ModuleDefinition.Categories = (!string.IsNullOrEmpty(moduledefinition.Categories)) ? moduledefinition.Categories : ModuleDefinition.Categories;
// get current version
ModuleDefinition.Version = moduledefinition.Version;
// remove module definition from list as it is already synced
moduledefinitions.Remove(moduledefinition);
}
// load db properties
ModuleDefinition.ModuleDefinitionId = moduledefinition.ModuleDefinitionId;
ModuleDefinition.CreatedBy = moduledefinition.CreatedBy;
ModuleDefinition.CreatedOn = moduledefinition.CreatedOn;
ModuleDefinition.ModifiedBy = moduledefinition.ModifiedBy;
ModuleDefinition.ModifiedOn = moduledefinition.ModifiedOn;
}
// any remaining module definitions are orphans
foreach (ModuleDefinition moduledefinition in moduledefinitions)
{
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
}
if (siteId != -1)
{
var siteKey = _tenants.GetAlias().SiteKey;
var assemblies = new List<string>();
// get all module definition permissions for site
List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
// get settings for site
var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList();
// populate module definition site settings and permissions
foreach (ModuleDefinition moduledefinition in ModuleDefinitions)
{
moduledefinition.SiteId = siteId;
var setting = settings.FirstOrDefault(item => item.EntityId == moduledefinition.ModuleDefinitionId && item.SettingName == $"{settingprefix}{siteKey}");
if (setting != null)
{
moduledefinition.IsEnabled = bool.Parse(setting.SettingValue);
}
else
{
moduledefinition.IsEnabled = moduledefinition.IsAutoEnabled;
}
if (moduledefinition.IsEnabled)
{
// build list of assemblies for site
if (!assemblies.Contains(moduledefinition.AssemblyName))
{
assemblies.Add(moduledefinition.AssemblyName);
}
if (!string.IsNullOrEmpty(moduledefinition.Dependencies))
{
foreach (var assembly in moduledefinition.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse())
{
if (!assemblies.Contains(assembly.Trim()))
{
assemblies.Insert(0, assembly.Trim());
}
}
}
}
if (permissions.Count == 0)
{
// no module definition permissions exist for this site
moduledefinition.PermissionList = ClonePermissions(siteId, moduledefinition.PermissionList);
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.PermissionList);
}
else
{
if (permissions.Any(item => item.EntityId == moduledefinition.ModuleDefinitionId))
{
moduledefinition.PermissionList = permissions.Where(item => item.EntityId == moduledefinition.ModuleDefinitionId).ToList();
}
else
{
// permissions for module definition do not exist for this site
moduledefinition.PermissionList = ClonePermissions(siteId, moduledefinition.PermissionList);
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.PermissionList);
}
}
}
// cache site assemblies
var serverState = _serverState.GetServerState(siteKey);
foreach (var assembly in assemblies)
{
if (!serverState.Assemblies.Contains(assembly)) serverState.Assemblies.Add(assembly);
}
// clean up any orphaned permissions
var ids = new HashSet<int>(ModuleDefinitions.Select(item => item.ModuleDefinitionId));
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
{
try
{
_permissions.DeletePermission(permission.PermissionId);
}
catch
{
// multi-threading can cause a race condition to occur
}
}
}
return ModuleDefinitions;
}
private List<ModuleDefinition> LoadModuleDefinitionsFromAssemblies()
{
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll")))
{
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
}
}
return moduleDefinitions;
}
private List<ModuleDefinition> LoadModuleDefinitionsFromAssembly(List<ModuleDefinition> moduledefinitions, Assembly assembly)
{
ModuleDefinition moduledefinition;
Type[] moduletypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))).ToArray();
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes)
{
// Check if type should be ignored
if (modulecontroltype.IsOqtaneIgnore()) continue;
// create namespace root typename
string qualifiedModuleType = modulecontroltype.Namespace + ", " + modulecontroltype.Assembly.GetName().Name;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
{
// determine if this component is part of a module which implements IModule
Type moduletype = moduletypes.FirstOrDefault(item => item.Namespace == modulecontroltype.Namespace);
if (moduletype != null)
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = moduleobject.ModuleDefinition;
}
else
{
// set default property values
moduledefinition = new ModuleDefinition
{
Name = Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0),
Description = "Manage " + Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.ControlTypeTemplate = modulecontroltype.Namespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name;
if (moduledefinition.Resources != null)
{
foreach (var resource in moduledefinition.Resources)
{
if (resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/Modules/" + Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "/").Replace("//", "/");
}
}
}
moduledefinition.IsPortable = false;
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType))
{
Type servertype = Type.GetType(moduledefinition.ServerManagerType);
if (servertype != null && servertype.GetInterface(nameof(IPortable)) != null)
{
moduledefinition.IsPortable = true;
}
}
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";
}
if (moduledefinition.Categories == "Admin")
{
var shortName = moduledefinition.ModuleDefinitionName.Replace("Oqtane.Modules.Admin.", "").Replace(", Oqtane.Client", "");
if (Constants.DefaultHostModuleTypes.Contains(shortName))
{
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Host, true)
};
}
else
{
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Admin, true)
};
}
}
else
{
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Admin, true),
new Permission(PermissionNames.Utilize, RoleNames.Registered, true)
};
}
Debug.WriteLine($"Oqtane Info: Registering Module {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
}
moduledefinition = moduledefinitions[index];
// actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype) as IModuleControl;
string actions = modulecontrolobject.Actions;
if (!string.IsNullOrEmpty(actions))
{
foreach (string action in actions.Split(','))
{
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
}
}
moduledefinitions[index] = moduledefinition;
}
// process modules without UI components
foreach (var moduletype in moduletypes.Where(m1 => !modulecontroltypes.Any(m2 => m1.Namespace == m2.Namespace)))
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = moduleobject.ModuleDefinition;
moduledefinition.ModuleDefinitionName = moduletype.Namespace + ", " + moduletype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name;
moduledefinition.Categories = "Headless";
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Host, true)
};
moduledefinitions.Add(moduledefinition);
}
return moduledefinitions;
}
private List<Permission> ClonePermissions(int siteId, List<Permission> permissionList)
{
var permissions = new List<Permission>();
foreach (var p in permissionList)
{
var permission = new Permission();
permission.SiteId = siteId;
permission.EntityName = p.EntityName;
permission.EntityId = p.EntityId;
permission.PermissionName = p.PermissionName;
permission.RoleId = null;
permission.RoleName = p.RoleName;
permission.UserId = p.UserId;
permission.IsAuthorized = p.IsAuthorized;
permissions.Add(permission);
}
return permissions;
}
}
}