-Added REST per-endpoint permissions.

-Added REST endpoint "/v2/server/restart".
-Added REST endpoint "/v2/server/reload".
-Added REST endpoint "/v3/server/rawcmd", will output all returned lines as an array instead.
-Added "uptime", "serverpassword", "rules/ServerSideInventory" fields to REST endpoint "/v2/server/status".
-REST requests are now logged.
-Endpoint "/v2/server/rawcmd" does now check whether the user has the sufficient permission to execute the command.
-Fixed Config.EnableTokenEndpointAuthentication not working properly before.
-Removed obsolete "api" permission (only "restapi" now).
This commit is contained in:
CoderCow 2013-07-25 12:31:11 +02:00
parent 4e7b497ae4
commit 0ea83746cf
9 changed files with 415 additions and 237 deletions

View file

@ -180,7 +180,7 @@ namespace TShockAPI
add(Permissions.ban, Ban, "ban"); add(Permissions.ban, Ban, "ban");
add(Permissions.whitelist, Whitelist, "whitelist"); add(Permissions.whitelist, Whitelist, "whitelist");
add(Permissions.maintenance, Off, "off", "exit"); 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, OffNoSave, "off-nosave", "exit-nosave");
add(Permissions.maintenance, CheckUpdates, "checkupdates"); add(Permissions.maintenance, CheckUpdates, "checkupdates");
add(Permissions.updateplugins, UpdatePlugins, "updateplugins"); add(Permissions.updateplugins, UpdatePlugins, "updateplugins");
@ -1312,7 +1312,7 @@ namespace TShockAPI
string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!");
TShock.Utils.StopServer(true, reason); TShock.Utils.StopServer(true, reason);
} }
//Added restart command
private static void Restart(CommandArgs args) private static void Restart(CommandArgs args)
{ {
if (Main.runningMono) if (Main.runningMono)
@ -1321,21 +1321,8 @@ namespace TShockAPI
} }
else 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!"); string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!");
TShock.Utils.StopServer(true, reason); TShock.Utils.RestartServer(true, reason);
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Environment.Exit(0);
} }
} }
@ -2326,14 +2313,10 @@ namespace TShockAPI
private static void Reload(CommandArgs args) private static void Reload(CommandArgs args)
{ {
FileTools.SetupConfig(); TShock.Utils.Reload(args.Player);
TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
TShock.Groups.LoadPermisions();
TShock.Regions.ReloadAllRegions();
args.Player.SendSuccessMessage( args.Player.SendSuccessMessage(
"Configuration, permissions, and regions reload complete. Some changes may require a server restart."); "Configuration, permissions, and regions reload complete. Some changes may require a server restart.");
Hooks.GeneralHooks.OnReloadEvent(args.Player);
} }
private static void ServerPassword(CommandArgs args) private static void ServerPassword(CommandArgs args)

View file

@ -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 [Description("Allows you to bypass the max slots for up to 5 slots above your max")] public static readonly string
reservedslot; 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; [Description("User can kick others")] public static readonly string kick;
@ -178,15 +178,67 @@ namespace TShockAPI
[Description("User can elevate other users' groups temporarily.")] public static readonly string settempgroup; [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; [Description("User can download updates to plugins that are currently running.")] public static readonly string updateplugins;
#region Rest Endpoint Permissions
[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;
#endregion
static Permissions() static Permissions()
{ {
foreach (var field in typeof (Permissions).GetFields()) foreach (var field in typeof (Permissions).GetFields())
{ {
field.SetValue(null, field.Name); field.SetValue(null, field.Name);
} }
//Backwards compatability.
restapi = "api";
} }
/// <summary> /// <summary>

View file

@ -38,6 +38,15 @@ namespace Rests
/// <returns>Response object or null to not handle request</returns> /// <returns>Response object or null to not handle request</returns>
public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters); public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters);
/// <summary>
/// Secure Rest command delegate including a token.
/// </summary>
/// <param name="parameters">Parameters in the url</param>
/// <param name="verbs">{x} in urltemplate</param>
/// <param name="tokenData">The data of stored for the provided token.</param>
/// <returns>Response object or null to not handle request</returns>
public delegate object SecureRestCommandD(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData);
public class Rest : IDisposable public class Rest : IDisposable
{ {
private readonly List<RestCommand> commands = new List<RestCommand>(); private readonly List<RestCommand> commands = new List<RestCommand>();
@ -182,7 +191,41 @@ namespace Rests
protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) 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);
if (parms.Count > 0)
{
bool isFirstParam = true;
foreach (IParameter paramImpl in parms)
{
Parameter param = (paramImpl as Parameter);
if (param == null || (!includeToken && param.Name.Equals("token", StringComparison.InvariantCultureIgnoreCase)))
continue;
if (!isFirstParam)
requestBuilder.Append('&');
else
{
requestBuilder.Append('?');
isFirstParam = false;
}
requestBuilder.Append(param.Name);
requestBuilder.Append('=');
requestBuilder.Append(param.Value);
}
}
return requestBuilder.ToString();
} }
#region Dispose #region Dispose

View file

@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using HttpServer;
namespace Rests namespace Rests
{ {
@ -26,8 +27,10 @@ namespace Rests
public string UriTemplate { get; protected set; } public string UriTemplate { get; protected set; }
public string UriVerbMatch { get; protected set; } public string UriVerbMatch { get; protected set; }
public string[] UriVerbs { get; protected set; } public string[] UriVerbs { get; protected set; }
public RestCommandD Callback { get; protected set; } public virtual bool RequiresToken { get { return false; } }
public bool RequiresToken { get; set; } public bool DoLog { get; set; }
private RestCommandD callback;
/// <summary> /// <summary>
/// ///
@ -42,8 +45,8 @@ namespace Rests
UriVerbMatch = string.Format("^{0}$", string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}"))); UriVerbMatch = string.Format("^{0}$", string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}")));
var matches = Regex.Matches(uritemplate, "\\{([^\\{\\}]*)\\}"); var matches = Regex.Matches(uritemplate, "\\{([^\\{\\}]*)\\}");
UriVerbs = (from Match match in matches select match.Groups[1].Value).ToArray(); UriVerbs = (from Match match in matches select match.Groups[1].Value).ToArray();
Callback = callback; this.callback = callback;
RequiresToken = true; DoLog = true;
} }
/// <summary> /// <summary>
@ -60,5 +63,43 @@ namespace Rests
{ {
get { return UriVerbs.Length > 0; } 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);
}
} }
} }

View file

@ -38,78 +38,127 @@ namespace TShockAPI
public void RegisterRestfulCommands() public void RegisterRestfulCommands()
{ {
// Server Commands // Server Commands
Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); Rest.Register(new SecureRestCommand("/v2/server/broadcast", ServerBroadcast));
Rest.Register(new RestCommand("/v2/server/off", ServerOff)); Rest.Register(new SecureRestCommand("/v2/server/off", ServerOff, Permissions.restmaintenance));
Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); Rest.Register(new SecureRestCommand("/v2/server/restart", ServerRestart, Permissions.restmaintenance));
Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); Rest.Register(new SecureRestCommand("/v2/server/reload", ServerReload, Permissions.restcfg));
Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); Rest.Register(new SecureRestCommand("/v2/server/rawcmd", ServerCommand, Permissions.restrawcommand));
Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); Rest.Register(new SecureRestCommand("/v3/server/rawcmd", ServerCommandV3, Permissions.restrawcommand));
Rest.Register(new SecureRestCommand("/tokentest", ServerTokenTest));
if (TShock.Config.EnableTokenEndpointAuthentication)
{
Rest.Register(new SecureRestCommand("/v2/server/status", ServerStatusV2));
Rest.Register(new SecureRestCommand("/status", ServerStatus));
}
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)));
}
// User Commands // User Commands
Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); Rest.Register(new SecureRestCommand("/v2/users/activelist", UserActiveListV2, Permissions.restviewusers));
Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); Rest.Register(new SecureRestCommand("/v2/users/create", UserCreateV2, Permissions.restmanageusers) { DoLog = false });
Rest.Register(new RestCommand("/v2/users/list", UserListV2)); Rest.Register(new SecureRestCommand("/v2/users/list", UserListV2, Permissions.restviewusers));
Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); Rest.Register(new SecureRestCommand("/v2/users/read", UserInfoV2, Permissions.restviewusers));
Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); Rest.Register(new SecureRestCommand("/v2/users/destroy", UserDestroyV2, Permissions.restmanageusers));
Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); Rest.Register(new SecureRestCommand("/v2/users/update", UserUpdateV2, Permissions.restmanageusers) { DoLog = false });
// Ban Commands // Ban Commands
Rest.Register(new RestCommand("/bans/create", BanCreate)); Rest.Register(new SecureRestCommand("/bans/create", BanCreate, Permissions.restmanagebans));
Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); Rest.Register(new SecureRestCommand("/v2/bans/list", BanListV2, Permissions.restviewbans));
Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); Rest.Register(new SecureRestCommand("/v2/bans/read", BanInfoV2, Permissions.restviewbans));
Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); Rest.Register(new SecureRestCommand("/v2/bans/destroy", BanDestroyV2, Permissions.restmanagebans));
// World Commands // World Commands
Rest.Register(new RestCommand("/world/read", WorldRead)); Rest.Register(new SecureRestCommand("/world/read", WorldRead));
Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); Rest.Register(new SecureRestCommand("/world/meteor", WorldMeteor, Permissions.restcauseevents));
Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); Rest.Register(new SecureRestCommand("/world/bloodmoon/{bool}", WorldBloodmoon, Permissions.restcauseevents));
Rest.Register(new RestCommand("/v2/world/save", WorldSave)); Rest.Register(new SecureRestCommand("/v2/world/save", WorldSave, Permissions.restcfg));
Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); Rest.Register(new SecureRestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings, Permissions.restcfg));
Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); Rest.Register(new SecureRestCommand("/v2/world/butcher", WorldButcher, Permissions.restbutcher));
// Player Commands // Player Commands
Rest.Register(new RestCommand("/lists/players", PlayerList)); Rest.Register(new SecureRestCommand("/lists/players", PlayerList));
Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); Rest.Register(new SecureRestCommand("/v2/players/list", PlayerListV2));
Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); Rest.Register(new SecureRestCommand("/v2/players/read", PlayerReadV2, Permissions.restuserinfo));
Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); Rest.Register(new SecureRestCommand("/v2/players/kick", PlayerKickV2, Permissions.restkick));
Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); Rest.Register(new SecureRestCommand("/v2/players/ban", PlayerBanV2, Permissions.restban, Permissions.restmanagebans));
Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); Rest.Register(new SecureRestCommand("/v2/players/kill", PlayerKill, Permissions.restkill));
Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); Rest.Register(new SecureRestCommand("/v2/players/mute", PlayerMute, Permissions.restmute));
Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); Rest.Register(new SecureRestCommand("/v2/players/unmute", PlayerUnMute, Permissions.restmute));
// Group Commands // Group Commands
Rest.Register(new RestCommand("/v2/groups/list", GroupList)); Rest.Register(new SecureRestCommand("/v2/groups/list", GroupList, Permissions.restviewgroups));
Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); Rest.Register(new SecureRestCommand("/v2/groups/read", GroupInfo, Permissions.restviewgroups));
Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); Rest.Register(new SecureRestCommand("/v2/groups/destroy", GroupDestroy, Permissions.restmanagegroups));
Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); Rest.Register(new SecureRestCommand("/v2/groups/create", GroupCreate, Permissions.restmanagegroups));
Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); Rest.Register(new SecureRestCommand("/v2/groups/update", GroupUpdate, Permissions.restmanagegroups));
} }
#region RestServerMethods #region RestServerMethods
private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) private object ServerCommand(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
if (string.IsNullOrWhiteSpace(parameters["cmd"])) if (string.IsNullOrWhiteSpace(parameters["cmd"]))
return RestMissingParam("cmd"); return RestMissingParam("cmd");
TSRestPlayer tr = new TSRestPlayer(); TSRestPlayer tr = new TSRestPlayer(tokenData.Username, tokenData.UserGroup);
Commands.HandleCommand(tr, parameters["cmd"]); Commands.HandleCommand(tr, parameters["cmd"]);
return RestResponse(string.Join("\n", tr.GetCommandOutput())); return RestResponse(string.Join("\n", tr.GetCommandOutput()));
} }
private object ServerOff(RestVerbs verbs, IParameterCollection parameters) private object ServerCommandV3(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{
if (string.IsNullOrWhiteSpace(parameters["cmd"]))
return RestMissingParam("cmd");
TSRestPlayer tr = new TSRestPlayer(tokenData.Username, tokenData.UserGroup);
Commands.HandleCommand(tr, parameters["cmd"]);
return new Dictionary<string,object>
{
{"status", "200"},
{"response", tr.GetCommandOutput()}
};
}
private object ServerOff(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
if (!GetBool(parameters["confirm"], false)) if (!GetBool(parameters["confirm"], false))
return RestInvalidParam("confirm"); return RestInvalidParam("confirm");
// Inform players the server is shutting down // Inform players the server is shutting down
var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; var reason = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"];
TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), msg); TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), reason);
return RestResponse("The server is shutting down"); return RestResponse("The server is shutting down");
} }
private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) 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 shutting down" : 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)
{
try {
TShock.Utils.Reload(new TSRestPlayer(tokenData.Username, tokenData.UserGroup));
} catch (Exception ex) {
return RestError("Exception was thrown during the reload: " + ex);
}
return RestResponse("Configuration, permissions, and regions reload complete. Some changes may require a server restart.");
}
private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var msg = parameters["msg"]; var msg = parameters["msg"];
if (string.IsNullOrWhiteSpace(msg)) if (string.IsNullOrWhiteSpace(msg))
@ -118,11 +167,8 @@ namespace TShockAPI
return RestResponse("The message was broadcasted successfully"); return RestResponse("The message was broadcasted successfully");
} }
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(); var activeplayers = Main.player.Where(p => null != p && p.active).ToList();
return new RestObject() return new RestObject()
{ {
@ -133,18 +179,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() var ret = new RestObject()
{ {
{"name", TShock.Config.ServerName}, {"name", TShock.Config.ServerName},
{"port", TShock.Config.ServerPort}, {"port", TShock.Config.ServerPort},
{"playercount", Main.player.Where(p => null != p && p.active).Count()}, {"playercount", Main.player.Where(p => null != p && p.active).Count()},
{"maxplayers", TShock.Config.MaxSlots}, {"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)) if (GetBool(parameters["players"], false))
@ -174,13 +219,14 @@ namespace TShockAPI
rules.Add("PvPMode", TShock.Config.PvPMode); rules.Add("PvPMode", TShock.Config.PvPMode);
rules.Add("SpawnProtection", TShock.Config.SpawnProtection); 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); ret.Add("rules", rules);
} }
return ret; return ret;
} }
private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
return RestResponse("Token is valid and was passed through correctly"); return RestResponse("Token is valid and was passed through correctly");
} }
@ -189,12 +235,12 @@ namespace TShockAPI
#region RestUserMethods #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)) } }; 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<string,object>(){ return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary<string,object>(){
{"name", p.Name}, {"name", p.Name},
@ -204,7 +250,7 @@ namespace TShockAPI
}) } }; }) } };
} }
private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var username = parameters["user"]; var username = parameters["user"];
if (string.IsNullOrWhiteSpace(username)) if (string.IsNullOrWhiteSpace(username))
@ -232,7 +278,7 @@ namespace TShockAPI
return RestResponse("User was successfully created"); 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); var ret = UserFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -274,7 +320,7 @@ namespace TShockAPI
return response; return response;
} }
private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var ret = UserFind(parameters); var ret = UserFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -292,7 +338,7 @@ namespace TShockAPI
return RestResponse("User deleted successfully"); 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); var ret = UserFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -306,7 +352,7 @@ namespace TShockAPI
#region RestBanMethods #region RestBanMethods
private object BanCreate(RestVerbs verbs, IParameterCollection parameters) private object BanCreate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var ip = parameters["ip"]; var ip = parameters["ip"];
var name = parameters["name"]; var name = parameters["name"];
@ -325,7 +371,7 @@ namespace TShockAPI
return RestResponse("Ban created successfully"); 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); var ret = BanFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -357,7 +403,7 @@ namespace TShockAPI
return RestResponse("Ban deleted successfully"); 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); var ret = BanFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -371,7 +417,7 @@ namespace TShockAPI
}; };
} }
private object BanListV2(RestVerbs verbs, IParameterCollection parameters) private object BanListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var banList = new ArrayList(); var banList = new ArrayList();
foreach (var ban in TShock.Bans.GetBans()) foreach (var ban in TShock.Bans.GetBans())
@ -393,7 +439,7 @@ namespace TShockAPI
#region RestWorldMethods #region RestWorldMethods
private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
bool autoSave; bool autoSave;
if (!bool.TryParse(verbs["bool"], out autoSave)) if (!bool.TryParse(verbs["bool"], out autoSave))
@ -403,14 +449,14 @@ namespace TShockAPI
return RestResponse("AutoSave has been set to " + autoSave); 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(); SaveManager.Instance.SaveWorld();
return RestResponse("World saved"); return RestResponse("World saved");
} }
private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) private object WorldButcher(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
bool killFriendly; bool killFriendly;
if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) if (!bool.TryParse(parameters["killfriendly"], out killFriendly))
@ -432,7 +478,7 @@ namespace TShockAPI
return RestResponse(killcount + " NPCs have been killed"); 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() return new RestObject()
{ {
@ -445,7 +491,7 @@ namespace TShockAPI
}; };
} }
private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
if (null == WorldGen.genRand) if (null == WorldGen.genRand)
WorldGen.genRand = new Random(); WorldGen.genRand = new Random();
@ -453,7 +499,7 @@ namespace TShockAPI
return RestResponse("Meteor has been spawned"); 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; bool bloodmoon;
if (!bool.TryParse(verbs["bool"], out bloodmoon)) if (!bool.TryParse(verbs["bool"], out bloodmoon))
@ -467,23 +513,23 @@ namespace TShockAPI
#region RestPlayerMethods #region RestPlayerMethods
private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
return PlayerSetMute(parameters, false); 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); 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(); var activeplayers = Main.player.Where(p => null != p && p.active).ToList();
return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; 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(); var playerList = new ArrayList();
foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p))
@ -495,7 +541,7 @@ namespace TShockAPI
return new RestObject() { { "players", playerList } }; 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); var ret = PlayerFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -515,7 +561,7 @@ namespace TShockAPI
}; };
} }
private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var ret = PlayerFind(parameters); var ret = PlayerFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -526,7 +572,7 @@ namespace TShockAPI
return RestResponse("Player " + player.Name + " was kicked"); 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); var ret = PlayerFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -539,7 +585,7 @@ namespace TShockAPI
return RestResponse("Player " + player.Name + " was banned"); 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); var ret = PlayerFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -556,7 +602,7 @@ namespace TShockAPI
#region RestGroupMethods #region RestGroupMethods
private object GroupList(RestVerbs verbs, IParameterCollection parameters) private object GroupList(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var groups = new ArrayList(); var groups = new ArrayList();
foreach (Group group in TShock.Groups) foreach (Group group in TShock.Groups)
@ -566,7 +612,7 @@ namespace TShockAPI
return new RestObject() { { "groups", groups } }; 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); var ret = GroupFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -583,7 +629,7 @@ namespace TShockAPI
}; };
} }
private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var ret = GroupFind(parameters); var ret = GroupFind(parameters);
if (ret is RestObject) if (ret is RestObject)
@ -602,7 +648,7 @@ namespace TShockAPI
return RestResponse("Group '" + group.Name + "' deleted successfully"); 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"]; var name = parameters["group"];
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
@ -619,7 +665,7 @@ namespace TShockAPI
return RestResponse("Group '" + name + "' created successfully"); 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); var ret = GroupFind(parameters);
if (ret is RestObject) if (ret is RestObject)

View file

@ -19,37 +19,41 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using HttpServer; using HttpServer;
using TShockAPI;
using TShockAPI.DB;
namespace Rests namespace Rests
{ {
/// <summary>
///
/// </summary>
/// <param name="username">Username to verify</param>
/// <param name="password">Password to verify</param>
/// <returns>Returning a restobject with a null error means a successful verification.</returns>
public delegate RestObject VerifyD(string username, string password);
public class SecureRest : Rest public class SecureRest : Rest
{ {
public Dictionary<string, object> Tokens { get; protected set; } public struct TokenData
public event VerifyD Verify; {
public static readonly TokenData None = default(TokenData);
public string Username { get; set; }
public Group UserGroup { get; set; }
}
public Dictionary<string,TokenData> Tokens { get; protected set; }
public SecureRest(IPAddress ip, int port) public SecureRest(IPAddress ip, int port)
: base(ip, port) : base(ip, port)
{ {
Tokens = new Dictionary<string, object>(); Tokens = new Dictionary<string, TokenData>();
Register(new RestCommand("/token/create/{username}/{password}", NewToken) {RequiresToken = false});
Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { RequiresToken = false }); Register(new RestCommand("/token/create/{username}/{password}", NewToken) { DoLog = false });
Register(new RestCommand("/token/destroy/{token}", DestroyToken) {RequiresToken = true}); Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { DoLog = false });
foreach (KeyValuePair<string, string> t in TShockAPI.TShock.RESTStartupTokens) Register(new SecureRestCommand("/token/destroy/{token}", DestroyToken));
foreach (KeyValuePair<string, TokenData> t in TShockAPI.TShock.RESTStartupTokens)
{ {
Tokens.Add(t.Key, t.Value); Tokens.Add(t.Key, t.Value);
} }
} }
private object DestroyToken(RestVerbs verbs, IParameterCollection parameters) private object DestroyToken(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData)
{ {
var token = verbs["token"]; var token = verbs["token"];
try try
@ -70,29 +74,7 @@ namespace Rests
var user = parameters["username"]; var user = parameters["username"];
var pass = verbs["password"]; var pass = verbs["password"];
RestObject obj = null; return this.NewTokenInternal(user, pass);
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;
} }
private object NewToken(RestVerbs verbs, IParameterCollection parameters) private object NewToken(RestVerbs verbs, IParameterCollection parameters)
@ -100,45 +82,56 @@ namespace Rests
var user = verbs["username"]; var user = verbs["username"];
var pass = verbs["password"]; var pass = verbs["password"];
RestObject obj = null; RestObject response = this.NewTokenInternal(user, pass);
if (Verify != null) response["deprecated"] = "This endpoint is depracted and will be removed in the future.";
obj = Verify(user, pass); return response;
}
if (obj == null) private RestObject NewTokenInternal(string username, string password)
obj = new RestObject("401") {
{Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; 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 (obj.Error != null) if (!TShock.Utils.HashPassword(password).Equals(userAccount.Password, StringComparison.InvariantCultureIgnoreCase))
return obj; return new RestObject("401")
{ Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." };
string hash; Group userGroup = TShock.Utils.GetGroup(userAccount.Group);
if (!userGroup.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. (restapi)" };
string tokenHash;
var rand = new Random(); var rand = new Random();
var randbytes = new byte[32]; var randbytes = new byte[32];
do do
{ {
rand.NextBytes(randbytes); rand.NextBytes(randbytes);
hash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); tokenHash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2"));
} while (Tokens.ContainsKey(hash)); } while (Tokens.ContainsKey(tokenHash));
Tokens.Add(hash, user); Tokens.Add(tokenHash, new TokenData { Username = userAccount.Name, UserGroup = userGroup });
obj["token"] = hash; RestObject response = new RestObject("200") { Response = "Successful login" };
obj["deprecated"] = "This method will be removed from TShock in 3.6."; response["token"] = tokenHash;
return obj; return response;
} }
protected override object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) protected override object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms)
{ {
if (cmd.RequiresToken) if (!cmd.RequiresToken)
{ return base.ExecuteCommand(cmd, verbs, parms);
var strtoken = parms["token"];
if (strtoken == null) var token = parms["token"];
if (token == null)
return new Dictionary<string, string> return new Dictionary<string, string>
{{"status", "401"}, {"error", "Not authorized. The specified API endpoint requires a token."}}; {{"status", "401"}, {"error", "Not authorized. The specified API endpoint requires a token."}};
object token; SecureRestCommand secureCmd = (SecureRestCommand)cmd;
if (!Tokens.TryGetValue(strtoken, out token)) TokenData tokenData;
if (!Tokens.TryGetValue(token, out tokenData))
return new Dictionary<string, string> return new Dictionary<string, string>
{ {
{"status", "403"}, {"status", "403"},
@ -147,8 +140,26 @@ namespace Rests
"Not authorized. The specified API endpoint requires a token, but the provided token was not valid." "Not authorized. The specified API endpoint requires a token, but the provided token was not valid."
} }
}; };
if (secureCmd.Permissions.Length > 0 && secureCmd.Permissions.All(perm => !tokenData.UserGroup.HasPermission(perm)))
{
return new Dictionary<string, string>
{
{"status", "403"},
{
"error",
string.Format("Not authorized. User \"{0}\" has no access to use the specified API endpoint.", tokenData.Username)
} }
return base.ExecuteCommand(cmd, verbs, parms); };
}
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;
} }
} }
} }

View file

@ -759,13 +759,13 @@ namespace TShockAPI
} }
} }
public class TSRestPlayer : TSServerPlayer public class TSRestPlayer : TSPlayer
{ {
internal List<string> CommandReturn = new List<string>(); internal List<string> CommandOutput = new List<string>();
public TSRestPlayer() public TSRestPlayer(string playerName, Group playerGroup): base(playerName)
{ {
Group = new SuperAdminGroup(); Group = playerGroup;
AwaitingResponse = new Dictionary<string, Action<object>>(); AwaitingResponse = new Dictionary<string, Action<object>>();
} }
@ -781,7 +781,7 @@ namespace TShockAPI
public override void SendMessage(string msg, byte red, byte green, byte blue) 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) public override void SendInfoMessage(string msg)
@ -806,7 +806,7 @@ namespace TShockAPI
public List<string> GetCommandOutput() public List<string> GetCommandOutput()
{ {
return CommandReturn; return this.CommandOutput;
} }
} }

View file

@ -72,7 +72,7 @@ namespace TShockAPI
/// <summary> /// <summary>
/// Used for implementing REST Tokens prior to the REST system starting up. /// Used for implementing REST Tokens prior to the REST system starting up.
/// </summary> /// </summary>
public static Dictionary<string, string> RESTStartupTokens = new Dictionary<string, string>(); public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
/// <summary> /// <summary>
/// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. /// 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); RememberedPos = new RememberedPosManager(DB);
InventoryDB = new InventoryManager(DB); InventoryDB = new InventoryManager(DB);
RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort);
RestApi.Verify += RestApi_Verify;
RestApi.Port = Config.RestApiPort; RestApi.Port = Config.RestApiPort;
RestManager = new RestManager(RestApi); RestManager = new RestManager(RestApi);
RestManager.RegisterRestfulCommands(); RestManager.RegisterRestfulCommands();
@ -294,33 +293,6 @@ namespace TShockAPI
// ReSharper restore LocalizableElement // 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) protected override void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
@ -507,7 +479,7 @@ namespace TShockAPI
break; break;
case "-rest-token": case "-rest-token":
string token = Convert.ToString(parms[++i]); string token = Convert.ToString(parms[++i]);
RESTStartupTokens.Add(token, "null"); RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroup = new SuperAdminGroup() });
Console.WriteLine("Startup parameter overrode REST token."); Console.WriteLine("Startup parameter overrode REST token.");
break; break;
case "-rest-enabled": case "-rest-enabled":

View file

@ -562,6 +562,36 @@ namespace TShockAPI
Netplay.disconnect = true; Netplay.disconnect = true;
} }
/// <summary>
/// Stops the server after kicking all players with a reason message, and optionally saving the world then attempts to
/// restart it.
/// </summary>
/// <param name="save">bool perform a world save before stop (default: true)</param>
/// <param name="reason">string reason (default: "Server shutting down!")</param>
public void RestartServer(bool save = true, string reason = "Server shutting down!")
{
if (TShock.Config.ServerSideInventory)
foreach (TSPlayer player in TShock.Players)
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
TShock.InventoryDB.InsertPlayerData(player);
StopServer(true, reason);
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Environment.Exit(0);
}
/// <summary>
/// Reloads all configuration settings, groups, regions and raises the reload event.
/// </summary>
public void Reload(TSPlayer player)
{
FileTools.SetupConfig();
TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
TShock.Groups.LoadPermisions();
TShock.Regions.ReloadAllRegions();
Hooks.GeneralHooks.OnReloadEvent(player);
}
#if COMPAT_SIGS #if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")] [Obsolete("This method is for signature compatibility for external code only")]
public void ForceKick(TSPlayer player, string reason) public void ForceKick(TSPlayer player, string reason)