using System; using System.Collections.Generic; using System.Xml; using JetBrains.Annotations; using UnityEngine; 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 () { allModulesList.Clear (); modules.Clear (); } public override void ParseElement (XmlElement _childElement) { allModulesList.Clear (); if (!WebModule.TryParse (_childElement, out WebModule webModule)) { return; } if (knownModules.TryGetValue (webModule.Name, out WebModule knownModule)) { webModule = webModule.FixPermissionLevelsFromKnownModule (knownModule); } 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 (Parent) { allModulesList.Clear (); modules [_module.Name] = _module; Parent.Save (); } } public bool RemoveModule (string _module) { lock (Parent) { allModulesList.Clear (); bool removed = modules.Remove (_module); if (removed) { Parent.Save (); } return removed; } } public List GetModules () { lock (Parent) { 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 readonly struct WebModule { public readonly string Name; public readonly int LevelGlobal; public readonly int[] LevelPerMethod; public readonly bool IsDefault; public WebModule (string _name, int _level, bool _isDefault) { LevelPerMethod = null; Name = _name; LevelGlobal = _level; IsDefault = _isDefault; } public WebModule (string _name, int _levelGlobal, int[] _levelPerMethod, bool _isDefault) { 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 = new WebModule (name, permissionLevel, perMethodLevels, false); return true; } [MustUseReturnValue] public WebModule SetLevelGlobal (int _level) { int[] perMethodClone = LevelPerMethod == null ? null : new int[LevelPerMethod.Length]; if (perMethodClone != null) { Array.Copy (LevelPerMethod, perMethodClone, perMethodClone.Length); } return new WebModule (Name, _level, perMethodClone, false); } [MustUseReturnValue] public WebModule SetLevelForMethod (ERequestMethod _method, int _level) { int[] perMethodClone = createDefaultPerMethodArray (); if (LevelPerMethod != null) { Array.Copy (LevelPerMethod, perMethodClone, perMethodClone.Length); } perMethodClone[(int)_method] = _level; return new WebModule (Name, LevelGlobal, perMethodClone, false); } 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; } [MustUseReturnValue] public WebModule FixPermissionLevelsFromKnownModule (WebModule _knownModule) { if (_knownModule.LevelPerMethod == null) { if (LevelPerMethod != null) { return new WebModule (Name, LevelGlobal, false); } return this; } WebModule result = this; for (int i = 0; i < _knownModule.LevelPerMethod.Length; i++) { if (result.LevelPerMethod == null || result.LevelPerMethod[i] == MethodLevelNotSupported) { result = result.SetLevelForMethod ((ERequestMethod)i, _knownModule.LevelPerMethod[i]); } } 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 (!_module.IsDefault) { Log.Warning ($"Call to AddKnownModule with IsDefault==false! From:\n{StackTraceUtility.ExtractStackTrace()}"); } if (string.IsNullOrEmpty (_module.Name)) { return; } lock (Parent) { allModulesList.Clear (); knownModules [_module.Name] = _module; if (modules.TryGetValue (_module.Name, out WebModule overrideModule)) { overrideModule = overrideModule.FixPermissionLevelsFromKnownModule (_module); modules[_module.Name] = overrideModule; } } } public bool IsKnownModule (string _module) { if (string.IsNullOrEmpty (_module)) { return false; } lock (Parent) { return knownModules.ContainsKey (_module); } } public bool ModuleAllowedWithLevel (string _module, int _level) { lock (Parent) { return GetModule (_module).LevelGlobal >= _level; } } public WebModule GetModule (string _module) { lock (Parent) { 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 } }