From 3d635dab60d11bdf5dcfbe1baf9f16a8221b9bec Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Fri, 10 Jul 2020 00:29:39 +0300 Subject: [PATCH] authorization system --- .../BasicAuthenticationHandler.cs | 14 ++---- .../Controllers/ResurrectorController.cs | 9 +++- NetworkResurrector.Api/appsettings.json | 11 +++-- .../DependencyInjectionExtensions.cs | 7 +++ .../Mappings/MappingProfile.cs | 3 ++ .../NetworkResurrector.Application.csproj | 2 +- .../Queries/GetToken.cs | 15 +++++-- .../Services/ParamProvider.cs | 4 +- .../Services/UserService.cs | 44 ++++++++++++++----- .../Stores/ISecurityStore.cs | 10 +++++ .../Stores/SecurityStore.cs | 42 ++++++++++++++++++ NetworkResurrector.Domain/Entities/User.cs | 2 +- .../Models/SecurityToken.cs | 11 +++++ .../Models/TokenValidation.cs | 8 ++++ .../Services/IParamProvider.cs | 4 +- 15 files changed, 149 insertions(+), 37 deletions(-) create mode 100644 NetworkResurrector.Application/Stores/ISecurityStore.cs create mode 100644 NetworkResurrector.Application/Stores/SecurityStore.cs create mode 100644 NetworkResurrector.Domain/Models/SecurityToken.cs create mode 100644 NetworkResurrector.Domain/Models/TokenValidation.cs diff --git a/NetworkResurrector.Api/Authentication/BasicAuthenticationHandler.cs b/NetworkResurrector.Api/Authentication/BasicAuthenticationHandler.cs index ec1913a..43c9f48 100644 --- a/NetworkResurrector.Api/Authentication/BasicAuthenticationHandler.cs +++ b/NetworkResurrector.Api/Authentication/BasicAuthenticationHandler.cs @@ -3,11 +3,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NetworkResurrector.Application.Services; using NetworkResurrector.Domain.Entities; -using System; -using System.Linq; using System.Net.Http.Headers; using System.Security.Claims; -using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -31,12 +28,9 @@ namespace NetworkResurrector.Api.Authentication User user; try { - var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); - var credentialBytes = Convert.FromBase64String(authHeader.Parameter); - var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':'); - var username = credentials.First(); - var password = credentials.Last(); - user = await _userService.Authenticate(username, password); + var authorizationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); + var token = authorizationHeader.Parameter; + user = await _userService.Authenticate(token); } catch { @@ -47,7 +41,7 @@ namespace NetworkResurrector.Api.Authentication return AuthenticateResult.Fail("Invalid Username or Password"); var claims = new[] { - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()), new Claim(ClaimTypes.Name, user.UserName), }; diff --git a/NetworkResurrector.Api/Controllers/ResurrectorController.cs b/NetworkResurrector.Api/Controllers/ResurrectorController.cs index bc83232..6bcc75b 100644 --- a/NetworkResurrector.Api/Controllers/ResurrectorController.cs +++ b/NetworkResurrector.Api/Controllers/ResurrectorController.cs @@ -18,11 +18,18 @@ namespace NetworkResurrector.Api.Controllers _mediator = mediator; } - [HttpGet("token")] + [AllowAnonymous] + [HttpGet("token/{userName}/{password}")] public async Task GetToken([FromRoute] GetToken.Query query) { var result = await _mediator.Send(query); return Ok(result); } + + [HttpGet("validate-token")] + public IActionResult ValidateToken() + { + return Ok("Valid"); + } } } diff --git a/NetworkResurrector.Api/appsettings.json b/NetworkResurrector.Api/appsettings.json index dabcd02..55dfb99 100644 --- a/NetworkResurrector.Api/appsettings.json +++ b/NetworkResurrector.Api/appsettings.json @@ -11,8 +11,11 @@ } }, "AllowedHosts": "*", - "Credentials": { - "UserName": "***REMOVED***", - "Password": "***REMOVED***" - } + "Users": [ + { + "UserId": 1, + "UserName": "***REMOVED***", + "Password": "***REMOVED***" + } + ] } diff --git a/NetworkResurrector.Application/DependencyInjectionExtensions.cs b/NetworkResurrector.Application/DependencyInjectionExtensions.cs index 0085e52..89a9f1f 100644 --- a/NetworkResurrector.Application/DependencyInjectionExtensions.cs +++ b/NetworkResurrector.Application/DependencyInjectionExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using NetworkResurrector.Application.Services; +using NetworkResurrector.Application.Stores; using NetworkResurrector.Domain.Services; namespace NetworkResurrector.Application @@ -10,6 +11,12 @@ namespace NetworkResurrector.Application { services.AddSingleton(); services.AddScoped(); + services.AddStores(); + } + + private static void AddStores(this IServiceCollection services) + { + services.AddSingleton(); } } } diff --git a/NetworkResurrector.Application/Mappings/MappingProfile.cs b/NetworkResurrector.Application/Mappings/MappingProfile.cs index 58c629a..76b8990 100644 --- a/NetworkResurrector.Application/Mappings/MappingProfile.cs +++ b/NetworkResurrector.Application/Mappings/MappingProfile.cs @@ -1,4 +1,6 @@ using AutoMapper; +using NetworkResurrector.Application.Queries; +using NetworkResurrector.Domain.Models; namespace NetworkResurrector.Application.Mappings { @@ -6,6 +8,7 @@ namespace NetworkResurrector.Application.Mappings { public MappingProfile() { + CreateMap(); } } } diff --git a/NetworkResurrector.Application/NetworkResurrector.Application.csproj b/NetworkResurrector.Application/NetworkResurrector.Application.csproj index be77fc1..dd8ca69 100644 --- a/NetworkResurrector.Application/NetworkResurrector.Application.csproj +++ b/NetworkResurrector.Application/NetworkResurrector.Application.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/NetworkResurrector.Application/Queries/GetToken.cs b/NetworkResurrector.Application/Queries/GetToken.cs index e63adde..38c59f7 100644 --- a/NetworkResurrector.Application/Queries/GetToken.cs +++ b/NetworkResurrector.Application/Queries/GetToken.cs @@ -1,5 +1,6 @@ using AutoMapper; using MediatR; +using NetworkResurrector.Application.Services; using System; using System.Threading; using System.Threading.Tasks; @@ -17,23 +18,29 @@ namespace NetworkResurrector.Application.Queries public class Model { - public Guid Token { get; set; } + public string Token { get; set; } public DateTime ValidUntil { get; set; } } public class QueryHandler : IRequestHandler { + private readonly IUserService _userService; private readonly IMapper _mapper; - public QueryHandler(IMapper mapper) + public QueryHandler(IUserService userService, IMapper mapper) { + _userService = userService; _mapper = mapper; } public async Task Handle(Query request, CancellationToken cancellationToken) { - - return new Model() { Token = Guid.NewGuid(), ValidUntil = DateTime.Now }; + var securityToken = await _userService.Login(request.UserName, request.Password); + if (securityToken == null) + return null; + + var result = _mapper.Map(securityToken); + return result; } } } diff --git a/NetworkResurrector.Application/Services/ParamProvider.cs b/NetworkResurrector.Application/Services/ParamProvider.cs index 2c45b9e..9a48bff 100644 --- a/NetworkResurrector.Application/Services/ParamProvider.cs +++ b/NetworkResurrector.Application/Services/ParamProvider.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.Configuration; -using NetworkResurrector.Domain.Models.Settings; +using NetworkResurrector.Domain.Entities; using NetworkResurrector.Domain.Services; namespace NetworkResurrector.Application.Services @@ -13,6 +13,6 @@ namespace NetworkResurrector.Application.Services _configuration = configuration; } - public Credentials Credentials => _configuration.GetSection("Credentials").Get(); + public User[] Users => _configuration.GetSection("Users").Get(); } } diff --git a/NetworkResurrector.Application/Services/UserService.cs b/NetworkResurrector.Application/Services/UserService.cs index 89a5127..d322fb9 100644 --- a/NetworkResurrector.Application/Services/UserService.cs +++ b/NetworkResurrector.Application/Services/UserService.cs @@ -1,34 +1,54 @@ -using NetworkResurrector.Domain.Entities; +using NetworkResurrector.Application.Stores; +using NetworkResurrector.Domain.Entities; +using NetworkResurrector.Domain.Models; using NetworkResurrector.Domain.Services; +using System; +using System.Linq; using System.Threading.Tasks; namespace NetworkResurrector.Application.Services { public interface IUserService { - Task Authenticate(string username, string password); + Task Login(string userName, string password); + Task Authenticate(string token); } public class UserService : IUserService { private readonly IParamProvider _paramProvider; + private readonly ISecurityStore _securityStore; - public UserService(IParamProvider paramProvider) + public UserService(IParamProvider paramProvider, ISecurityStore securityStore) { _paramProvider = paramProvider; + _securityStore = securityStore; } - public async Task Authenticate(string username, string password) + public async Task Login(string userName, string password) { - return await Task.Run(() => CheckCredentials(username, password)); - } - - private User CheckCredentials(string username, string password) - { - if (_paramProvider.Credentials.UserName == username && _paramProvider.Credentials.Password == password) - return new User() { UserName = username, Id = 1 }; - else + var user = _paramProvider.Users.FirstOrDefault(z => z.UserName == userName && z.Password == password); + if (user == null) return null; + + var token = $"{Guid.NewGuid()}-{Guid.NewGuid()}-{user.UserId}"; + _securityStore.SetToken(token, user.UserId); + + var securityToken = new SecurityToken() { UserId = user.UserId, Token = token }; + return securityToken; + } + + public async Task Authenticate(string token) + { + var tokenValidation = _securityStore.ValidateToken(token); + if (tokenValidation.Success) + { + var user = _paramProvider.Users.FirstOrDefault(z => z.UserId == tokenValidation.UserId); + if (user != null) + return user; + } + + return null; } } } diff --git a/NetworkResurrector.Application/Stores/ISecurityStore.cs b/NetworkResurrector.Application/Stores/ISecurityStore.cs new file mode 100644 index 0000000..87ef287 --- /dev/null +++ b/NetworkResurrector.Application/Stores/ISecurityStore.cs @@ -0,0 +1,10 @@ +using NetworkResurrector.Domain.Models; + +namespace NetworkResurrector.Application.Stores +{ + public interface ISecurityStore + { + void SetToken(string token, int userId); + TokenValidation ValidateToken(string token); + } +} \ No newline at end of file diff --git a/NetworkResurrector.Application/Stores/SecurityStore.cs b/NetworkResurrector.Application/Stores/SecurityStore.cs new file mode 100644 index 0000000..4ebbb72 --- /dev/null +++ b/NetworkResurrector.Application/Stores/SecurityStore.cs @@ -0,0 +1,42 @@ +using NetworkResurrector.Domain.Models; +using System.Collections.Generic; +using System.Linq; + +namespace NetworkResurrector.Application.Stores +{ + public class SecurityStore : ISecurityStore + { + private List Tokens { get; } + + public SecurityStore() + { + Tokens = new List(); + } + + public void SetToken(string token, int userId) + { + var registered = Tokens.FirstOrDefault(z => z.UserId == userId); + if (registered == null) + Tokens.Add(new SecurityToken() { UserId = userId, Token = token }); + else + registered.Token = token; + } + + public TokenValidation ValidateToken(string token) + { + var lastIndexOfSeparator = token.LastIndexOf('-') + 1; + var userIdString = token.Substring(lastIndexOfSeparator, token.Length - lastIndexOfSeparator); + + if (!int.TryParse(userIdString, out int userId)) + return InvalidToken; + + var valid = Tokens.FirstOrDefault(z => z.UserId == userId && z.Token == token); + if (valid != null) + return new TokenValidation() { Success = true, UserId = userId }; + + return InvalidToken; + } + + private TokenValidation InvalidToken => new TokenValidation() { Success = false }; + } +} \ No newline at end of file diff --git a/NetworkResurrector.Domain/Entities/User.cs b/NetworkResurrector.Domain/Entities/User.cs index 8ec1ee8..355bd0f 100644 --- a/NetworkResurrector.Domain/Entities/User.cs +++ b/NetworkResurrector.Domain/Entities/User.cs @@ -2,7 +2,7 @@ { public class User { - public int Id { get; set; } + public int UserId { get; set; } public string UserName { get; set; } public string Password { get; set; } } diff --git a/NetworkResurrector.Domain/Models/SecurityToken.cs b/NetworkResurrector.Domain/Models/SecurityToken.cs new file mode 100644 index 0000000..63afc61 --- /dev/null +++ b/NetworkResurrector.Domain/Models/SecurityToken.cs @@ -0,0 +1,11 @@ +using System; + +namespace NetworkResurrector.Domain.Models +{ + public class SecurityToken + { + public int UserId { get; set; } + public string Token { get; set; } + public DateTime ValidUntil { get; set; } + } +} diff --git a/NetworkResurrector.Domain/Models/TokenValidation.cs b/NetworkResurrector.Domain/Models/TokenValidation.cs new file mode 100644 index 0000000..7217153 --- /dev/null +++ b/NetworkResurrector.Domain/Models/TokenValidation.cs @@ -0,0 +1,8 @@ +namespace NetworkResurrector.Domain.Models +{ + public class TokenValidation + { + public bool Success { get; set; } + public int UserId { get; set; } + } +} diff --git a/NetworkResurrector.Domain/Services/IParamProvider.cs b/NetworkResurrector.Domain/Services/IParamProvider.cs index 7fa2621..1aee4cf 100644 --- a/NetworkResurrector.Domain/Services/IParamProvider.cs +++ b/NetworkResurrector.Domain/Services/IParamProvider.cs @@ -1,9 +1,9 @@ -using NetworkResurrector.Domain.Models.Settings; +using NetworkResurrector.Domain.Entities; namespace NetworkResurrector.Domain.Services { public interface IParamProvider { - Credentials Credentials { get; } + User[] Users { get; } } }