diff --git a/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs b/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs new file mode 100644 index 0000000..10317e7 --- /dev/null +++ b/MainProject.Tests/Core/Attributes/JwtAuthorizationAttribute_Tests.cs @@ -0,0 +1,168 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Common.User; +using DatabaseSqlServer = BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using Microsoft.AspNetCore.Builder; +using BasicDotnetTemplate.MainProject.Models.Settings; +using BasicDotnetTemplate.MainProject.Utils; +using Microsoft.AspNetCore.Mvc.Filters; +using BasicDotnetTemplate.MainProject.Core.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + + +namespace BasicDotnetTemplate.MainProject.Tests; + +[TestClass] +public class JwtAuthorizationAttribute_Tests +{ + private static AuthenticatedUser? _authenticatedUser = null; + private static string? _token = null; + private static JwtAuthorizationAttribute? _attribute; + private static JwtTokenUtils? _jwtTokenUtils; + + [TestInitialize] + public void Setup() + { + _attribute = new JwtAuthorizationAttribute(); + + DatabaseSqlServer.User user = new DatabaseSqlServer.User() + { + Guid = Guid.NewGuid().ToString(), + Username = "Username", + FirstName = "FirstName", + LastName = "LastName", + Email = "Email", + PasswordHash = "PasswordHash", + PasswordSalt = "PasswordSalt", + Password = "Password", + Role = new DatabaseSqlServer.Role() + { + Name = "Role.Name" + }, + IsTestUser = true + }; + _authenticatedUser = new AuthenticatedUser(user); + + WebApplicationBuilder builder = WebApplication.CreateBuilder(Array.Empty()); + AppSettings appSettings = ProgramUtils.AddConfiguration(ref builder, System.AppDomain.CurrentDomain.BaseDirectory + "/JsonData"); + _jwtTokenUtils = new JwtTokenUtils(appSettings); + _token = _jwtTokenUtils.GenerateToken(user.Guid); + } + + private static AuthorizationFilterContext CreateAuthorizationContextWithAllowAnonymous() + { + var httpContext = new DefaultHttpContext(); + var routeData = new RouteData(); + var actionDescriptor = new ControllerActionDescriptor + { + EndpointMetadata = new List { new AllowAnonymousAttribute() } + }; + var actionContext = new ActionContext(httpContext, routeData, actionDescriptor); + + actionContext.ActionDescriptor.EndpointMetadata.Add(new AllowAnonymousAttribute()); + + return new AuthorizationFilterContext(actionContext, []); + } + + private static AuthorizationFilterContext CreateAuthorizationContext() + { + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); + return new AuthorizationFilterContext(actionContext, new List()); + } + + [TestMethod] + public void OnAuthorization_AllowAnonymous_SkipsAuthorization() + { + try + { + var context = CreateAuthorizationContextWithAllowAnonymous(); + _attribute?.OnAuthorization(context); + + Assert.IsNull(context.Result); + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex.Message}"); + } + } + + [TestMethod] + public void OnAuthorization_NoAuthenticatedUser_ReturnsUnauthorized() + { + var context = CreateAuthorizationContext(); + IConfiguration configuration = TestUtils.CreateConfiguration(); + + context.HttpContext.RequestServices = new ServiceCollection() + .AddSingleton(configuration) + .BuildServiceProvider(); + + context.HttpContext.Items["User"] = null; + _attribute?.OnAuthorization(context); + Assert.IsInstanceOfType(context.Result, typeof(UnauthorizedResult)); + } + + [TestMethod] + public void OnAuthorization_EmptyAuthorizationHeader_ReturnsUnauthorized() + { + var context = CreateAuthorizationContext(); + IConfiguration configuration = TestUtils.CreateConfiguration(); + + context.HttpContext.RequestServices = new ServiceCollection() + .AddSingleton(configuration) + .BuildServiceProvider(); + + context.HttpContext.Items["User"] = _authenticatedUser; + context.HttpContext.Request.Headers.Authorization = string.Empty; + + _attribute?.OnAuthorization(context); + + Assert.IsInstanceOfType(context.Result, typeof(UnauthorizedResult)); + } + + + [TestMethod] + public void OnAuthorization_InvalidToken_ReturnsUnauthorized() + { + var context = CreateAuthorizationContext(); + IConfiguration configuration = TestUtils.CreateConfiguration(); + + context.HttpContext.RequestServices = new ServiceCollection() + .AddSingleton(configuration) + .BuildServiceProvider(); + + var invalidToken = _jwtTokenUtils?.GenerateToken(Guid.NewGuid().ToString()); + context.HttpContext.Request.Headers.Authorization = $"Bearer {invalidToken}"; + + context.HttpContext.Items["User"] = _authenticatedUser; + + _attribute?.OnAuthorization(context); + + Assert.IsInstanceOfType(context.Result, typeof(UnauthorizedResult)); + } + + [TestMethod] + public void OnAuthorization_ValidToken() + { + var context = CreateAuthorizationContext(); + IConfiguration configuration = TestUtils.CreateConfiguration(); + + context.HttpContext.RequestServices = new ServiceCollection() + .AddSingleton(configuration) + .BuildServiceProvider(); + context.HttpContext.Request.Headers.Authorization = $"Bearer {_token}"; + + context.HttpContext.Items["User"] = _authenticatedUser; + + _attribute?.OnAuthorization(context); + + Assert.IsNotInstanceOfType(context.Result, typeof(UnauthorizedResult)); + } + +} \ No newline at end of file diff --git a/MainProject.Tests/Core/AutoMapperConfiguration_Tests.cs b/MainProject.Tests/Core/Middlewares/AutoMapperConfiguration_Tests.cs similarity index 100% rename from MainProject.Tests/Core/AutoMapperConfiguration_Tests.cs rename to MainProject.Tests/Core/Middlewares/AutoMapperConfiguration_Tests.cs diff --git a/MainProject.Tests/Services/JwtService_Tests.cs b/MainProject.Tests/Services/JwtService_Tests.cs index 6be0bc5..a4150f6 100644 --- a/MainProject.Tests/Services/JwtService_Tests.cs +++ b/MainProject.Tests/Services/JwtService_Tests.cs @@ -27,14 +27,14 @@ public class JwtService_Tests try { var jwtService = TestUtils.CreateJwtService(); - if(jwtService != null) + if (jwtService != null) { Assert.IsInstanceOfType(jwtService, typeof(JwtService)); } else { Assert.Fail($"JwtService is null"); - } + } } catch (Exception ex) { @@ -50,7 +50,7 @@ public class JwtService_Tests { var jwtService = TestUtils.CreateJwtService(); var testString = "test"; - if(jwtService != null) + if (jwtService != null) { var jwt = jwtService.GenerateToken(testString); Assert.IsTrue(jwt != null); @@ -68,31 +68,6 @@ public class JwtService_Tests } } - [TestMethod] - public void ValidateToken_Null() - { - try - { - var jwtService = TestUtils.CreateJwtService(); - var testString = "test"; - if(jwtService != null) - { - var jwt = jwtService.GenerateToken(testString); - var user = jwtService.ValidateToken($"Bearer {jwt}"); - Assert.IsTrue(user == null); - } - else - { - Assert.Fail($"JwtService is null"); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.InnerException); - Assert.Fail($"An exception was thrown: {ex.Message}"); - } - } - } diff --git a/MainProject/Controllers/UserController.cs b/MainProject/Controllers/UserController.cs index 6674881..871feb8 100644 --- a/MainProject/Controllers/UserController.cs +++ b/MainProject/Controllers/UserController.cs @@ -20,6 +20,7 @@ namespace BasicDotnetTemplate.MainProject.Controllers this._userService = userService; } + [JwtAuthorization()] [HttpGet("get/{guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status404NotFound)] diff --git a/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs b/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs index 4fd8d48..dcab994 100644 --- a/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs +++ b/MainProject/Core/Attributes/JwtAuthorizationAttribute .cs @@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Authorization; using BasicDotnetTemplate.MainProject.Models.Settings; using BasicDotnetTemplate.MainProject.Services; using DatabaseSqlServer = BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using BasicDotnetTemplate.MainProject.Utils; +using BasicDotnetTemplate.MainProject.Models.Api.Common.User; namespace BasicDotnetTemplate.MainProject.Core.Attributes @@ -18,13 +20,9 @@ namespace BasicDotnetTemplate.MainProject.Core.Attributes [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class JwtAuthorizationAttribute : Attribute, IAuthorizationFilter { - private readonly IJwtService _jwtService; - public JwtAuthorizationAttribute( - IJwtService jwtService ) { - _jwtService = jwtService; } public static void Unauthorized(AuthorizationFilterContext context) @@ -34,30 +32,54 @@ namespace BasicDotnetTemplate.MainProject.Core.Attributes public void OnAuthorization(AuthorizationFilterContext context) { - DatabaseSqlServer.User? user = null; // If [AllowAnonymous], skip if (context.ActionDescriptor.EndpointMetadata.Any(em => em is AllowAnonymousAttribute)) { return; } + string? userGuidFromToken = null; + var configuration = context.HttpContext.RequestServices.GetRequiredService(); var appSettings = new AppSettings(); configuration.GetSection("AppSettings").Bind(appSettings); string? headerAuthorization = context.HttpContext.Request.Headers.Authorization.FirstOrDefault(); - - if(!String.IsNullOrEmpty(headerAuthorization)) + AuthenticatedUser? userContext = context.HttpContext.Items["User"] != null ? (AuthenticatedUser?)context.HttpContext.Items["User"] : null; + + if (userContext == null) { - user = _jwtService.ValidateToken(headerAuthorization!); - if(user == null) + Unauthorized(context); + } + else + { + if (!String.IsNullOrEmpty(headerAuthorization)) + { + userGuidFromToken = JwtAuthorizationAttribute.ValidateToken(headerAuthorization!, appSettings); + if (String.IsNullOrEmpty(userGuidFromToken)) + { + Unauthorized(context); + } + else + { + if (userContext!.Guid != userGuidFromToken) + { + Unauthorized(context); + } + } + } + else { Unauthorized(context); } } - else - { - Unauthorized(context); - } + + + } + + private static string? ValidateToken(string headerAuthorization, AppSettings appSettings) + { + JwtTokenUtils _jwtTokenUtils = new(appSettings); + return _jwtTokenUtils.ValidateToken(headerAuthorization); } } } diff --git a/MainProject/Services/JwtService.cs b/MainProject/Services/JwtService.cs index ff17cc3..694a029 100644 --- a/MainProject/Services/JwtService.cs +++ b/MainProject/Services/JwtService.cs @@ -14,7 +14,6 @@ namespace BasicDotnetTemplate.MainProject.Services; public interface IJwtService { string GenerateToken(string guid); - DatabaseSqlServer.User? ValidateToken(string headerAuthorization); } public class JwtService : BaseService, IJwtService @@ -38,17 +37,7 @@ public class JwtService : BaseService, IJwtService return _jwtTokenUtils.GenerateToken(guid); } - public DatabaseSqlServer.User? ValidateToken(string headerAuthorization) - { - DatabaseSqlServer.User? user = null; - string? guid = _jwtTokenUtils.ValidateToken(headerAuthorization); - if(!String.IsNullOrEmpty(guid)) - { - var userTask = Task.Run(() => this._userService.GetUserByGuidAsync(guid)); - user = userTask.Result; - } - return user; - } + }