Merge pull request #505 from CoderCow/patch-1
REST Security and General Improvements
This commit is contained in:
commit
f82bff1b17
12 changed files with 592 additions and 256 deletions
|
|
@ -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<Command>(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<string,int> restUsersTokens = new Dictionary<string,int>();
|
||||
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<string> restUsers = new List<string>(
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a configuration file from a given path
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,16 @@ namespace Rests
|
|||
/// <param name="parameters">Parameters in the url</param>
|
||||
/// <param name="verbs">{x} in urltemplate</param>
|
||||
/// <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 token data.
|
||||
/// </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
|
||||
{
|
||||
|
|
@ -165,24 +174,47 @@ namespace Rests
|
|||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
return new RestObject("500")
|
||||
{
|
||||
{"status", "500"},
|
||||
{"error", "Internal server error."},
|
||||
{"errormsg", exception.Message},
|
||||
{"stacktrace", exception.StackTrace},
|
||||
};
|
||||
}
|
||||
return new Dictionary<string, string>
|
||||
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
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/
|
||||
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<string,object>(){
|
||||
{"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)
|
||||
|
|
|
|||
93
TShockAPI/Rest/RestPermissions.cs
Normal file
93
TShockAPI/Rest/RestPermissions.cs
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,38 +18,66 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
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
|
||||
{
|
||||
/// <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 Dictionary<string, object> 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<string,TokenData> Tokens { get; protected set; }
|
||||
|
||||
public SecureRest(IPAddress ip, int port)
|
||||
: base(ip, port)
|
||||
{
|
||||
Tokens = new Dictionary<string, object>();
|
||||
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<string, string> t in TShockAPI.TShock.RESTStartupTokens)
|
||||
Tokens = new Dictionary<string, TokenData>();
|
||||
|
||||
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<string, TokenData> 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<string, string>
|
||||
{{"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<string, string>
|
||||
{{"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<string, string>
|
||||
{{"status", "401"}, {"error", "Not authorized. The specified API endpoint requires a token."}};
|
||||
|
||||
object token;
|
||||
if (!Tokens.TryGetValue(strtoken, out token))
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{"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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>>();
|
||||
}
|
||||
|
||||
|
|
@ -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<string> GetCommandOutput()
|
||||
{
|
||||
return CommandReturn;
|
||||
return this.CommandOutput;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ namespace TShockAPI
|
|||
/// <summary>
|
||||
/// Used for implementing REST Tokens prior to the REST system starting up.
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> RESTStartupTokens = new Dictionary<string, string>();
|
||||
public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
|
||||
|
||||
/// <summary>
|
||||
/// 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":
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@
|
|||
<Compile Include="PluginUpdater\PluginUpdaterThread.cs" />
|
||||
<Compile Include="PluginUpdater\PluginVersionCheck.cs" />
|
||||
<Compile Include="PluginUpdater\VersionInfo.cs" />
|
||||
<Compile Include="Rest\RestPermissions.cs" />
|
||||
<Compile Include="SaveManager.cs" />
|
||||
<Compile Include="DB\BanManager.cs" />
|
||||
<Compile Include="DB\InventoryManager.cs" />
|
||||
|
|
@ -191,7 +192,7 @@
|
|||
</PropertyGroup>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
|
||||
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
|
|
|||
|
|
@ -560,6 +560,36 @@ namespace TShockAPI
|
|||
|
||||
// Disconnect after kick as that signifies server is exiting and could cause a race
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue