From 14c82f4f0638a39b5b8a4f086dadd6df22ffbcce Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 24 Nov 2024 17:18:35 +0100 Subject: [PATCH] Implemented security module tests --- HopFrame.sln | 7 + HopFrame.sln.DotSettings.user | 18 ++- .../Authentication/HopFrameAuthentication.cs | 1 - .../HopFrame.Database.Tests.csproj | 1 - .../AuthenticationTests.cs | 141 ++++++++++++++++++ .../AuthorizationTests.cs | 92 ++++++++++++ .../HopFrame.Security.Tests.csproj | 34 +++++ 7 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 tests/HopFrame.Security.Tests/AuthenticationTests.cs create mode 100644 tests/HopFrame.Security.Tests/AuthorizationTests.cs create mode 100644 tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj diff --git a/HopFrame.sln b/HopFrame.sln index 7a60cf4..94da369 100644 --- a/HopFrame.sln +++ b/HopFrame.sln @@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security.Tests", "tests\HopFrame.Security.Tests\HopFrame.Security.Tests.csproj", "{6747753A-6059-48F1-B779-D73765A373A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +62,10 @@ Global {1CAAC943-B8FE-48DD-9712-92699647DE18}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.Build.0 = Release|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6747753A-6059-48F1-B779-D73765A373A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} @@ -70,5 +76,6 @@ Global {8F983A37-63CF-48D5-988D-58B78EF8AECD} = {EEA20D27-D471-44AF-A287-C0E068D93182} {921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {EEA20D27-D471-44AF-A287-C0E068D93182} {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} + {6747753A-6059-48F1-B779-D73765A373A6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A} EndGlobalSection EndGlobal diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index a0c3151..4f1a50c 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -1,4 +1,5 @@  + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -8,9 +9,16 @@ <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /> <Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /> </AssemblyExplorer> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="UserRepositoryTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + + + + + + + + <SessionState ContinuousTestingMode="0" Name="Authentication_With_NoToken_Should_Fail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> - <TestId>xUnit::1CAAC943-B8FE-48DD-9712-92699647DE18::net8.0::HopFrame.Database.Tests.Repositories.UserRepositoryTests</TestId> + <TestId>xUnit::6747753A-6059-48F1-B779-D73765A373A6::net8.0::HopFrame.Security.Tests.AuthenticationTests.Authentication_With_NoToken_Should_Fail</TestId> </TestAncestor> </SessionState> @@ -27,6 +35,12 @@ + + + + + + diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs index 9c65b14..8709f91 100644 --- a/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs +++ b/src/HopFrame.Security/Authentication/HopFrameAuthentication.cs @@ -17,7 +17,6 @@ public class HopFrameAuthentication( UrlEncoder encoder, ISystemClock clock, ITokenRepository tokens, - IUserRepository users, IPermissionRepository perms) : AuthenticationHandler(options, logger, encoder, clock) { diff --git a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj index 4435c88..ce93d67 100644 --- a/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj +++ b/tests/HopFrame.Database.Tests/HopFrame.Database.Tests.csproj @@ -11,7 +11,6 @@ - diff --git a/tests/HopFrame.Security.Tests/AuthenticationTests.cs b/tests/HopFrame.Security.Tests/AuthenticationTests.cs new file mode 100644 index 0000000..2594cbb --- /dev/null +++ b/tests/HopFrame.Security.Tests/AuthenticationTests.cs @@ -0,0 +1,141 @@ +using System.Text.Encodings.Web; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; + +namespace HopFrame.Security.Tests; + +public class AuthenticationTests { + + private async Task SetupEnvironment(Token correctToken = null, string providedToken = null) { + var options = new Mock>(); + options + .Setup(x => x.Get(It.IsAny())) + .Returns(new AuthenticationSchemeOptions()); + + var logger = new Mock(); + logger + .Setup(x => x.CreateLogger(It.IsAny())) + .Returns(new Mock>().Object); + + var encoder = new Mock(); + var clock = new Mock(); + var tokens = new Mock(); + var perms = new Mock(); + + var provideCorrectToken = correctToken is null; + correctToken ??= new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Type = Token.AccessTokenType, + Owner = new User { + Id = Guid.NewGuid() + } + }; + + tokens + .Setup(x => x.GetToken(It.Is(t => t == correctToken.Content.ToString()))) + .ReturnsAsync(correctToken); + + perms + .Setup(x => x.GetFullPermissions(It.IsAny())) + .ReturnsAsync(new List()); + + var auth = new HopFrameAuthentication(options.Object, logger.Object, encoder.Object, clock.Object, tokens.Object, perms.Object); + var context = new DefaultHttpContext(); + if (provideCorrectToken) + context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, correctToken.Content.ToString()); + if (providedToken is not null) + context.HttpContext.Request.Headers.Append(HopFrameAuthentication.SchemeName, providedToken); + + await auth.InitializeAsync(new AuthenticationScheme(HopFrameAuthentication.SchemeName, null, typeof(HopFrameAuthentication)), context); + return auth; + } + + [Fact] + public async Task Authentication_Should_Succeed() { + // Arrange + var auth = await SetupEnvironment(); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.True(result.Succeeded); + } + + [Fact] + public async Task Authentication_With_NoToken_Should_Fail() { + // Arrange + var auth = await SetupEnvironment(new Token()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("No Access Token provided", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_InvalidToken_Should_Fail() { + // Arrange + var auth = await SetupEnvironment(null, Guid.NewGuid().ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token does not exist", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_ExpiredToken_Should_Fail() { + // Arrange + var token = new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.MinValue, + Type = Token.AccessTokenType, + Owner = new User() + }; + var auth = await SetupEnvironment(token, token.Content.ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token is expired", result.Failure.Message); + } + + [Fact] + public async Task Authentication_With_UnownedToken_Should_Fail() { + // Arrange + var token = new Token { + Content = Guid.NewGuid(), + CreatedAt = DateTime.Now, + Type = Token.AccessTokenType, + Owner = null + }; + var auth = await SetupEnvironment(token, token.Content.ToString()); + + // Act + var result = await auth.AuthenticateAsync(); + + // Assert + Assert.False(result.Succeeded); + Assert.NotNull(result.Failure); + Assert.Equal("The provided Access Token does not match any user", result.Failure.Message); + } + + +} \ No newline at end of file diff --git a/tests/HopFrame.Security.Tests/AuthorizationTests.cs b/tests/HopFrame.Security.Tests/AuthorizationTests.cs new file mode 100644 index 0000000..730dfde --- /dev/null +++ b/tests/HopFrame.Security.Tests/AuthorizationTests.cs @@ -0,0 +1,92 @@ +using System.Security.Claims; +using HopFrame.Security.Authentication; +using HopFrame.Security.Authorization; +using HopFrame.Security.Claims; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Moq; + +namespace HopFrame.Security.Tests; + +public class AuthorizationTests { + + private (AuthorizedFilter, AuthorizationFilterContext) SetupEnvironment(string[] userPermissions, string[] requiredPermissions, bool accessTokenProvided = true) { + var filter = new AuthorizedFilter(requiredPermissions); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext { HttpContext = httpContext, RouteData = new RouteData(), ActionDescriptor = new ActionDescriptor() }; + var context = new Mock(MockBehavior.Default, actionContext, new List()); + + context + .Setup(x => x.Filters) + .Returns(new List()); + + context.SetupProperty(c => c.Result); + + var claims = new List { + new(HopFrameClaimTypes.UserId, Guid.NewGuid().ToString()) + }; + if (accessTokenProvided) + claims.Add(new (HopFrameClaimTypes.AccessTokenId, Guid.NewGuid().ToString())); + claims.AddRange(userPermissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm))); + + context.Object.HttpContext.User.AddIdentity(new ClaimsIdentity(claims, HopFrameAuthentication.SchemeName)); + + return (filter, context.Object); + } + + [Fact] + public void OnAuthorization_Should_Succeed() { + // Arrange + var (filter, context) = SetupEnvironment(["test.permission"], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.Null(context.Result); + } + + [Fact] + public void OnAuthorization_With_NoToken_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment([], [], false); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + + [Fact] + public void OnAuthorization_With_NoPermissions_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment([], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + + [Fact] + public void OnAuthorization_With_InsufficientPermissions_Should_Fail() { + // Arrange + var (filter, context) = SetupEnvironment(["permission.other"], ["test.permission"]); + + // Act + filter.OnAuthorization(context); + + // Assert + Assert.NotNull(context.Result); + Assert.IsType(context.Result); + } + +} \ No newline at end of file diff --git a/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj b/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj new file mode 100644 index 0000000..c1feef2 --- /dev/null +++ b/tests/HopFrame.Security.Tests/HopFrame.Security.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + + + + + + ..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll + + + + + + + +