From 9447a8a09f87d6ea2cb62860021c595386668eec Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 20 Feb 2019 22:55:15 +0200 Subject: A lot of work !!! --- .../Tango.Web/Authentication/TokensManager.cs | 6 +- .../Tango.Web/Authentication/WebToken.cs | 151 ++++++++++++++++++++- .../Tango.Web/Authentication/WebTokenResponse.cs | 2 +- .../Tango.Web/Controllers/JsonController.cs | 76 ----------- .../Tango.Web/Controllers/TangoController.cs | 125 +++++++++++++++++ Software/Visual_Studio/Tango.Web/Tango.Web.csproj | 18 ++- Software/Visual_Studio/Tango.Web/TangoWebClient.cs | 82 +++++------ Software/Visual_Studio/Tango.Web/app.config | 6 +- Software/Visual_Studio/Tango.Web/packages.config | 3 +- 9 files changed, 334 insertions(+), 135 deletions(-) delete mode 100644 Software/Visual_Studio/Tango.Web/Controllers/JsonController.cs create mode 100644 Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs (limited to 'Software/Visual_Studio/Tango.Web') diff --git a/Software/Visual_Studio/Tango.Web/Authentication/TokensManager.cs b/Software/Visual_Studio/Tango.Web/Authentication/TokensManager.cs index 890d69d53..5829bfca3 100644 --- a/Software/Visual_Studio/Tango.Web/Authentication/TokensManager.cs +++ b/Software/Visual_Studio/Tango.Web/Authentication/TokensManager.cs @@ -48,8 +48,8 @@ namespace Tango.Web.Authentication Value = tokenObject, WebToken = new WebToken() { - AccessToken = token, - Expiration = DateTime.UtcNow.Add(ExpirationTime) + //AccessToken = token, + //Expiration = DateTime.UtcNow.Add(ExpirationTime) }, }; @@ -73,7 +73,7 @@ namespace Tango.Web.Authentication if (DateTime.UtcNow > _tokens[token].WebToken.Expiration) { _tokens.Remove(token); - throw new SessionExpiredException("Session Expired."); + throw new TokenExpiredException("Session Expired."); } return _tokens[token].Value; diff --git a/Software/Visual_Studio/Tango.Web/Authentication/WebToken.cs b/Software/Visual_Studio/Tango.Web/Authentication/WebToken.cs index 71ec6eb0b..14fc49942 100644 --- a/Software/Visual_Studio/Tango.Web/Authentication/WebToken.cs +++ b/Software/Visual_Studio/Tango.Web/Authentication/WebToken.cs @@ -1,6 +1,12 @@ -using System; +using JWT; +using JWT.Algorithms; +using JWT.Builder; +using JWT.Serializers; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -8,7 +14,146 @@ namespace Tango.Web.Authentication { public class WebToken { - public DateTime Expiration { get; set; } - public String AccessToken { get; set; } + public DateTime Issued { get; protected set; } + public DateTime? Expiration { get; protected set; } + public String AccessToken { get; protected set; } + + public WebToken() + { + + } + + public static WebToken CreateNew(String secret, DateTime? expiration = null) + { + DateTime issued = DateTime.UtcNow; + + var builder = new JwtBuilder() + .WithAlgorithm(new HMACSHA256Algorithm()) + .WithSecret(secret) + .IssuedAt(issued); + + if (expiration != null) + { + builder = builder.ExpirationTime(expiration.Value); + } + + builder = builder.AddClaim("object", null); + + return new WebToken() + { + AccessToken = builder.Build(), + Expiration = expiration, + Issued = issued, + }; + } + + public static void Validate(String secret, String token) + { + var json = new JwtBuilder() + .WithSecret(secret) + .MustVerifySignature() + .Decode(token); + } + + public void Validate(String secret) + { + var json = new JwtBuilder() + .WithSecret(secret) + .MustVerifySignature() + .Decode(AccessToken); + } + + public static WebToken FromToken(String token) + { + WebToken webToken = new WebToken(); + + var payload = new JwtBuilder() + .WithValidator(null) + .Decode>(token); + + webToken.AccessToken = token; + + if (payload.ContainsKey("exp")) + { + long exp = long.Parse(payload["exp"].ToString()); + webToken.Expiration = ConvertEpochToDateTime(exp); + } + + if (payload.ContainsKey("iat")) + { + long iat = long.Parse(payload["iat"].ToString()); + webToken.Issued = ConvertEpochToDateTime(iat); + } + + return webToken; + } + + protected static DateTime ConvertEpochToDateTime(long seconds) + { + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return epoch.AddSeconds(seconds); + } + } + + public class WebToken : WebToken where T : class + { + public T Object { get; protected set; } + + private WebToken() + { + + } + + public static WebToken CreateNew(String secret, T obj = null, DateTime? expiration = null) + { + DateTime issued = DateTime.UtcNow; + + var builder = new JwtBuilder() + .WithAlgorithm(new HMACSHA256Algorithm()) + .WithSecret(secret) + .IssuedAt(issued); + + if (expiration != null) + { + builder = builder.ExpirationTime(expiration.Value); + } + + builder = builder.AddClaim("object", obj); + + return new WebToken() + { + AccessToken = builder.Build(), + Expiration = expiration, + Issued = issued, + Object = obj, + }; + } + + public static new WebToken FromToken(String token) + { + WebToken webToken = new WebToken(); + + var payload = new JwtBuilder() + .WithValidator(null) + .Decode>(token); + + webToken.AccessToken = token; + + if (payload.ContainsKey("exp")) + { + long exp = long.Parse(payload["exp"].ToString()); + webToken.Expiration = ConvertEpochToDateTime(exp); + } + + if (payload.ContainsKey("iat")) + { + long iat = long.Parse(payload["iat"].ToString()); + webToken.Issued = ConvertEpochToDateTime(iat); + } + + webToken.Object = JsonConvert.DeserializeObject(payload["object"].ToString()); + + return webToken; + } } } diff --git a/Software/Visual_Studio/Tango.Web/Authentication/WebTokenResponse.cs b/Software/Visual_Studio/Tango.Web/Authentication/WebTokenResponse.cs index 17ac6636f..190a47cc2 100644 --- a/Software/Visual_Studio/Tango.Web/Authentication/WebTokenResponse.cs +++ b/Software/Visual_Studio/Tango.Web/Authentication/WebTokenResponse.cs @@ -9,6 +9,6 @@ namespace Tango.Web.Authentication { public class WebTokenResponse : WebResponseMessage { - public WebToken WebToken { get; set; } + public String AccessToken { get; set; } } } diff --git a/Software/Visual_Studio/Tango.Web/Controllers/JsonController.cs b/Software/Visual_Studio/Tango.Web/Controllers/JsonController.cs deleted file mode 100644 index 1fae9cccc..000000000 --- a/Software/Visual_Studio/Tango.Web/Controllers/JsonController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Security.Authentication; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http; -using System.Web.Http.Controllers; -using Tango.Logging; - -namespace Tango.Web.Controllers -{ - public class JsonController : ApiController - { - protected LogManager LogManager { get; private set; } - - public JsonController() - { - LogManager = LogManager.Default; - } - - public override async Task ExecuteAsync(HttpControllerContext context, CancellationToken cancellationToken) - { - string controllerName = String.Empty; - string actionName = String.Empty; - - try - { - var routeData = HttpContext.Current.Request.RequestContext.RouteData; - actionName = routeData.Values["action"].ToString(); - controllerName = routeData.Values["controller"].ToString(); - } - catch { } - - try - { - String request = String.Empty; - - try - { - request = context.Request.Content.ReadAsStringAsync().Result; - } - catch {} - - LogManager.Log($"Request Received on {controllerName + "/" + actionName}: \n{request}"); - - var result = await base.ExecuteAsync(context, cancellationToken); - return result; - } - catch (Exception ex) - { - LogManager.Log(ex, $"An error occurred while processing the request message on {controllerName + "/" + actionName}."); - - HttpStatusCode code = HttpStatusCode.InternalServerError; - - if (ex is ArgumentException) - { - code = HttpStatusCode.BadRequest; - } - else if (ex is AuthenticationException) - { - code = HttpStatusCode.Unauthorized; - } - -#if DEBUG - throw new HttpResponseException(Request.CreateErrorResponse(code, ex.ToString())); -#else - throw new HttpResponseException(Request.CreateErrorResponse(code, ex.FlattenMessage())); -#endif - } - } - } -} \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs b/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs new file mode 100644 index 000000000..854d1cf96 --- /dev/null +++ b/Software/Visual_Studio/Tango.Web/Controllers/TangoController.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Authentication; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using Tango.Logging; +using Tango.Transport.Web; +using Tango.Web.Authentication; + +namespace Tango.Web.Controllers +{ + public class TangoController : ApiController + { + protected LogManager LogManager { get; private set; } + + public TangoController() + { + LogManager = LogManager.Default; + } + + public override async Task ExecuteAsync(HttpControllerContext context, CancellationToken cancellationToken) + { + string controllerName = String.Empty; + string actionName = String.Empty; + + try + { + var routeData = HttpContext.Current.Request.RequestContext.RouteData; + actionName = routeData.Values["action"].ToString(); + controllerName = routeData.Values["controller"].ToString(); + } + catch { } + + try + { + String request = String.Empty; + + try + { + request = context.Request.Content.ReadAsStringAsync().Result; + } + catch { } + + LogManager.Log($"Request Received on {controllerName + "/" + actionName}: \n{request}"); + + OnRequestArrived(context.Request); + + var result = await base.ExecuteAsync(context, cancellationToken); + return result; + } + catch (Exception ex) + { + LogManager.Log(ex, $"An error occurred while processing the request message on {controllerName + "/" + actionName}."); + + HttpStatusCode code = HttpStatusCode.InternalServerError; + + if (ex is ArgumentException) + { + code = HttpStatusCode.BadRequest; + } + else if (ex is AuthenticationException || ex is TokenExpiredException) + { + code = HttpStatusCode.Unauthorized; + } + + var httpException = new HttpResponseException(Request.CreateErrorResponse(code, ex)); + +#if DEBUG + throw httpException; +#else + //Remove Stack trace + var expandedException = httpException.Response.Content as System.Net.Http.ObjectContent; + + if (expandedException != null) + { + var expandedExceptionValues = expandedException.Value as HttpError; + + if (expandedExceptionValues != null) + { + expandedExceptionValues["StackTrace"] = "StackTrace not provided."; + } + } +#endif + + + throw httpException; + } + } + + protected virtual void OnRequestArrived(HttpRequestMessage request) + { + //Do nothing. + } + } + + public class TangoController : TangoController where T : class + { + public WebToken RequestToken { get; set; } + + protected override void OnRequestArrived(HttpRequestMessage request) + { + base.OnRequestArrived(request); + + var authorizationHeader = request.Headers.Authorization; + + if (authorizationHeader != null && authorizationHeader.Parameter != null) + { + try + { + RequestToken = WebToken.FromToken(authorizationHeader.Parameter); + } + catch (Exception ex) + { + throw new HttpParseException("Could not parse the provided token embedded object.", ex); + } + } + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Web/Tango.Web.csproj b/Software/Visual_Studio/Tango.Web/Tango.Web.csproj index 56711df28..961ec04e8 100644 --- a/Software/Visual_Studio/Tango.Web/Tango.Web.csproj +++ b/Software/Visual_Studio/Tango.Web/Tango.Web.csproj @@ -48,6 +48,9 @@ ..\packages\Google.Protobuf.3.4.1\lib\net45\Google.Protobuf.dll + + ..\packages\JWT.5.0.0\lib\net46\JWT.dll + ..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.6\lib\net45\Microsoft.AI.Agent.Intercept.dll @@ -186,13 +189,14 @@ ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll @@ -257,7 +261,7 @@ - + @@ -268,8 +272,12 @@ - - + + Designer + + + Designer + diff --git a/Software/Visual_Studio/Tango.Web/TangoWebClient.cs b/Software/Visual_Studio/Tango.Web/TangoWebClient.cs index bb943533a..42a9d801c 100644 --- a/Software/Visual_Studio/Tango.Web/TangoWebClient.cs +++ b/Software/Visual_Studio/Tango.Web/TangoWebClient.cs @@ -15,8 +15,8 @@ namespace Tango.Web private bool _disposed; private TLoginRequest _lastLoginRequest; - private DeploymentSlot _environment; - public DeploymentSlot Environment + private DeploymentSlot? _environment; + public DeploymentSlot? Environment { get { return _environment; } set @@ -31,26 +31,40 @@ namespace Tango.Web } } + public String Address { get; set; } public String Controller { get; private set; } + public String Token { get; private set; } public WebToken WebToken { get; private set; } public bool IsAuthenticated { get; private set; } public TangoWebClient(DeploymentSlot environment, String controller) { + _client = new WebTransportClient(); Controller = controller; Environment = environment; - _client = new WebTransportClient(); } - public TangoWebClient(DeploymentSlot environment, String controller, WebToken token) : this(environment, controller) + public TangoWebClient(DeploymentSlot environment, String controller, String token) : this(environment, controller) { - WebToken = token; + Token = token; + } + + public TangoWebClient(String address, String controller, String token) + { + _client = new WebTransportClient(); + Address = address; + Controller = controller; + Token = token; } public async Task Login(TLoginRequest request) { var response = await _client.PostJson(GetActionAddress("Login"), request); - WebToken = response.WebToken; + Token = response.AccessToken; + _client.AuthenticationToken = Token; + + WebToken = WebToken.FromToken(Token); + _lastLoginRequest = request; IsAuthenticated = true; return response; @@ -58,27 +72,11 @@ namespace Tango.Web protected virtual async Task Post(String action, TRequest request) where TRequest : class, IWebRequestMessage where TResponse : class, IWebResponseMessage { - bool has_own_token = false; - - if (request is WebRequestSecureMessage) + if (IsAuthenticated) { - has_own_token = (request as WebRequestSecureMessage).AccessToken != null; - - if (!has_own_token) + if (DateTime.UtcNow >= WebToken.Expiration) { - if (IsAuthenticated) - { - if (DateTime.UtcNow >= WebToken.Expiration) - { - await Login(_lastLoginRequest); - } - } - else - { - throw new AuthenticationException("This tango web client has not been authenticated with the service. Please use the login method to authenticate."); - } - - (request as WebRequestSecureMessage).AccessToken = WebToken.AccessToken; + await Login(_lastLoginRequest); } } @@ -87,28 +85,15 @@ namespace Tango.Web var response = await _client.PostJson(GetActionAddress(action), request); return response; } - catch (SessionExpiredException) + catch (TokenExpiredException) { - if (!has_own_token) + try { - try - { - await Login(_lastLoginRequest); - - if (request is WebRequestSecureMessage) - { - (request as WebRequestSecureMessage).AccessToken = WebToken.AccessToken; - } - - var response = await _client.PostJson(GetActionAddress(action), request); - return response; - } - catch - { - throw; - } + await Login(_lastLoginRequest); + var response = await _client.PostJson(GetActionAddress(action), request); + return response; } - else + catch { throw; } @@ -126,7 +111,14 @@ namespace Tango.Web protected virtual String GetServiceAddress() { - return Environment.ToAddress() + $"/api/{Controller}/"; + if (Environment != null) + { + return Environment.Value.ToAddress() + $"/api/{Controller}/"; + } + else + { + return Address + $"/api/{Controller}/"; + } } public virtual void Dispose() diff --git a/Software/Visual_Studio/Tango.Web/app.config b/Software/Visual_Studio/Tango.Web/app.config index 63664236a..bc38de989 100644 --- a/Software/Visual_Studio/Tango.Web/app.config +++ b/Software/Visual_Studio/Tango.Web/app.config @@ -8,7 +8,7 @@ - + @@ -30,6 +30,10 @@ + + + + diff --git a/Software/Visual_Studio/Tango.Web/packages.config b/Software/Visual_Studio/Tango.Web/packages.config index cb6027500..0a940a2ea 100644 --- a/Software/Visual_Studio/Tango.Web/packages.config +++ b/Software/Visual_Studio/Tango.Web/packages.config @@ -3,6 +3,7 @@ + @@ -29,7 +30,7 @@ - + -- cgit v1.3.1