Release/v2.1.0 #44

Merged
leon.hoppe merged 32 commits from release/v2.1.0 into main 2024-12-22 19:24:17 +01:00
7 changed files with 290 additions and 4 deletions
Showing only changes of commit 14c82f4f06 - Show all commits

View File

@@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1D98E5DE
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database.Tests", "tests\HopFrame.Database.Tests\HopFrame.Database.Tests.csproj", "{1CAAC943-B8FE-48DD-9712-92699647DE18}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{1CAAC943-B8FE-48DD-9712-92699647DE18}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{1E821490-AEDC-4F55-B758-52F4FADAB53A} = {64EDCBED-A84F-4936-8697-78DC43CB2427} {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} {8F983A37-63CF-48D5-988D-58B78EF8AECD} = {EEA20D27-D471-44AF-A287-C0E068D93182}
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {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} {1CAAC943-B8FE-48DD-9712-92699647DE18} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A}
{6747753A-6059-48F1-B779-D73765A373A6} = {1D98E5DE-CB8B-4C1C-A319-D49AC137441A}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationSchemeOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8525b7a9e58c77f532f1a88d4f2897e3c2baf316b9eb2c391b242a3885fcce6_003FAuthenticationSchemeOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb7208b3f72528d22781d25fde9a55271bdf2b5aade4f03b1324579a25493cd8_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb7208b3f72528d22781d25fde9a55271bdf2b5aade4f03b1324579a25493cd8_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -8,9 +9,16 @@
&lt;Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /&gt;&#xD; &lt;Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" /&gt;&#xD;
&lt;Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /&gt;&#xD; &lt;Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String> &lt;/AssemblyExplorer&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=420e6947_002Dcf5f_002D4e55_002D94cb_002D22d1ee8dd4ee/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="UserRepositoryTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=de547b37_002D85df_002D4c95_002Da13a_002D03b9d37ad50b/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Authentication_With_NoToken_Should_Fail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD; &lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::1CAAC943-B8FE-48DD-9712-92699647DE18::net8.0::HopFrame.Database.Tests.Repositories.UserRepositoryTests&lt;/TestId&gt;&#xD; &lt;TestId&gt;xUnit::6747753A-6059-48F1-B779-D73765A373A6::net8.0::HopFrame.Security.Tests.AuthenticationTests.Authentication_With_NoToken_Should_Fail&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD; &lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String> &lt;/SessionState&gt;</s:String>
@@ -27,6 +35,12 @@

View File

@@ -17,7 +17,6 @@ public class HopFrameAuthentication(
UrlEncoder encoder, UrlEncoder encoder,
ISystemClock clock, ISystemClock clock,
ITokenRepository tokens, ITokenRepository tokens,
IUserRepository users,
IPermissionRepository perms) IPermissionRepository perms)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock) { : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock) {

View File

@@ -11,7 +11,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0"/> <PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="FakeItEasy" Version="8.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="xunit" Version="2.5.3"/> <PackageReference Include="xunit" Version="2.5.3"/>

View File

@@ -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<HopFrameAuthentication> SetupEnvironment(Token correctToken = null, string providedToken = null) {
var options = new Mock<IOptionsMonitor<AuthenticationSchemeOptions>>();
options
.Setup(x => x.Get(It.IsAny<string>()))
.Returns(new AuthenticationSchemeOptions());
var logger = new Mock<ILoggerFactory>();
logger
.Setup(x => x.CreateLogger(It.IsAny<String>()))
.Returns(new Mock<ILogger<HopFrameAuthentication>>().Object);
var encoder = new Mock<UrlEncoder>();
var clock = new Mock<ISystemClock>();
var tokens = new Mock<ITokenRepository>();
var perms = new Mock<IPermissionRepository>();
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<string>(t => t == correctToken.Content.ToString())))
.ReturnsAsync(correctToken);
perms
.Setup(x => x.GetFullPermissions(It.IsAny<User>()))
.ReturnsAsync(new List<string>());
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);
}
}

View File

@@ -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<AuthorizationFilterContext>(MockBehavior.Default, actionContext, new List<IFilterMetadata>());
context
.Setup(x => x.Filters)
.Returns(new List<IFilterMetadata>());
context.SetupProperty(c => c.Result);
var claims = new List<Claim> {
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<UnauthorizedResult>(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<UnauthorizedResult>(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<UnauthorizedResult>(context.Result);
}
}

View File

@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.5.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authentication">
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.11\Microsoft.AspNetCore.Authentication.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\HopFrame.Security\HopFrame.Security.csproj" />
</ItemGroup>
</Project>