using System.Collections.Generic; using System.Xml; namespace Webserver.Permissions { public class AdminWebModules : AdminSectionAbs { public static AdminWebModules Instance { get; private set; } public AdminWebModules (AdminTools _parent) : base (_parent, "webmodules") { Instance = this; } private readonly Dictionary modules = new CaseInsensitiveStringDictionary (); #region IO public override void Clear () { modules.Clear (); } protected override void ParseElement (XmlElement _childElement) { if (WebModule.TryParse (_childElement, out WebModule webModule)) { modules [webModule.Name] = webModule; } } public override void Save (XmlElement _root) { XmlElement modulesElement = _root.AddXmlElement (SectionTypeName); // modulesElement.AddXmlComment (" "); foreach ((string _, WebModule module) in modules) { module.ToXml (modulesElement); } } #endregion #region Runtime interaction public void AddModule (WebModule _module) { lock (this) { allModulesList.Clear (); modules [_module.Name] = _module; Parent.Save (); } } public bool RemoveModule (string _module) { lock (this) { allModulesList.Clear (); bool removed = modules.Remove (_module); if (removed) { Parent.Save (); } return removed; } } public List GetModules () { lock (this) { if (allModulesList.Count != 0) { return allModulesList; } foreach ((string moduleName, WebModule moduleDefaultPerm) in knownModules) { allModulesList.Add (modules.TryGetValue (moduleName, out WebModule modulePermission) ? modulePermission : moduleDefaultPerm); } return allModulesList; } } #endregion public struct WebModule { public string Name; public int LevelGlobal; public int[] LevelPerMethod; public bool IsDefault; public WebModule (string _name, int _level, bool _isDefault = false) { LevelPerMethod = null; Name = _name; LevelGlobal = _level; IsDefault = _isDefault; } public WebModule (string _name, int _levelGlobal, int[] _levelPerMethod, bool _isDefault = false) { if (_levelPerMethod == null || _levelPerMethod.Length != (int)ERequestMethod.Count) { LevelPerMethod = createDefaultPerMethodArray (); for (int i = 0; i < (int)ERequestMethod.Count; i++) { if (_levelPerMethod != null && i < _levelPerMethod.Length) { LevelPerMethod [i] = _levelPerMethod [i]; } } } else { LevelPerMethod = _levelPerMethod; } Name = _name; LevelGlobal = _levelGlobal; IsDefault = _isDefault; } public void ToXml (XmlElement _parent) { bool hasPerMethodLevels = LevelPerMethod != null; XmlElement permissionElement = _parent.AddXmlElement ("module") .SetAttrib ("name", Name) .SetAttrib ("permission_level", LevelGlobal.ToString ()); if (!hasPerMethodLevels) { return; } for (int i = 0; i < LevelPerMethod.Length; i++) { ERequestMethod method = (ERequestMethod)i; int level = LevelPerMethod [i]; if (level == MethodLevelNotSupported) { continue; } permissionElement.AddXmlElement ("method") .SetAttrib ("name", method.ToStringCached ()) .SetAttrib ("permission_level", level == MethodLevelInheritGlobal ? MethodLevelInheritKeyword : level.ToString ()); } } public static bool TryParse (XmlElement _element, out WebModule _result) { _result = default; if (!_element.TryGetAttribute ("name", out string name)) { Log.Warning ($"[Web] [Perms] Ignoring module-entry because of missing 'name' attribute: {_element.OuterXml}"); return false; } if (!_element.TryGetAttribute ("permission_level", out string permissionLevelString)) { Log.Warning ($"[Web] [Perms] Ignoring module-entry because of missing 'permission_level' attribute: {_element.OuterXml}"); return false; } if (!int.TryParse (permissionLevelString, out int permissionLevel)) { Log.Warning ( $"[Web] [Perms] Ignoring module-entry because of invalid (non-numeric) value for 'permission_level' attribute: {_element.OuterXml}"); return false; } int[] perMethodLevels = null; foreach (XmlNode child in _element.ChildNodes) { if (child.NodeType != XmlNodeType.Element) { continue; } XmlElement childElem = (XmlElement)child; if (childElem.Name != "method") { Log.Warning ($"[Web] [Perms] Ignoring module child element, invalid element name: {childElem.OuterXml}"); continue; } if (!childElem.TryGetAttribute ("name", out string methodName)) { Log.Warning ($"[Web] [Perms] Ignoring module child element, missing 'name' attribute: {childElem.OuterXml}"); continue; } if (!EnumUtils.TryParse (methodName, out ERequestMethod method, true)) { Log.Warning ( $"[Web] [Perms] Ignoring module child element, unknown method name in 'name' attribute: {childElem.OuterXml}"); continue; } if (method >= ERequestMethod.Count) { Log.Warning ( $"[Web] [Perms] Ignoring module child element, invalid method name in 'name' attribute: {childElem.OuterXml}"); continue; } if (!childElem.TryGetAttribute ("permission_level", out permissionLevelString)) { Log.Warning ($"[Web] [Perms] Ignoring module child element, missing 'permission_level' attribute: {childElem.OuterXml}"); continue; } int methodPermissionLevel; if (permissionLevelString.EqualsCaseInsensitive (MethodLevelInheritKeyword)) { methodPermissionLevel = MethodLevelInheritGlobal; } else if (!int.TryParse (permissionLevelString, out methodPermissionLevel)) { Log.Warning ( $"[Web] [Perms] Ignoring module child element, invalid (non-numeric) value for 'permission_level' attribute: {childElem.OuterXml}"); continue; } perMethodLevels ??= createDefaultPerMethodArray (); perMethodLevels [(int)method] = methodPermissionLevel; } _result = perMethodLevels != null ? new WebModule (name, permissionLevel, perMethodLevels) : new WebModule (name, permissionLevel); return true; } private static int[] createDefaultPerMethodArray () { int[] result = new int[(int)ERequestMethod.Count]; for (int i = 0; i < (int)ERequestMethod.Count; i++) { result [i] = MethodLevelNotSupported; } return result; } } #region Specials /// /// Use global (API based) permission level for the method /// public const int MethodLevelInheritGlobal = int.MinValue; /// /// Keyword used to signal inherit level on user facing levels like admins.xml or WebAPIs /// public const string MethodLevelInheritKeyword = "inherit"; /// /// Method not supported /// public const int MethodLevelNotSupported = int.MinValue + 1; public const int PermissionLevelUser = 1000; public const int PermissionLevelGuest = 2000; /// /// Contains all registered modules and their default permission /// private readonly Dictionary knownModules = new CaseInsensitiveStringDictionary (); /// /// Public list of all modules, both those with custom permissions as well as those that do not with their default permission /// private readonly List allModulesList = new List (); public void AddKnownModule (WebModule _module) { if (string.IsNullOrEmpty (_module.Name)) { return; } _module.IsDefault = true; lock (this) { allModulesList.Clear (); knownModules [_module.Name] = _module; } } public bool IsKnownModule (string _module) { if (string.IsNullOrEmpty (_module)) { return false; } lock (this) { return knownModules.ContainsKey (_module); } } public bool ModuleAllowedWithLevel (string _module, int _level) { return GetModule (_module).LevelGlobal >= _level; } public WebModule GetModule (string _module) { if (modules.TryGetValue (_module, out WebModule result)) { return result; } return knownModules.TryGetValue (_module, out result) ? result : defaultModulePermission; } private readonly WebModule defaultModulePermission = new WebModule ("", 0, true); #endregion } }