diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 99c20c84..43f43112 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -180,7 +180,7 @@ namespace TShockAPI add(Permissions.ban, Ban, "ban"); add(Permissions.whitelist, Whitelist, "whitelist"); add(Permissions.maintenance, Off, "off", "exit"); - add(Permissions.maintenance, Restart, "restart"); //Added restart command + add(Permissions.maintenance, Restart, "restart"); add(Permissions.maintenance, OffNoSave, "off-nosave", "exit-nosave"); add(Permissions.maintenance, CheckUpdates, "checkupdates"); add(Permissions.updateplugins, UpdatePlugins, "updateplugins"); @@ -245,7 +245,8 @@ namespace TShockAPI add(Permissions.savessi, OverrideSSI, "overridessi", "ossi"); add(Permissions.xmas, ForceXmas, "forcexmas"); add(Permissions.settempgroup, TempGroup, "tempgroup"); - add(null, Aliases, "aliases"); + add(null, Aliases, "aliases"); + add(Rests.RestPermissions.restmanage, ManageRest, "rest"); //add(null, TestCallbackCommand, "test"); TShockCommands = new ReadOnlyCollection(tshockCommands); @@ -1308,7 +1309,7 @@ namespace TShockAPI string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); TShock.Utils.StopServer(true, reason); } - //Added restart command + private static void Restart(CommandArgs args) { if (Main.runningMono) @@ -1317,21 +1318,8 @@ namespace TShockAPI } else { - if (TShock.Config.ServerSideInventory) - { - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) - { - TShock.InventoryDB.InsertPlayerData(player); - } - } - } - - string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); - TShock.Utils.StopServer(true, reason); - System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); - Environment.Exit(0); + string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); + TShock.Utils.RestartServer(true, reason); } } @@ -1352,7 +1340,59 @@ namespace TShockAPI args.Player.SendInfoMessage("Starting plugin update process:"); args.Player.SendInfoMessage("This may take a while, do not turn off the server!"); new PluginUpdaterThread(args.Player); - } + } + + private static void ManageRest(CommandArgs args) + { + string subCommand = "help"; + if (args.Parameters.Count > 0) + subCommand = args.Parameters[0]; + + switch(subCommand.ToLower()) + { + case "listusers": + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + + Dictionary restUsersTokens = new Dictionary(); + foreach (Rests.SecureRest.TokenData tokenData in TShock.RestApi.Tokens.Values) + { + if (restUsersTokens.ContainsKey(tokenData.Username)) + restUsersTokens[tokenData.Username]++; + else + restUsersTokens.Add(tokenData.Username, 1); + } + + List restUsers = new List( + restUsersTokens.Select(ut => string.Format("{0} ({1} tokens)", ut.Key, ut.Value))); + + PaginationTools.SendPage( + args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(restUsers), new PaginationTools.Settings { + NothingToDisplayString = "There are currently no active REST users.", + HeaderFormat = "Active REST Users ({0}/{1}):", + FooterFormat = "Type /rest listusers {0} for more." + } + ); + + break; + } + case "destroytokens": + { + TShock.RestApi.Tokens.Clear(); + args.Player.SendSuccessMessage("All REST tokens have been destroyed."); + break; + } + default: + { + args.Player.SendInfoMessage("Available REST Sub-Commands:"); + args.Player.SendMessage("listusers - Lists all REST users and their current active tokens.", Color.White); + args.Player.SendMessage("destroytokens - Destroys all current REST tokens.", Color.White); + break; + } + } + } #endregion Server Maintenence Commands @@ -2373,14 +2413,10 @@ namespace TShockAPI private static void Reload(CommandArgs args) { - FileTools.SetupConfig(); - TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); - TShock.Groups.LoadPermisions(); - TShock.Regions.ReloadAllRegions(); + TShock.Utils.Reload(args.Player); + args.Player.SendSuccessMessage( "Configuration, permissions, and regions reload complete. Some changes may require a server restart."); - - Hooks.GeneralHooks.OnReloadEvent(args.Player); } private static void ServerPassword(CommandArgs args) diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index 304de298..db5a6488 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -129,7 +129,7 @@ namespace TShockAPI [Description("This will announce a player's location on join")] public bool EnableGeoIP; - [Description("This will turn on a token requirement for the /status API endpoint.")] public bool + [Description("This will turn on token requirement for the public REST API endpoints.")] public bool EnableTokenEndpointAuthentication; [Description("Deprecated. Use ServerName instead.")] public string ServerNickname = "TShock Server"; @@ -256,6 +256,11 @@ namespace TShockAPI [Description("#.#.#. = Red/Blue/Green - RGB Colors for broadcasts. Max value: 255.")] public float[] BroadcastRGB = {127,255,212}; + // TODO: Get rid of this when the old REST permission model is removed. + [Description( + "Whether the REST API should use the new permission model. Note: The old permission model will become depracted in the future." + )] public bool RestUseNewPermissionModel = true; + /// /// Reads a configuration file from a given path /// diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index b853b32f..e7dee2b7 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -73,7 +73,7 @@ namespace TShockAPI [Description("Allows you to bypass the max slots for up to 5 slots above your max")] public static readonly string reservedslot; - [Description("User is notified when an update is available")] public static readonly string maintenance; + [Description("User is notified when an update is available, user can turn off / restart the server.")] public static readonly string maintenance; [Description("User can kick others")] public static readonly string kick; @@ -166,9 +166,6 @@ namespace TShockAPI [Description("User can save all the players SSI state.")] public static readonly string savessi; - [Description("User can use rest api calls.")] - public static readonly string restapi; - [Description("User can force the server to Christmas mode.")] public static readonly string xmas; [Description("User can use /home.")] public static readonly string home; @@ -177,16 +174,14 @@ namespace TShockAPI [Description("User can elevate other users' groups temporarily.")] public static readonly string settempgroup; - [Description("User can download updates to plugins that are currently running.")] public static readonly string updateplugins; - static Permissions() + [Description("User can download updates to plugins that are currently running.")] public static readonly string updateplugins; + + static Permissions() { foreach (var field in typeof (Permissions).GetFields()) { field.SetValue(null, field.Name); } - - //Backwards compatability. - restapi = "api"; } /// diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index 3f2ae2e2..0aaa1ad4 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -36,7 +36,16 @@ namespace Rests /// Parameters in the url /// {x} in urltemplate /// Response object or null to not handle request - public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters); + public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters); + + /// + /// Secure Rest command delegate including token data. + /// + /// Parameters in the url + /// {x} in urltemplate + /// The data of stored for the provided token. + /// Response object or null to not handle request + public delegate object SecureRestCommandD(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData); public class Rest : IDisposable { @@ -165,24 +174,47 @@ namespace Rests } catch (Exception exception) { - return new Dictionary + return new RestObject("500") { - {"status", "500"}, {"error", "Internal server error."}, {"errormsg", exception.Message}, {"stacktrace", exception.StackTrace}, }; } - return new Dictionary + return new RestObject("404") { - {"status", "404"}, {"error", "Specified API endpoint doesn't exist. Refer to the documentation for a list of valid endpoints."} }; } protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) { - return cmd.Callback(verbs, parms); + object result = cmd.Execute(verbs, parms); + if (cmd.DoLog) + Log.ConsoleInfo("Anonymous requested REST endpoint: " + BuildRequestUri(cmd, verbs, parms, false)); + + return result; + } + + protected virtual string BuildRequestUri( + RestCommand cmd, RestVerbs verbs, IParameterCollection parms, bool includeToken = true + ) { + StringBuilder requestBuilder = new StringBuilder(cmd.UriTemplate); + char separator = '?'; + foreach (IParameter paramImpl in parms) + { + Parameter param = (paramImpl as Parameter); + if (param == null || (!includeToken && param.Name.Equals("token", StringComparison.InvariantCultureIgnoreCase))) + continue; + + requestBuilder.Append(separator); + requestBuilder.Append(param.Name); + requestBuilder.Append('='); + requestBuilder.Append(param.Value); + separator = '&'; + } + + return requestBuilder.ToString(); } #region Dispose diff --git a/TShockAPI/Rest/RestCommand.cs b/TShockAPI/Rest/RestCommand.cs index 334752de..afe96d33 100644 --- a/TShockAPI/Rest/RestCommand.cs +++ b/TShockAPI/Rest/RestCommand.cs @@ -16,8 +16,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System.Linq; -using System.Text.RegularExpressions; - +using System.Text.RegularExpressions; +using HttpServer; + namespace Rests { public class RestCommand @@ -26,8 +27,10 @@ namespace Rests public string UriTemplate { get; protected set; } public string UriVerbMatch { get; protected set; } public string[] UriVerbs { get; protected set; } - public RestCommandD Callback { get; protected set; } - public bool RequiresToken { get; set; } + public virtual bool RequiresToken { get { return false; } } + public bool DoLog { get; set; } + + private RestCommandD callback; /// /// @@ -42,8 +45,8 @@ namespace Rests UriVerbMatch = string.Format("^{0}$", string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}"))); var matches = Regex.Matches(uritemplate, "\\{([^\\{\\}]*)\\}"); UriVerbs = (from Match match in matches select match.Groups[1].Value).ToArray(); - Callback = callback; - RequiresToken = true; + this.callback = callback; + DoLog = true; } /// @@ -59,6 +62,44 @@ namespace Rests public bool HasVerbs { get { return UriVerbs.Length > 0; } + } + + public virtual object Execute(RestVerbs verbs, IParameterCollection parameters) + { + return callback(verbs, parameters); } + } + + public class SecureRestCommand: RestCommand + { + public override bool RequiresToken { get { return true; } } + public string[] Permissions { get; set; } + + private SecureRestCommandD callback; + + public SecureRestCommand(string name, string uritemplate, SecureRestCommandD callback, params string[] permissions) + : base(name, uritemplate, null) + { + this.callback = callback; + Permissions = permissions; + } + + public SecureRestCommand(string uritemplate, SecureRestCommandD callback, params string[] permissions) + : this(string.Empty, uritemplate, callback, permissions) + { + } + + public override object Execute(RestVerbs verbs, IParameterCollection parameters) + { + return new RestObject("401") { Error = "Not authorized. The specified API endpoint requires a token." }; + } + + public object Execute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (tokenData.Equals(SecureRest.TokenData.None)) + return new RestObject("401") { Error = "Not authorized. The specified API endpoint requires a token." }; + + return callback(verbs, parameters, tokenData); + } } } \ No newline at end of file diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 45cdb309..23f284bc 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -17,7 +17,8 @@ along with this program. If not, see . */ using System; using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; +using System.IO; using System.Linq; using HttpServer; using Rests; @@ -37,92 +38,175 @@ namespace TShockAPI public void RegisterRestfulCommands() { - // Server Commands - Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); - Rest.Register(new RestCommand("/v2/server/off", ServerOff)); - Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); - Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); - Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); - Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); + // Server Commands + if (TShock.Config.EnableTokenEndpointAuthentication) + { + Rest.Register(new SecureRestCommand("/v2/server/status", ServerStatusV2)); + Rest.Register(new SecureRestCommand("/status", ServerStatus)); + Rest.Register(new SecureRestCommand("/v3/server/motd", ServerMotd)); + Rest.Register(new SecureRestCommand("/v3/server/rules", ServerRules)); + } + else + { + Rest.Register(new RestCommand("/v2/server/status", (a, b) => this.ServerStatusV2(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/status", (a, b) => this.ServerStatusV2(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/v3/server/motd", (a, b) => this.ServerMotd(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/v3/server/rules", (a, b) => this.ServerRules(a, b, SecureRest.TokenData.None))); + } + + Rest.Register(new SecureRestCommand("/v2/server/broadcast", ServerBroadcast)); + Rest.Register(new SecureRestCommand("/v3/server/reload", ServerReload, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/server/off", ServerOff, RestPermissions.restmaintenance)); + Rest.Register(new SecureRestCommand("/v3/server/restart", ServerRestart, RestPermissions.restmaintenance)); + Rest.Register(new SecureRestCommand("/v2/server/rawcmd", ServerCommand, RestPermissions.restrawcommand)); + Rest.Register(new SecureRestCommand("/v3/server/rawcmd", ServerCommandV3, RestPermissions.restrawcommand)); + Rest.Register(new SecureRestCommand("/tokentest", ServerTokenTest)); // User Commands - Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); - Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); - Rest.Register(new RestCommand("/v2/users/list", UserListV2)); - Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); - Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); - Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); + Rest.Register(new SecureRestCommand("/v2/users/activelist", UserActiveListV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/create", UserCreateV2, RestPermissions.restmanageusers) { DoLog = false }); + Rest.Register(new SecureRestCommand("/v2/users/list", UserListV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/read", UserInfoV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/destroy", UserDestroyV2, RestPermissions.restmanageusers)); + Rest.Register(new SecureRestCommand("/v2/users/update", UserUpdateV2, RestPermissions.restmanageusers) { DoLog = false }); // Ban Commands - Rest.Register(new RestCommand("/bans/create", BanCreate)); - Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); - Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); - Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); + Rest.Register(new SecureRestCommand("/bans/create", BanCreate, RestPermissions.restmanagebans)); + Rest.Register(new SecureRestCommand("/v2/bans/list", BanListV2, RestPermissions.restviewbans)); + Rest.Register(new SecureRestCommand("/v2/bans/read", BanInfoV2, RestPermissions.restviewbans)); + Rest.Register(new SecureRestCommand("/v2/bans/destroy", BanDestroyV2, RestPermissions.restmanagebans)); // World Commands - Rest.Register(new RestCommand("/world/read", WorldRead)); - Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); - Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); - Rest.Register(new RestCommand("/v2/world/save", WorldSave)); - Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); - Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); + Rest.Register(new SecureRestCommand("/world/read", WorldRead)); + Rest.Register(new SecureRestCommand("/world/meteor", WorldMeteor, RestPermissions.restcauseevents)); + Rest.Register(new SecureRestCommand("/world/bloodmoon/{bool}", WorldBloodmoon, RestPermissions.restcauseevents)); + Rest.Register(new SecureRestCommand("/v2/world/save", WorldSave, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/world/butcher", WorldButcher, RestPermissions.restbutcher)); // Player Commands - Rest.Register(new RestCommand("/lists/players", PlayerList)); - Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); - Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); - Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); - Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); - Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); - Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); - Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); + Rest.Register(new SecureRestCommand("/lists/players", PlayerList)); + Rest.Register(new SecureRestCommand("/v2/players/list", PlayerListV2)); + Rest.Register(new SecureRestCommand("/v2/players/read", PlayerReadV2, RestPermissions.restuserinfo)); + Rest.Register(new SecureRestCommand("/v2/players/kick", PlayerKickV2, RestPermissions.restkick)); + Rest.Register(new SecureRestCommand("/v2/players/ban", PlayerBanV2, RestPermissions.restban, RestPermissions.restmanagebans)); + Rest.Register(new SecureRestCommand("/v2/players/kill", PlayerKill, RestPermissions.restkill)); + Rest.Register(new SecureRestCommand("/v2/players/mute", PlayerMute, RestPermissions.restmute)); + Rest.Register(new SecureRestCommand("/v2/players/unmute", PlayerUnMute, RestPermissions.restmute)); // Group Commands - Rest.Register(new RestCommand("/v2/groups/list", GroupList)); - Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); - Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); - Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); - Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); + Rest.Register(new SecureRestCommand("/v2/groups/list", GroupList, RestPermissions.restviewgroups)); + Rest.Register(new SecureRestCommand("/v2/groups/read", GroupInfo, RestPermissions.restviewgroups)); + Rest.Register(new SecureRestCommand("/v2/groups/destroy", GroupDestroy, RestPermissions.restmanagegroups)); + Rest.Register(new SecureRestCommand("/v2/groups/create", GroupCreate, RestPermissions.restmanagegroups)); + Rest.Register(new SecureRestCommand("/v2/groups/update", GroupUpdate, RestPermissions.restmanagegroups)); } #region RestServerMethods - private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) + private object ServerCommand(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (string.IsNullOrWhiteSpace(parameters["cmd"])) - return RestMissingParam("cmd"); + return RestMissingParam("cmd"); + + Group restPlayerGroup; + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) + restPlayerGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + else + restPlayerGroup = new SuperAdminGroup(); - TSRestPlayer tr = new TSRestPlayer(); + TSRestPlayer tr = new TSRestPlayer(tokenData.Username, restPlayerGroup); Commands.HandleCommand(tr, parameters["cmd"]); return RestResponse(string.Join("\n", tr.GetCommandOutput())); + } + + private object ServerCommandV3(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (string.IsNullOrWhiteSpace(parameters["cmd"])) + return RestMissingParam("cmd"); + + Group restPlayerGroup; + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) + restPlayerGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + else + restPlayerGroup = new SuperAdminGroup(); + + TSRestPlayer tr = new TSRestPlayer(tokenData.Username, restPlayerGroup); + Commands.HandleCommand(tr, parameters["cmd"]); + return new RestObject() + { + {"response", tr.GetCommandOutput()} + }; } - private object ServerOff(RestVerbs verbs, IParameterCollection parameters) + private object ServerOff(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (!GetBool(parameters["confirm"], false)) return RestInvalidParam("confirm"); // Inform players the server is shutting down - var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; - TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), msg); + var reason = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; + TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), reason); return RestResponse("The server is shutting down"); + } + + private object ServerRestart(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (!GetBool(parameters["confirm"], false)) + return RestInvalidParam("confirm"); + + // Inform players the server is shutting down + var reason = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is restarting" : parameters["message"]; + TShock.Utils.RestartServer(!GetBool(parameters["nosave"], false), reason); + + return RestResponse("The server is shutting down and will attempt to restart"); + } + + private object ServerReload(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + TShock.Utils.Reload(new TSRestPlayer(tokenData.Username, TShock.Groups.GetGroupByName(tokenData.UserGroupName))); + + return RestResponse("Configuration, permissions, and regions reload complete. Some changes may require a server restart."); } - private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) + private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var msg = parameters["msg"]; if (string.IsNullOrWhiteSpace(msg)) return RestMissingParam("msg"); TShock.Utils.Broadcast(msg); return RestResponse("The message was broadcasted successfully"); + } + + private object ServerMotd(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + string motdFilePath = Path.Combine(TShock.SavePath, "motd.txt"); + if (!File.Exists(motdFilePath)) + return this.RestError("The motd.txt was not found.", "500"); + + return new RestObject() + { + {"motd", File.ReadAllLines(motdFilePath)} + }; + } + + private object ServerRules(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + string rulesFilePath = Path.Combine(TShock.SavePath, "rules.txt"); + if (!File.Exists(rulesFilePath)) + return this.RestError("The rules.txt was not found.", "500"); + + return new RestObject() + { + {"rules", File.ReadAllLines(rulesFilePath)} + }; } - private object ServerStatus(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatus(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { - if (TShock.Config.EnableTokenEndpointAuthentication) - return RestError("Server settings require a token for this API call"); - var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); return new RestObject() { @@ -133,18 +217,17 @@ namespace TShockAPI }; } - private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { - if (TShock.Config.EnableTokenEndpointAuthentication) - return RestError("Server settings require a token for this API call"); - var ret = new RestObject() { {"name", TShock.Config.ServerName}, {"port", TShock.Config.ServerPort}, {"playercount", Main.player.Where(p => null != p && p.active).Count()}, {"maxplayers", TShock.Config.MaxSlots}, - {"world", Main.worldName} + {"world", Main.worldName}, + {"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")}, + {"serverpassword", !string.IsNullOrEmpty(TShock.Config.ServerPassword)} }; if (GetBool(parameters["players"], false)) @@ -173,28 +256,33 @@ namespace TShockAPI rules.Add("HardcoreOnly", TShock.Config.HardcoreOnly); rules.Add("PvPMode", TShock.Config.PvPMode); rules.Add("SpawnProtection", TShock.Config.SpawnProtection); - rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + rules.Add("ServerSideInventory", TShock.Config.ServerSideInventory); ret.Add("rules", rules); } return ret; } - private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) - { - return RestResponse("Token is valid and was passed through correctly"); + private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + return new RestObject() + { + {"response", "Token is valid and was passed through correctly."}, + {"associateduser", tokenData.Username} + }; } #endregion #region RestUserMethods - private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters) + private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { { "activeusers", string.Join("\t", TShock.Players.Where(p => null != p && null != p.UserAccountName && p.Active).Select(p => p.UserAccountName)) } }; } - private object UserListV2(RestVerbs verbs, IParameterCollection parameters) + private object UserListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary(){ {"name", p.Name}, @@ -204,7 +292,7 @@ namespace TShockAPI }) } }; } - private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) + private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var username = parameters["user"]; if (string.IsNullOrWhiteSpace(username)) @@ -232,7 +320,7 @@ namespace TShockAPI return RestResponse("User was successfully created"); } - private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters) + private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -274,7 +362,7 @@ namespace TShockAPI return response; } - private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) + private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -292,7 +380,7 @@ namespace TShockAPI return RestResponse("User deleted successfully"); } - private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters) + private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -306,7 +394,7 @@ namespace TShockAPI #region RestBanMethods - private object BanCreate(RestVerbs verbs, IParameterCollection parameters) + private object BanCreate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ip = parameters["ip"]; var name = parameters["name"]; @@ -325,7 +413,7 @@ namespace TShockAPI return RestResponse("Ban created successfully"); } - private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters) + private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = BanFind(parameters); if (ret is RestObject) @@ -357,7 +445,7 @@ namespace TShockAPI return RestResponse("Ban deleted successfully"); } - private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters) + private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = BanFind(parameters); if (ret is RestObject) @@ -371,7 +459,7 @@ namespace TShockAPI }; } - private object BanListV2(RestVerbs verbs, IParameterCollection parameters) + private object BanListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var banList = new ArrayList(); foreach (var ban in TShock.Bans.GetBans()) @@ -393,7 +481,7 @@ namespace TShockAPI #region RestWorldMethods - private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) + private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool autoSave; if (!bool.TryParse(verbs["bool"], out autoSave)) @@ -403,14 +491,14 @@ namespace TShockAPI return RestResponse("AutoSave has been set to " + autoSave); } - private object WorldSave(RestVerbs verbs, IParameterCollection parameters) + private object WorldSave(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { SaveManager.Instance.SaveWorld(); return RestResponse("World saved"); } - private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) + private object WorldButcher(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool killFriendly; if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) @@ -432,7 +520,7 @@ namespace TShockAPI return RestResponse(killcount + " NPCs have been killed"); } - private object WorldRead(RestVerbs verbs, IParameterCollection parameters) + private object WorldRead(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { @@ -445,7 +533,7 @@ namespace TShockAPI }; } - private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) + private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (null == WorldGen.genRand) WorldGen.genRand = new Random(); @@ -453,7 +541,7 @@ namespace TShockAPI return RestResponse("Meteor has been spawned"); } - private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters) + private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool bloodmoon; if (!bool.TryParse(verbs["bool"], out bloodmoon)) @@ -467,23 +555,23 @@ namespace TShockAPI #region RestPlayerMethods - private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) + private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return PlayerSetMute(parameters, false); } - private object PlayerMute(RestVerbs verbs, IParameterCollection parameters) + private object PlayerMute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return PlayerSetMute(parameters, true); } - private object PlayerList(RestVerbs verbs, IParameterCollection parameters) + private object PlayerList(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; } - private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var playerList = new ArrayList(); foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) @@ -495,7 +583,7 @@ namespace TShockAPI return new RestObject() { { "players", playerList } }; } - private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -515,7 +603,7 @@ namespace TShockAPI }; } - private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -526,7 +614,7 @@ namespace TShockAPI return RestResponse("Player " + player.Name + " was kicked"); } - private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -539,7 +627,7 @@ namespace TShockAPI return RestResponse("Player " + player.Name + " was banned"); } - private object PlayerKill(RestVerbs verbs, IParameterCollection parameters) + private object PlayerKill(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -556,7 +644,7 @@ namespace TShockAPI #region RestGroupMethods - private object GroupList(RestVerbs verbs, IParameterCollection parameters) + private object GroupList(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var groups = new ArrayList(); foreach (Group group in TShock.Groups) @@ -566,7 +654,7 @@ namespace TShockAPI return new RestObject() { { "groups", groups } }; } - private object GroupInfo(RestVerbs verbs, IParameterCollection parameters) + private object GroupInfo(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) @@ -583,7 +671,7 @@ namespace TShockAPI }; } - private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) + private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) @@ -602,7 +690,7 @@ namespace TShockAPI return RestResponse("Group '" + group.Name + "' deleted successfully"); } - private object GroupCreate(RestVerbs verbs, IParameterCollection parameters) + private object GroupCreate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var name = parameters["group"]; if (string.IsNullOrWhiteSpace(name)) @@ -619,7 +707,7 @@ namespace TShockAPI return RestResponse("Group '" + name + "' created successfully"); } - private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters) + private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) diff --git a/TShockAPI/Rest/RestPermissions.cs b/TShockAPI/Rest/RestPermissions.cs new file mode 100644 index 00000000..4662276d --- /dev/null +++ b/TShockAPI/Rest/RestPermissions.cs @@ -0,0 +1,93 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2012 The TShock Team + +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.ComponentModel; +using System.Linq; + +namespace Rests +{ + public static class RestPermissions + { + [Description("User can create REST tokens.")] + public static readonly string restapi; + + [Description("User or REST user can destroy all REST tokens.")] + public static readonly string restmanage; + + + [Description("REST user can turn off / restart the server.")] + public static readonly string restmaintenance; + + [Description("REST user can reload configurations, save the world and set auto save settings.")] + public static readonly string restcfg; + + + [Description("REST user can list and get detailed information about users.")] + public static readonly string restviewusers; + + [Description("REST user can alter users.")] + public static readonly string restmanageusers; + + [Description("REST user can list and get detailed information about bans.")] + public static readonly string restviewbans; + + [Description("REST user can alter bans.")] + public static readonly string restmanagebans; + + [Description("REST user can list and get detailed information about groups.")] + public static readonly string restviewgroups; + + [Description("REST user can alter groups.")] + public static readonly string restmanagegroups; + + + [Description("REST user can get user information.")] + public static readonly string restuserinfo; + + [Description("REST user can kick players.")] + public static readonly string restkick; + + [Description("REST user can ban players.")] + public static readonly string restban; + + [Description("REST user can mute and unmute players.")] + public static readonly string restmute; + + [Description("REST user can kill players.")] + public static readonly string restkill; + + + [Description("REST user can drop meteors or change bloodmoon.")] + public static readonly string restcauseevents; + + [Description("REST user can butcher npcs.")] + public static readonly string restbutcher; + + + [Description("REST user can run raw TShock commands (the raw command permissions are also checked though).")] + public static readonly string restrawcommand; + + static RestPermissions() + { + foreach (var field in typeof (RestPermissions).GetFields()) + { + field.SetValue(null, field.Name); + } + } + } +} diff --git a/TShockAPI/Rest/SecureRest.cs b/TShockAPI/Rest/SecureRest.cs index 987cd295..cedeaafe 100644 --- a/TShockAPI/Rest/SecureRest.cs +++ b/TShockAPI/Rest/SecureRest.cs @@ -18,38 +18,66 @@ along with this program. If not, see . using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using HttpServer; - +using System.Net; +using System.Text; +using HttpServer; +using TShockAPI; +using TShockAPI.DB; + namespace Rests { - /// - /// - /// - /// Username to verify - /// Password to verify - /// Returning a restobject with a null error means a successful verification. - public delegate RestObject VerifyD(string username, string password); - public class SecureRest : Rest - { - public Dictionary Tokens { get; protected set; } - public event VerifyD Verify; + { + public struct TokenData + { + public static readonly TokenData None = default(TokenData); + + public string Username { get; set; } + public string UserGroupName { get; set; } + } + + public Dictionary Tokens { get; protected set; } public SecureRest(IPAddress ip, int port) : base(ip, port) { - Tokens = new Dictionary(); - Register(new RestCommand("/token/create/{username}/{password}", NewToken) {RequiresToken = false}); - Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { RequiresToken = false }); - Register(new RestCommand("/token/destroy/{token}", DestroyToken) {RequiresToken = true}); - foreach (KeyValuePair t in TShockAPI.TShock.RESTStartupTokens) + Tokens = new Dictionary(); + + Register(new RestCommand("/token/create/{username}/{password}", NewToken) { DoLog = false }); + Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { DoLog = false }); + Register(new SecureRestCommand("/token/destroy/{token}", DestroyToken)); + Register(new SecureRestCommand("/v3/token/destroy/all", DestroyAllTokens, RestPermissions.restmanage)); + + foreach (KeyValuePair t in TShockAPI.TShock.RESTStartupTokens) { Tokens.Add(t.Key, t.Value); } + + // TODO: Get rid of this when the old REST permission model is removed. + if (!TShock.Config.RestUseNewPermissionModel) + { + string warningMessage = string.Concat( + "You're using the old REST permission model which is highly vulnerable in matter of security. ", + "The old model will be removed with the next maintenance release of TShock. In order to switch to the new model, ", + "change the config setting \"RestUseNewPermissionModel\" to true." + ); + Log.Warn(warningMessage); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(warningMessage); + Console.ForegroundColor = ConsoleColor.Gray; + } + else + { + string warningMessage = string.Concat( + "You're using the new more secure REST permission model which can lead to compatibility problems ", + "with existing REST services. If compatibility problems occur, you can switch back to the unsecure permission ", + "model by changing the config setting \"RestUseNewPermissionModel\" to false, which is not recommended." + ); + Log.ConsoleInfo(warningMessage); + } } - private object DestroyToken(RestVerbs verbs, IParameterCollection parameters) + private object DestroyToken(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var token = verbs["token"]; try @@ -58,11 +86,19 @@ namespace Rests } catch (Exception) { - return new Dictionary - {{"status", "400"}, {"error", "The specified token queued for destruction failed to be deleted."}}; + return new RestObject("400") + { Error = "The specified token queued for destruction failed to be deleted." }; } - return new Dictionary - {{"status", "200"}, {"response", "Requested token was successfully destroyed."}}; + return new RestObject() + { Response = "Requested token was successfully destroyed." }; + } + + private object DestroyAllTokens(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + Tokens.Clear(); + + return new RestObject() + { Response = "All tokens were successfully destroyed." }; } private object NewTokenV2(RestVerbs verbs, IParameterCollection parameters) @@ -70,29 +106,7 @@ namespace Rests var user = parameters["username"]; var pass = verbs["password"]; - RestObject obj = null; - if (Verify != null) - obj = Verify(user, pass); - - if (obj == null) - obj = new RestObject("401") { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; - - if (obj.Error != null) - return obj; - - string hash; - var rand = new Random(); - var randbytes = new byte[32]; - do - { - rand.NextBytes(randbytes); - hash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); - } while (Tokens.ContainsKey(hash)); - - Tokens.Add(hash, user); - - obj["token"] = hash; - return obj; + return this.NewTokenInternal(user, pass); } private object NewToken(RestVerbs verbs, IParameterCollection parameters) @@ -100,55 +114,84 @@ namespace Rests var user = verbs["username"]; var pass = verbs["password"]; - RestObject obj = null; - if (Verify != null) - obj = Verify(user, pass); - - if (obj == null) - obj = new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - - if (obj.Error != null) - return obj; - - string hash; + RestObject response = this.NewTokenInternal(user, pass); + response["deprecated"] = "This endpoint is depracted and will be removed in the future."; + return response; + } + + private RestObject NewTokenInternal(string username, string password) + { + User userAccount = TShock.Users.GetUserByName(username); + if (userAccount == null || !string.IsNullOrWhiteSpace(userAccount.Address)) + return new RestObject("401") + { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; + + if (!TShock.Utils.HashPassword(password).Equals(userAccount.Password, StringComparison.InvariantCultureIgnoreCase)) + return new RestObject("401") + { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; + + Group userGroup = TShock.Utils.GetGroup(userAccount.Group); + if (!userGroup.HasPermission(RestPermissions.restapi) && userAccount.Group != "superadmin") + return new RestObject("403") + { Error = "Although your account was successfully found and identified, your account lacks the permission required to use the API. (restapi)" }; + + string tokenHash; var rand = new Random(); var randbytes = new byte[32]; do { rand.NextBytes(randbytes); - hash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); - } while (Tokens.ContainsKey(hash)); + tokenHash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); + } while (Tokens.ContainsKey(tokenHash)); - Tokens.Add(hash, user); - - obj["token"] = hash; - obj["deprecated"] = "This method will be removed from TShock in 3.6."; - return obj; + Tokens.Add(tokenHash, new TokenData { Username = userAccount.Name, UserGroupName = userGroup.Name }); + + RestObject response = new RestObject() { Response = "Successful login" }; + response["token"] = tokenHash; + return response; } - protected override object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) { - if (cmd.RequiresToken) - { - var strtoken = parms["token"]; - if (strtoken == null) - return new Dictionary - {{"status", "401"}, {"error", "Not authorized. The specified API endpoint requires a token."}}; - - object token; - if (!Tokens.TryGetValue(strtoken, out token)) - return new Dictionary - { - {"status", "403"}, - { - "error", - "Not authorized. The specified API endpoint requires a token, but the provided token was not valid." - } - }; - } - return base.ExecuteCommand(cmd, verbs, parms); + if (!cmd.RequiresToken) + return base.ExecuteCommand(cmd, verbs, parms); + + var token = parms["token"]; + if (token == null) + return new RestObject("401") + { Error = "Not authorized. The specified API endpoint requires a token." }; + + SecureRestCommand secureCmd = (SecureRestCommand)cmd; + TokenData tokenData; + if (!Tokens.TryGetValue(token, out tokenData)) + return new RestObject("403") + { Error = "Not authorized. The specified API endpoint requires a token, but the provided token was not valid." }; + + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) { + Group userGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + if (userGroup == null) + { + Tokens.Remove(token); + + return new RestObject("403") + { Error = "Not authorized. The provided token became invalid due to group changes, please create a new token." }; + } + + if (secureCmd.Permissions.Length > 0 && secureCmd.Permissions.All(perm => !userGroup.HasPermission(perm))) + { + return new RestObject("403") + { Error = string.Format("Not authorized. User \"{0}\" has no access to use the specified API endpoint.", tokenData.Username) }; + } + } + + object result = secureCmd.Execute(verbs, parms, tokenData); + if (cmd.DoLog) + TShock.Utils.SendLogs(string.Format( + "\"{0}\" requested REST endpoint: {1}", tokenData.Username, this.BuildRequestUri(cmd, verbs, parms, false)), + Color.PaleVioletRed); + + return result; } } } \ No newline at end of file diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index bd13cfd4..b8443a16 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -759,13 +759,13 @@ namespace TShockAPI } } - public class TSRestPlayer : TSServerPlayer + public class TSRestPlayer : TSPlayer { - internal List CommandReturn = new List(); + internal List CommandOutput = new List(); - public TSRestPlayer() + public TSRestPlayer(string playerName, Group playerGroup): base(playerName) { - Group = new SuperAdminGroup(); + Group = playerGroup; AwaitingResponse = new Dictionary>(); } @@ -781,7 +781,7 @@ namespace TShockAPI public override void SendMessage(string msg, byte red, byte green, byte blue) { - CommandReturn.Add(msg); + this.CommandOutput.Add(msg); } public override void SendInfoMessage(string msg) @@ -806,7 +806,7 @@ namespace TShockAPI public List GetCommandOutput() { - return CommandReturn; + return this.CommandOutput; } } diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 7449e780..833bf3f3 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -72,7 +72,7 @@ namespace TShockAPI /// /// Used for implementing REST Tokens prior to the REST system starting up. /// - public static Dictionary RESTStartupTokens = new Dictionary(); + public static Dictionary RESTStartupTokens = new Dictionary(); /// /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. @@ -220,7 +220,6 @@ namespace TShockAPI RememberedPos = new RememberedPosManager(DB); InventoryDB = new InventoryManager(DB); RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); - RestApi.Verify += RestApi_Verify; RestApi.Port = Config.RestApiPort; RestManager = new RestManager(RestApi); RestManager.RegisterRestfulCommands(); @@ -294,33 +293,6 @@ namespace TShockAPI // ReSharper restore LocalizableElement } - private RestObject RestApi_Verify(string username, string password) - { - var userAccount = Users.GetUserByName(username); - if (userAccount == null) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (Utils.HashPassword(password).ToUpper() != userAccount.Password.ToUpper()) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (!Utils.GetGroup(userAccount.Group).HasPermission(Permissions.restapi) && userAccount.Group != "superadmin") - { - return new RestObject("403") - { - Error = - "Although your account was successfully found and identified, your account lacks the permission required to use the API. (api)" - }; - } - - return new RestObject("200") {Response = "Successful login"}; //Maybe return some user info too? - } - protected override void Dispose(bool disposing) { if (disposing) @@ -507,7 +479,7 @@ namespace TShockAPI break; case "-rest-token": string token = Convert.ToString(parms[++i]); - RESTStartupTokens.Add(token, "null"); + RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" }); Console.WriteLine("Startup parameter overrode REST token."); break; case "-rest-enabled": diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index b98c1645..e21ddc8a 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -89,6 +89,7 @@ + @@ -191,7 +192,7 @@ - +