/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Linq; using System.Collections.Generic; namespace TShockAPI { /// /// A class used to group multiple users' permissions and settings. /// public class Group { // NOTE: Using a const still suffers from needing to recompile to change the default // ideally we would use a static but this means it can't be used for the default parameter :( /// /// Default chat color. /// public const string defaultChatColor = "255,255,255"; /// /// List of permissions available to the group. /// public readonly List permissions = new List(); /// /// List of permissions that the group is explicitly barred from. /// public readonly List negatedpermissions = new List(); /// /// The group's name. /// public string Name { get; set; } /// /// The group that this group inherits permissions from. /// public Group Parent { get; set; } /// /// The chat prefix for this group. /// public string Prefix { get; set; } /// /// The chat suffix for this group. /// public string Suffix { get; set; } /// /// The name of the parent, not particularly sure why this is here. /// We can use group.Parent.Name and not have this second reference. /// This was added for rest, so a discussion with Shank is necessary. /// public string ParentName { get { return (null == Parent) ? "" : Parent.Name; } } /// /// The chat color of the group. /// Returns "255,255,255", sets "255,255,255" /// public string ChatColor { get { return string.Format("{0},{1},{2}", R.ToString("D3"), G.ToString("D3"), B.ToString("D3")); } set { if (null != value) { string[] parts = value.Split(','); if (3 == parts.Length) { byte r, g, b; if (byte.TryParse(parts[0], out r) && byte.TryParse(parts[1], out g) && byte.TryParse(parts[2], out b)) { R = r; G = g; B = b; return; } } } } } /// /// The permissions of the user in string form. /// public string Permissions { get { List all = new List(permissions); negatedpermissions.ForEach(p => all.Add("!" + p)); return string.Join(",", all); } set { permissions.Clear(); negatedpermissions.Clear(); if (null != value) value.Split(',').ForEach(p => AddPermission(p.Trim())); } } /// /// The permissions of this group and all that it inherits from. /// public virtual List TotalPermissions { get { var cur = this; var traversed = new List(); HashSet all = new HashSet(); while (cur != null) { foreach (var perm in cur.permissions) { all.Add(perm); } foreach (var perm in cur.negatedpermissions) { all.Remove(perm); } if (traversed.Contains(cur)) { throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name)); } traversed.Add(cur); cur = cur.Parent; } return all.ToList(); } } /// /// The group's chat color red byte. /// public byte R = 255; /// /// The group's chat color green byte. /// public byte G = 255; /// /// The group's chat color blue byte. /// public byte B = 255; /// /// The default group attributed to unregistered users. /// public static Group DefaultGroup = null; /// /// Initializes a new instance of the group class. /// /// The name of the group. /// The parent group, if any. /// The chat color, in "RRR,GGG,BBB" format. /// The list of permissions associated with this group, separated by commas. public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255", string permissions = null) { Name = groupname; Parent = parentgroup; ChatColor = chatcolor; Permissions = permissions; } /// /// Checks to see if a group has a specified permission. /// /// The permission to check. /// True if the group has that permission. public virtual bool HasPermission(string permission) { bool negated = false; if (String.IsNullOrEmpty(permission) || (RealHasPermission(permission, ref negated) && !negated)) { return true; } if (negated) return false; string[] nodes = permission.Split('.'); for (int i = nodes.Length - 1; i >= 0; i--) { nodes[i] = "*"; if (RealHasPermission(String.Join(".", nodes, 0, i + 1), ref negated)) { return !negated; } } return false; } private bool RealHasPermission(string permission, ref bool negated) { negated = false; if (string.IsNullOrEmpty(permission)) return true; var cur = this; var traversed = new List(); while (cur != null) { if (cur.negatedpermissions.Contains(permission)) { negated = true; return false; } if (cur.permissions.Contains(permission)) return true; if (traversed.Contains(cur)) { throw new InvalidOperationException("Infinite group parenting ({0})".SFormat(cur.Name)); } traversed.Add(cur); cur = cur.Parent; } return false; } /// /// Adds a permission to the list of negated permissions. /// /// The permission to negate. public void NegatePermission(string permission) { // Avoid duplicates if (!negatedpermissions.Contains(permission)) { negatedpermissions.Add(permission); permissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions } } /// /// Adds a permission to the list of permissions. /// /// The permission to add. public void AddPermission(string permission) { if (permission.StartsWith("!")) { NegatePermission(permission.Substring(1)); return; } // Avoid duplicates if (!permissions.Contains(permission)) { permissions.Add(permission); negatedpermissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions } } /// /// Clears the permission list and sets it to the list provided, /// will parse "!permission" and add it to the negated permissions. /// /// The new list of permissions to associate with the group. public void SetPermission(List permission) { permissions.Clear(); negatedpermissions.Clear(); permission.ForEach(p => AddPermission(p)); } /// /// Will remove a permission from the respective list, /// where "!permission" will remove a negated permission. /// /// public void RemovePermission(string permission) { if (permission.StartsWith("!")) { negatedpermissions.Remove(permission.Substring(1)); return; } permissions.Remove(permission); } /// /// Assigns all fields of this instance to another. /// /// The other instance. public void AssignTo(Group otherGroup) { otherGroup.Name = Name; otherGroup.Parent = Parent; otherGroup.Prefix = Prefix; otherGroup.Suffix = Suffix; otherGroup.R = R; otherGroup.G = G; otherGroup.B = B; otherGroup.Permissions = Permissions; } public override string ToString() { return this.Name; } } /// /// This class is the SuperAdminGroup, which has access to everything. /// public class SuperAdminGroup : Group { /// /// The superadmin class has every permission, represented by '*'. /// public override List TotalPermissions { get { return new List { "*" }; } } /// /// Initializes a new instance of the SuperAdminGroup class with the configured parameters. /// Those can be changed in the config file. /// public SuperAdminGroup() : base("superadmin") { R = (byte)TShock.Config.Settings.SuperAdminChatRGB[0]; G = (byte)TShock.Config.Settings.SuperAdminChatRGB[1]; B = (byte)TShock.Config.Settings.SuperAdminChatRGB[2]; Prefix = TShock.Config.Settings.SuperAdminChatPrefix; Suffix = TShock.Config.Settings.SuperAdminChatSuffix; } /// /// Override to allow access to everything. /// /// The permission /// True public override bool HasPermission(string permission) { return true; } } }