From 1fe7284f7cfe502289bf33a44f4657fc743d9802 Mon Sep 17 00:00:00 2001 From: high Date: Mon, 5 Sep 2011 02:44:46 -0400 Subject: [PATCH] Added SecureRest which implements token requirement for commands. Still must implement a Verify function and probably change SecureRest around a bit. --- TShockAPI/Rest.cs | 74 +++++++++++++++++++++++++++++--------- TShockAPI/SecureRest.cs | 57 +++++++++++++++++++++++++++++ TShockAPI/TShock.cs | 2 +- TShockAPI/TShockAPI.csproj | 3 +- TShockAPI/Tools.cs | 24 +++++++++---- 5 files changed, 134 insertions(+), 26 deletions(-) create mode 100644 TShockAPI/SecureRest.cs diff --git a/TShockAPI/Rest.cs b/TShockAPI/Rest.cs index 21b21924..7aa6b8a1 100644 --- a/TShockAPI/Rest.cs +++ b/TShockAPI/Rest.cs @@ -15,9 +15,9 @@ namespace TShockAPI /// Rest command delegate /// /// Parameters in the url - /// Http request + /// {x} in urltemplate /// Response object or null to not handle request - public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters, RequestEventArgs request); + public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters); public class Rest : IDisposable { readonly List commands = new List(); @@ -62,7 +62,7 @@ namespace TShockAPI protected virtual void OnRequest(object sender, RequestEventArgs e) { - var obj = Process(sender, e); + var obj = ProcessRequest(sender, e); if (obj == null) throw new NullReferenceException("obj"); var str = JsonConvert.SerializeObject(obj, Formatting.Indented); @@ -72,25 +72,40 @@ namespace TShockAPI return; } - protected virtual object Process(object sender, RequestEventArgs e) + protected virtual object ProcessRequest(object sender, RequestEventArgs e) { foreach (var com in commands) { - var matches = Regex.Matches(e.Request.Uri.AbsolutePath, com.UriMatch); - if (matches.Count == com.UriNames.Length) + var verbs = new RestVerbs(); + if (com.HasVerbs) { - var verbs = new RestVerbs(); - for (int i = 0; i < matches.Count; i++) - verbs.Add(com.UriNames[i], matches[i].Groups[1].Value); + var match = Regex.Match(e.Request.Uri.AbsolutePath, com.UriVerbMatch); + if (!match.Success) + continue; + if ((match.Groups.Count - 1) != com.UriVerbs.Length) + continue; - var obj = com.Callback(verbs, e.Request.Parameters, e); - if (obj != null) - return obj; + for (int i = 0; i < com.UriVerbs.Length; i++) + verbs.Add(com.UriVerbs[i], match.Groups[i + 1].Value); } + else if (com.UriTemplate.ToLower() != e.Request.Uri.AbsolutePath.ToLower()) + { + continue; + } + + var obj = ExecuteCommand(com, verbs, e.Request.Parameters); + if (obj != null) + return obj; + } return new Dictionary { { "Error", "Invalid request" } }; } + protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) + { + return cmd.Callback(verbs, parms); + } + #region Dispose public void Dispose() { @@ -123,18 +138,43 @@ namespace TShockAPI public class RestCommand { + public string Name { get; protected set; } public string UriTemplate { get; protected set; } - public string UriMatch { get; protected set; } - public string[] UriNames { get; protected set; } + public string UriVerbMatch { get; protected set; } + public string[] UriVerbs { get; protected set; } public RestCommandD Callback { get; protected set; } + public bool RequiesToken { get; set; } - public RestCommand(string uritemplate, RestCommandD callback) + /// + /// + /// + /// Used for identification + /// Url template + /// Rest Command callback + public RestCommand(string name, string uritemplate, RestCommandD callback) { + Name = name; UriTemplate = uritemplate; - UriMatch = string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}")); + UriVerbMatch = string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}")); var matches = Regex.Matches(uritemplate, "\\{([^\\{\\}]*)\\}"); - UriNames = (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; + RequiesToken = true; + } + /// + /// + /// + /// Url template + /// Rest Command callback + public RestCommand(string uritemplate, RestCommandD callback) + : this(string.Empty, uritemplate, callback) + { + + } + + public bool HasVerbs + { + get { return UriVerbs.Length > 0; } } } } diff --git a/TShockAPI/SecureRest.cs b/TShockAPI/SecureRest.cs new file mode 100644 index 00000000..85c36ff2 --- /dev/null +++ b/TShockAPI/SecureRest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using HttpServer; + +namespace TShockAPI +{ + public delegate bool VerifyD(string username, string password); + public class SecureRest : Rest + { + public Dictionary Tokens { get; protected set; } + public event VerifyD Verify; + public SecureRest(IPAddress ip, int port) + : base(ip, port) + { + Tokens = new Dictionary(); + Register(new RestCommand("/token/new/{username}/{password}", newtoken) { RequiesToken = false }); + } + object newtoken(RestVerbs verbs, IParameterCollection parameters) + { + var user = verbs["username"]; + var pass = verbs["password"]; + + if (Verify != null && !Verify(user, pass)) + return new Dictionary { { "Error", "Failed to verify username/password" } }; + + string hash = string.Empty; + var rand = new Random(); + var randbytes = new byte[20]; + do + { + rand.NextBytes(randbytes); + hash = Tools.HashPassword(randbytes); + } while (Tokens.ContainsKey(hash)); + + Tokens.Add(hash, new Object()); + + return new Dictionary { { "Token", hash } }; ; + } + protected override object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) + { + if (cmd.RequiesToken) + { + var strtoken = parms["token"]; + if (strtoken == null) + return new Dictionary { { "Error", "Token Missing" } }; + + object token; + if (!Tokens.TryGetValue(strtoken, out token)) + return new Dictionary { { "Error", "Token Invalid" } }; + } + return base.ExecuteCommand(cmd, verbs, parms); + } + } +} diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index a4b0bb0b..555f17b6 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -176,7 +176,7 @@ namespace TShockAPI Regions = new RegionManager(DB); Itembans = new ItemManager(DB); RememberedPos = new RemeberedPosManager(DB); - RestApi = new Rest(IPAddress.Any, 8080); + RestApi = new SecureRest(IPAddress.Any, 8080); RestManager = new RestManager(RestApi); RestManager.RegisterRestfulCommands(); if (Config.EnableGeoIP) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index adadb917..2fac0775 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -126,6 +126,7 @@ + @@ -182,7 +183,7 @@ - +