Skip to content

Commit

Permalink
add testclass for middleware; fix bug where status set to 401 on succ…
Browse files Browse the repository at this point in the history
…essful auth in middleware
  • Loading branch information
Friedemann Braune committed May 4, 2024
1 parent 377c015 commit 4ccedef
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 2 deletions.
7 changes: 7 additions & 0 deletions KeycloakAuthorizationServicesDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Keycloak.AuthServices.Sdk.T
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Authentication.Tests", "tests\Keycloak.AuthServices.Authentication.Tests\Keycloak.AuthServices.Authentication.Tests.csproj", "{FE34728A-25AA-44E1-A3A6-AB500307C406}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.AuthServices.Authorization.Tests", "tests\Keycloak.AuthServices.Authorization.Tests\Keycloak.AuthServices.Authorization.Tests.csproj", "{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -124,6 +126,10 @@ Global
{FE34728A-25AA-44E1-A3A6-AB500307C406}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE34728A-25AA-44E1-A3A6-AB500307C406}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE34728A-25AA-44E1-A3A6-AB500307C406}.Release|Any CPU.Build.0 = Release|Any CPU
{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -144,6 +150,7 @@ Global
{5BA4F4CA-9C35-4D9F-A3AC-1E4273DA0807} = {AEBE10B1-96B1-4060-B8C1-1F9BFA7A586C}
{A85B6B1E-2030-47BE-9B9F-F645B08E501D} = {96857509-627A-4FD2-AC82-34387619A7B1}
{FE34728A-25AA-44E1-A3A6-AB500307C406} = {96857509-627A-4FD2-AC82-34387619A7B1}
{DCFB0819-72C9-42DF-8AC6-3FE1E25C4103} = {96857509-627A-4FD2-AC82-34387619A7B1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E1907BFD-C144-4B48-AA40-972F499D4E08}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class UriBasedResourceProtectionMiddleware
/// <exception cref="ArgumentNullException">Thrown, if <see cref="IKeycloakProtectionClient"/> is null.</exception>
public UriBasedResourceProtectionMiddleware(RequestDelegate next, IKeycloakProtectionClient client)
{
this.next = next;
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.client = client ?? throw new ArgumentNullException(nameof(client));
}

Expand Down Expand Up @@ -72,10 +72,10 @@ private async Task<bool> EvaluateAuthorization(HttpContext context)

if (isAuthorized)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return true;
}

context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Keycloak.AuthServices.Authorization\Keycloak.AuthServices.Authorization.csproj" />
<ProjectReference Include="..\..\src\Keycloak.AuthServices.Sdk\Keycloak.AuthServices.Sdk.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="4.20.70" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
namespace Keycloak.AuthServices.Authorization.Tests;

using FluentAssertions;
using Keycloak.AuthServices.Sdk.AuthZ;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Moq;

#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "<Pending>")]
public class UriBasedResourceProtectionMiddlewareTests
{
[Fact]
public void Constructor_DelegateNull_ThrowArgumentNullException()
{
// Arrange
var clientMock = new Mock<IKeycloakProtectionClient>();

// Act & Assert
Assert.Throws<ArgumentNullException>(() => _ = new UriBasedResourceProtectionMiddleware(null, clientMock.Object));
}

[Fact]
public void Constructor_KeycloakClientNull_ThrowArgumentNullException()
{
// Arrange
var requestDelegateMock = new Mock<RequestDelegate>();

// Act & Assert
Assert.Throws<ArgumentNullException>(() => _ = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, null));
}

[Fact]
public async void EvaluateAuthorization_NoAdditionalAttributesGiven_CallAuthorizationClientWithExpectedParameters()
{
// Arrange
var resourceName = "/resourceName";
var scope = "GET";

var clientMock = new Mock<IKeycloakProtectionClient>();
var requestDelegateMock = new Mock<RequestDelegate>();
clientMock.Setup(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);

var httpRequestMock = new Mock<HttpRequest>();
httpRequestMock.Setup(x => x.Path).Returns(resourceName);
httpRequestMock.Setup(x => x.Method).Returns(scope);

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(httpRequestMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
clientMock.Verify(x => x.VerifyAccessToResource(resourceName, scope, CancellationToken.None), Times.Once);
}

[Fact]
public async void EvaluateAuthorization_DisableAttributesGiven_DontCallAuthorizationClientButCallDelegate()
{
// Arrange
var clientMock = new Mock<IKeycloakProtectionClient>();
var requestDelegateMock = new Mock<RequestDelegate>();

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(new Mock<HttpRequest>().Object);
SetAttributesOnContextMock(httpContextMock,
new List<Attribute>() { new DisableUriBasedResourceProtectionAttribute() },
requestDelegateMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
clientMock.Verify(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
requestDelegateMock.Verify(x => x.Invoke(httpContextMock.Object), Times.Once);
}

[Fact]
public async void EvaluateAuthorization_AllowAnonymousAttributesGiven_DontCallAuthorizationClientButCallDelegate()
{
// Arrange
var clientMock = new Mock<IKeycloakProtectionClient>();
var requestDelegateMock = new Mock<RequestDelegate>();

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(new Mock<HttpRequest>().Object);
SetAttributesOnContextMock(httpContextMock,
new List<Attribute>() { new AllowAnonymousAttribute() },
requestDelegateMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
clientMock.Verify(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
requestDelegateMock.Verify(x => x.Invoke(httpContextMock.Object), Times.Once);
}

[Fact]
public async void EvaluateAuthorization_DisableAndExplicitAuthAttributesGiven_DontCallAuthorizationClientButCallDelegate()
{
// Arrange
var clientMock = new Mock<IKeycloakProtectionClient>();
var requestDelegateMock = new Mock<RequestDelegate>();

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(new Mock<HttpRequest>().Object);
SetAttributesOnContextMock(httpContextMock,
new List<Attribute>() {
new DisableUriBasedResourceProtectionAttribute(),
new ExplicitResourceProtectionAttribute(It.IsAny<string>(), It.IsAny<string>())
},
requestDelegateMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
clientMock.Verify(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
requestDelegateMock.Verify(x => x.Invoke(httpContextMock.Object), Times.Once);
}

[Fact]
public async void EvaluateAuthorization_ExplicitAuthAttributesGiven_CallAuthorizationClientWithAttributeProperties()
{
// Arrange
var resourceName = "/resourceName";
var scope = "GET";
var resourceNameFromAttribute = "/resourceNameFromAttribute";
var scopeFromAttribute = "GET";

var clientMock = new Mock<IKeycloakProtectionClient>();
clientMock.Setup(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var requestDelegateMock = new Mock<RequestDelegate>();

var httpRequestMock = new Mock<HttpRequest>();
httpRequestMock.Setup(x => x.Path).Returns(resourceName);
httpRequestMock.Setup(x => x.Method).Returns(scope);

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(httpRequestMock.Object);
SetAttributesOnContextMock(httpContextMock,
new List<Attribute>() {
new ExplicitResourceProtectionAttribute(resourceNameFromAttribute, scopeFromAttribute)
},
requestDelegateMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
clientMock.Verify(x => x.VerifyAccessToResource(resourceNameFromAttribute, scopeFromAttribute, CancellationToken.None), Times.Once);
clientMock.Verify(x => x.VerifyAccessToResource(resourceName, scope, CancellationToken.None), Times.Never);
}

[Fact]
public async void EvaluateAuthorization_ClientReturnsTrue_CallDelegate()
{
// Arrange
var clientMock = new Mock<IKeycloakProtectionClient>();
clientMock.Setup(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var requestDelegateMock = new Mock<RequestDelegate>();

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(new Mock<HttpRequest>().Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
requestDelegateMock.Verify(x => x.Invoke(httpContextMock.Object), Times.Once);
}

[Fact]
public async void EvaluateAuthorization_ClientReturnsFalse_DontCallDelegateAndSet401()
{
// Arrange

var clientMock = new Mock<IKeycloakProtectionClient>();
clientMock.Setup(x => x.VerifyAccessToResource(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var requestDelegateMock = new Mock<RequestDelegate>();

var httpResponseMock = new Mock<HttpResponse>();
httpResponseMock.SetupAllProperties();

var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request).Returns(new Mock<HttpRequest>().Object);
httpContextMock.Setup(x => x.Response).Returns(httpResponseMock.Object);

// Act
var target = new UriBasedResourceProtectionMiddleware(requestDelegateMock.Object, clientMock.Object);
await target.InvokeAsync(httpContextMock.Object);

// Assert
requestDelegateMock.Verify(x => x.Invoke(httpContextMock.Object), Times.Never);
httpContextMock.Object.Response.StatusCode.Should().Be(StatusCodes.Status401Unauthorized);
}

private static void SetAttributesOnContextMock(Mock<HttpContext> httpContextMock, IEnumerable<Attribute> attributes, RequestDelegate requestDelegate)
{
var endpoint = new Endpoint(requestDelegate, new EndpointMetadataCollection(attributes), null);

var endpointFeatureMock = new Mock<IEndpointFeature>();
endpointFeatureMock.Setup(x => x.Endpoint).Returns(endpoint);

var featuresMock = new Mock<IFeatureCollection>();
featuresMock.Setup(x => x.Get<IEndpointFeature>()).Returns(endpointFeatureMock.Object);

httpContextMock.Setup(x => x.Features).Returns(featuresMock.Object);
}
}

0 comments on commit 4ccedef

Please sign in to comment.