From 0c13716b841377835422f2a28343404132ab8d4f Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 18:41:44 +0000 Subject: [PATCH 01/10] Removed TShock.Config.EnableBanOnUsernames check from GetBanByName so that it can be used internally for queries --- TShockAPI/DB/BanManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index 082e5a41..57eaa909 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -92,10 +92,6 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)"); public Ban GetBanByName(string name, bool casesensitive = true) { - if (!TShock.Config.EnableBanOnUsernames) - { - return null; - } try { var namecol = casesensitive ? "Name" : "UPPER(Name)"; From 46f5f872ae02ed3ddf6aeea58ccefe4368b3822a Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 19:00:27 +0000 Subject: [PATCH 02/10] Moved local exceptions outside of try block in AddUser so the calling code gets the original exception and not a generic one Also added details of the SQL exception message into the message of the UserManagerException so its easier to determine the actual error when the exception message is returned / printed out --- TShockAPI/DB/UserManager.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 70423dfb..29944e18 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -50,20 +50,22 @@ namespace TShockAPI.DB /// User user public void AddUser(User user) { + if (!TShock.Groups.GroupExists(user.Group)) + throw new GroupNotExistsException(user.Group); + + int ret; try { - if (!TShock.Groups.GroupExists(user.Group)) - throw new GroupNotExistsException(user.Group); - - if ( - database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, - TShock.Utils.HashPassword(user.Password), user.Group, user.Address) < 1) - throw new UserExistsException(user.Name); + ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, + TShock.Utils.HashPassword(user.Password), user.Group, user.Address); } catch (Exception ex) { - throw new UserManagerException("AddUser SQL returned an error", ex); + throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex); } + + if (1 > ret) + throw new UserExistsException(user.Name); } /// From 4daa9add13cc0b8f786c0c46ec0037041ef018d3 Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 19:16:20 +0000 Subject: [PATCH 03/10] Added GetUsers method mirroring GetBans to enable RestAPI to provide a full users list as well as an active one --- TShockAPI/DB/UserManager.cs | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 29944e18..7ea6c63a 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -18,6 +18,7 @@ along with this program. If not, see . using System; using System.Data; using System.IO; +using System.Collections.Generic; using MySql.Data.MySqlClient; namespace TShockAPI.DB @@ -256,14 +257,7 @@ namespace TShockAPI.DB using (var reader = result) { if (reader.Read()) - { - user.ID = reader.Get("ID"); - user.Group = reader.Get("Usergroup"); - user.Password = reader.Get("Password"); - user.Name = reader.Get("Username"); - user.Address = reader.Get("IP"); - return user; - } + return LoadUserFromResult(user, result); } } catch (Exception ex) @@ -272,6 +266,37 @@ namespace TShockAPI.DB } throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); } + + public List GetUsers() + { + try + { + List users = new List(); + using (var reader = database.QueryReader("SELECT * FROM Users")) + { + while (reader.Read()) + { + users.Add(LoadUserFromResult(new User(), reader)); + } + return users; + } + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + return null; + } + + private User LoadUserFromResult(User user, QueryResult result) + { + user.ID = result.Get("ID"); + user.Group = result.Get("Usergroup"); + user.Password = result.Get("Password"); + user.Name = result.Get("Username"); + user.Address = result.Get("IP"); + return user; + } } public class User From fb11729547d10cf65b47319baac13a9de04c2b1e Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 19:23:56 +0000 Subject: [PATCH 04/10] Added IEnumerable support to GroupManager to facilitate RestAPI listing groups Added optional exceptions parameter to AddGroup and DeleteGroup to faciliate RestAPI group manipulation. This changes the behaviour of these methods to throw an exception on error instead of returning an error string. Corrected internal lists from being updated in failed DB updates in DeleteGroup and AddGroup Added doc for chatcolor on AddGroup --- TShockAPI/DB/GroupManager.cs | 83 ++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index da3ce79f..27fee59a 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections; using System.Collections.Generic; using System.Data; using System.IO; @@ -24,7 +25,7 @@ using MySql.Data.MySqlClient; namespace TShockAPI.DB { - public class GroupManager + public class GroupManager : IEnumerable { private IDbConnection database; @@ -64,21 +65,42 @@ namespace TShockAPI.DB if (group == "superadmin") return true; - return groups.Any(g => g.Name.Equals(group)); } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return groups.GetEnumerator(); + } + + public Group GetGroupByName(string name) + { + var ret = groups.Where(g => g.Name == name); + return 1 == ret.Count() ? ret.ElementAt(0) : null; + } + /// /// Adds group with name and permissions if it does not exist. /// /// name of group /// parent of group /// permissions - public String AddGroup(String name, string parentname, String permissions, String chatcolor) + /// chatcolor + /// exceptions true indicates use exceptions for errors false otherwise + public String AddGroup(String name, string parentname, String permissions, String chatcolor, bool exceptions = false) { String message = ""; if (GroupExists(name)) + { + if (exceptions) + throw new GroupExistsException(name); return "Error: Group already exists. Use /modGroup to change permissions."; + } var group = new Group(name, null, chatcolor); group.permissions.Add(permissions); @@ -88,6 +110,8 @@ namespace TShockAPI.DB if (parent == null) { var error = "Invalid parent {0} for group {1}".SFormat(group.Name, parentname); + if (exceptions) + throw new GroupManagerException(error); Log.ConsoleError(error); return error; } @@ -98,9 +122,12 @@ namespace TShockAPI.DB ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);" : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3"; if (database.Query(query, name, parentname, permissions, chatcolor) == 1) + { message = "Group " + name + " has been created successfully."; - - groups.Add(group); + groups.Add(group); + } + else if (exceptions) + throw new GroupManagerException("Failed to add group '" + name + "'"); return message; } @@ -115,15 +142,23 @@ namespace TShockAPI.DB return AddGroup(name, parent, permissions, "255,255,255"); } - public String DeleteGroup(String name) + public String DeleteGroup(String name, bool exceptions = false) { String message = ""; if (!GroupExists(name)) + { + if (exceptions) + throw new GroupNotExistException(name); return "Error: Group doesn't exists."; + } if (database.Query("DELETE FROM GroupList WHERE GroupName=@0", name) == 1) + { message = "Group " + name + " has been deleted successfully."; - groups.Remove(TShock.Utils.GetGroup(name)); + groups.Remove(TShock.Utils.GetGroup(name)); + } + else if (exceptions) + throw new GroupManagerException("Failed to delete group '" + name + "'"); return message; } @@ -239,4 +274,36 @@ namespace TShockAPI.DB } } } -} \ No newline at end of file + + [Serializable] + public class GroupManagerException : Exception + { + public GroupManagerException(string message) + : base(message) + { + } + + public GroupManagerException(string message, Exception inner) + : base(message, inner) + { + } + } + + [Serializable] + public class GroupExistsException : GroupManagerException + { + public GroupExistsException(string name) + : base("Group '" + name + "' already exists") + { + } + } + + [Serializable] + public class GroupNotExistException : GroupManagerException + { + public GroupNotExistException(string name) + : base("Group '" + name + "' does not exist") + { + } + } +} From c5497acae70b7f944d06eb9892cce09e47002a7f Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 21:16:50 +0000 Subject: [PATCH 05/10] Check for null in FindPlayer to avoid errors on null object --- TShockAPI/Utils.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index d5c723b9..df5c7207 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -205,6 +205,9 @@ namespace TShockAPI public List FindPlayer(string ply) { var found = new List(); + // Avoid errors caused by null search + if (null == ply) + return found; ply = ply.ToLower(); foreach (TSPlayer player in TShock.Players) { From c7a9ee32cd958c717a8e0ae757a5406979f9aac2 Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 21:22:47 +0000 Subject: [PATCH 06/10] Added ChatColor helper to Group useful for output methods --- TShockAPI/Group.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TShockAPI/Group.cs b/TShockAPI/Group.cs index 01131a5e..16448677 100644 --- a/TShockAPI/Group.cs +++ b/TShockAPI/Group.cs @@ -44,6 +44,11 @@ namespace TShockAPI byte.TryParse(chatcolor.Split(',')[2], out B); } + public string ChatColor() + { + return string.Format("{0}{1}{2}", R.ToString("X2"), G.ToString("X2"), B.ToString("X2")); + } + public virtual bool HasPermission(string permission) { var cur = this; From e5a078957f52d7c9cb9834880b8a819d5e07b3a0 Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 21:24:01 +0000 Subject: [PATCH 07/10] Added default for status = 200 on RestObject constructor --- TShockAPI/Rest/RestObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TShockAPI/Rest/RestObject.cs b/TShockAPI/Rest/RestObject.cs index 6b99d1cc..e63233dc 100644 --- a/TShockAPI/Rest/RestObject.cs +++ b/TShockAPI/Rest/RestObject.cs @@ -41,7 +41,7 @@ namespace Rests set { this["response"] = value; } } - public RestObject(string status) + public RestObject(string status = "200") { Status = status; } From ce5c659e89cfc3d53c8b13a5f48fdefe3b3f44ae Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 21:27:35 +0000 Subject: [PATCH 08/10] Moved RestApi initialisation to main Initialise method so its available early. This helps tools detect a server during its timeconsuming startup. Additional threading may be required to ensure timely responses during high load periods --- TShockAPI/TShock.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 2c50bf97..0c697c24 100755 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -210,6 +210,9 @@ namespace TShockAPI Commands.InitCommands(); //RconHandler.StartThread(); + if (Config.RestApiEnabled) + RestApi.Start(); + if (Config.BufferPackets) PacketBuffer = new PacketBufferer(); @@ -434,8 +437,6 @@ namespace TShockAPI AuthToken = 0; } Regions.ReloadAllRegions(); - if (Config.RestApiEnabled) - RestApi.Start(); StatTracker.CheckIn(); FixChestStacks(); From 78cc7a6f753ccba977e787f46c0816d59ab66af8 Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 18:48:28 +0000 Subject: [PATCH 09/10] Added the ability to delete a ban by name in the same way as a user --- TShockAPI/DB/BanManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index 57eaa909..65ab171a 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -123,11 +123,15 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)"); return false; } - public bool RemoveBan(string ip) + public bool RemoveBan(string match, bool byName = false, bool casesensitive = true) { try { - return database.Query("DELETE FROM Bans WHERE IP=@0", ip) != 0; + if (!byName) + return database.Query("DELETE FROM Bans WHERE IP=@0", match) != 0; + + var namecol = casesensitive ? "Name" : "UPPER(Name)"; + return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0; } catch (Exception ex) { From 6d08fef2758d8ed6b8a18627c7263c9c0b726317 Mon Sep 17 00:00:00 2001 From: stevenh Date: Mon, 13 Feb 2012 18:32:13 +0000 Subject: [PATCH 10/10] Fixed missing UNIQUE constraints when using SQLite Fixed missing NOT NULL constaints when using MySQL --- TShockAPI/DB/IQueryBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs index 107375da..9157e645 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/DB/IQueryBuilder.cs @@ -42,7 +42,7 @@ namespace TShockAPI.DB var columns = table.Columns.Select( c => - "'{0}' {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", + "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "", c.Unique ? "UNIQUE" : "")); return "CREATE TABLE '{0}' ({1})".SFormat(table.Name, string.Join(", ", columns)); @@ -198,7 +198,7 @@ namespace TShockAPI.DB var columns = table.Columns.Select( c => - "{0} {1} {2} {3}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", + "{0} {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : "")); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); return "CREATE TABLE {0} ({1} {2})".SFormat(table.Name, string.Join(", ", columns),