using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Xml; namespace Webserver { public class WebPermissions { private const string permissionsFileName = "webpermissions.xml"; private static WebPermissions instance; private readonly FileSystemWatcher fileWatcher; private readonly WebModulePermission defaultModulePermission = new WebModulePermission ("", 0); /// /// Registered user/pass admin tokens /// private readonly Dictionary adminTokens = new CaseInsensitiveStringDictionary (); /// /// Contains all registered modules and their default permission /// private readonly Dictionary knownModules = new CaseInsensitiveStringDictionary (); /// /// Manually defined module permissions /// private readonly Dictionary modulePermissions = 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 (); private readonly ReadOnlyCollection allModulesListRo; private static string SettingsFilePath => GamePrefs.GetString (EnumUtils.Parse ("SaveGameFolder")); private static string SettingsFileName => permissionsFileName; private static string SettingsFullPath => SettingsFilePath + "/" + SettingsFileName; private WebPermissions () { allModulesListRo = new ReadOnlyCollection (allModulesList); Directory.CreateDirectory (SettingsFilePath); Load (); fileWatcher = new FileSystemWatcher (SettingsFilePath, SettingsFileName); fileWatcher.Changed += OnFileChanged; fileWatcher.Created += OnFileChanged; fileWatcher.Deleted += OnFileChanged; fileWatcher.EnableRaisingEvents = true; } public static WebPermissions Instance { get { lock (typeof (WebPermissions)) { return instance ??= new WebPermissions (); } } } #region Admin Tokens public void AddAdmin (string _name, string _token, int _permissionLevel, bool _save = true) { AdminToken c = new AdminToken (_name, _token, _permissionLevel); lock (this) { adminTokens [_name] = c; if (_save) { Save (); } } } public void RemoveAdmin (string _name, bool _save = true) { lock (this) { adminTokens.Remove (_name); if (_save) { Save (); } } } public bool IsAdmin (string _name) { return adminTokens.ContainsKey (_name); } public AdminToken[] GetAdmins () { AdminToken[] result = new AdminToken[adminTokens.Count]; adminTokens.CopyValuesTo (result); return result; } public AdminToken GetWebAdmin (string _name, string _token) { if (IsAdmin (_name) && adminTokens [_name].token == _token) { return adminTokens [_name]; } return null; } #endregion #region Modules public void AddModulePermission (string _module, int _permissionLevel, bool _save = true) { WebModulePermission p = new WebModulePermission (_module, _permissionLevel); lock (this) { allModulesList.Clear (); modulePermissions [_module] = p; if (_save) { Save (); } } } public void AddKnownModule (string _module, int _defaultPermission) { if (string.IsNullOrEmpty (_module)) { return; } WebModulePermission p = new WebModulePermission (_module, _defaultPermission); lock (this) { allModulesList.Clear (); knownModules [_module] = p; } } public bool IsKnownModule (string _module) { if (string.IsNullOrEmpty (_module)) { return false; } lock (this) { return knownModules.ContainsKey (_module); } } public void RemoveModulePermission (string _module, bool _save = true) { lock (this) { allModulesList.Clear (); modulePermissions.Remove (_module); if (_save) { Save (); } } } public IList GetModules () { if (allModulesList.Count != 0) { return allModulesListRo; } foreach ((string moduleName, WebModulePermission moduleDefaultPerm) in knownModules) { allModulesList.Add (modulePermissions.TryGetValue (moduleName, out WebModulePermission modulePermission) ? modulePermission : moduleDefaultPerm); } return allModulesListRo; } public bool ModuleAllowedWithLevel (string _module, int _level) { WebModulePermission permInfo = GetModulePermission (_module); return permInfo.permissionLevel >= _level; } public WebModulePermission GetModulePermission (string _module) { if (modulePermissions.TryGetValue (_module, out WebModulePermission result)) { return result; } return knownModules.TryGetValue (_module, out result) ? result : defaultModulePermission; } #endregion #region IO Tasks private void OnFileChanged (object _source, FileSystemEventArgs _e) { Log.Out ("[Web] [Perms] Reloading " + SettingsFileName); Load (); } public void Load () { adminTokens.Clear (); modulePermissions.Clear (); if (!File.Exists (SettingsFullPath)) { Log.Out ($"[Web] [Perms] Permissions file '{SettingsFileName}' not found, creating."); Save (); return; } Log.Out ($"[Web] [Perms] Loading permissions file at '{SettingsFullPath}'"); XmlDocument xmlDoc = new XmlDocument (); try { xmlDoc.Load (SettingsFullPath); } catch (XmlException e) { Log.Error ("[Web] [Perms] Failed loading permissions file: " + e.Message); return; } XmlNode adminToolsNode = xmlDoc.DocumentElement; if (adminToolsNode == null) { Log.Error ("[Web] [Perms] Failed loading permissions file: No DocumentElement found"); return; } foreach (XmlNode childNode in adminToolsNode.ChildNodes) { switch (childNode.Name) { case "admintokens": ParseAdminTokens (childNode); break; case "permissions": ParseModulePermissions (childNode); break; } } Log.Out ("[Web] [Perms] Loading permissions file done."); } private void ParseAdminTokens (XmlNode _baseNode) { foreach (XmlNode subChild in _baseNode.ChildNodes) { if (subChild.NodeType == XmlNodeType.Comment) { continue; } if (subChild.NodeType != XmlNodeType.Element) { Log.Warning ($"[Web] [Perms] Unexpected XML node found in 'admintokens' section: {subChild.OuterXml}"); continue; } XmlElement lineItem = (XmlElement)subChild; if (!lineItem.HasAttribute ("name")) { Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'name' attribute: {subChild.OuterXml}"); continue; } if (!lineItem.HasAttribute ("token")) { Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'token' attribute: {subChild.OuterXml}"); continue; } if (!lineItem.HasAttribute ("permission_level")) { Log.Warning ($"[Web] [Perms] Ignoring admintoken-entry because of missing 'permission_level' attribute: {subChild.OuterXml}"); continue; } string name = lineItem.GetAttribute ("name"); string token = lineItem.GetAttribute ("token"); if (!int.TryParse (lineItem.GetAttribute ("permission_level"), out int permissionLevel)) { Log.Warning ( $"[Web] [Perms] Ignoring admintoken-entry because of invalid (non-numeric) value for 'permission_level' attribute: {subChild.OuterXml}"); continue; } AddAdmin (name, token, permissionLevel, false); } } private void ParseModulePermissions (XmlNode _baseNode) { foreach (XmlNode subChild in _baseNode.ChildNodes) { if (subChild.NodeType == XmlNodeType.Comment) { continue; } if (subChild.NodeType != XmlNodeType.Element) { Log.Warning ($"[Web] [Perms] Unexpected XML node found in 'permissions' section: {subChild.OuterXml}"); continue; } XmlElement lineItem = (XmlElement)subChild; if (!lineItem.HasAttribute ("module")) { Log.Warning ($"[Web] [Perms] Ignoring permission-entry because of missing 'module' attribute: {subChild.OuterXml}"); continue; } if (!lineItem.HasAttribute ("permission_level")) { Log.Warning ($"[Web] [Perms] Ignoring permission-entry because of missing 'permission_level' attribute: {subChild.OuterXml}"); continue; } if (!int.TryParse (lineItem.GetAttribute ("permission_level"), out int permissionLevel)) { Log.Warning ( $"[Web] [Perms] Ignoring permission-entry because of invalid (non-numeric) value for 'permission_level' attribute: {subChild.OuterXml}"); continue; } AddModulePermission (lineItem.GetAttribute ("module"), permissionLevel, false); } } public void Save () { XmlDocument xml = new XmlDocument (); xml.CreateXmlDeclaration (); // xml.AddXmlComment (XmlHeader); XmlElement root = xml.AddXmlElement ("webpermissions"); // AdminTokens XmlElement adminTokensElem = root.AddXmlElement ("admintokens"); adminTokensElem.AddXmlComment (" "); foreach ((string _, AdminToken adminToken) in adminTokens) { adminTokensElem.AddXmlElement ("token") .SetAttrib ("name", adminToken.name) .SetAttrib ("token", adminToken.token) .SetAttrib ("permission_level", adminToken.permissionLevel.ToString ()); } XmlElement modulePermissionsElem = root.AddXmlElement ("permissions"); foreach ((string _, WebModulePermission webModulePermission) in modulePermissions) { modulePermissionsElem.AddXmlElement ("permission") .SetAttrib ("module", webModulePermission.module) .SetAttrib ("permission_level", webModulePermission.permissionLevel.ToString ()); } fileWatcher.EnableRaisingEvents = false; xml.Save (SettingsFullPath); fileWatcher.EnableRaisingEvents = true; } #endregion public class AdminToken { public readonly string name; public readonly int permissionLevel; public readonly string token; public AdminToken (string _name, string _token, int _permissionLevel) { name = _name; token = _token; permissionLevel = _permissionLevel; } } public struct WebModulePermission { public readonly string module; public readonly int permissionLevel; public WebModulePermission (string _module, int _permissionLevel) { module = _module; permissionLevel = _permissionLevel; } } } }