-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6e4f2e7
commit f3a3537
Showing
3 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
103 changes: 103 additions & 0 deletions
103
src/Linker.WebApi.UnitTests/Filters/MinimumRoleAuthorizeAttributeSteps.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
namespace Linker.WebApi.UnitTests.Filters; | ||
|
||
using FluentAssertions; | ||
using Linker.Core.Models; | ||
using Linker.WebApi.Filters; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Abstractions; | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
internal sealed class MinimumRoleAuthorizeAttributeSteps | ||
{ | ||
private MinimumRoleAuthorizeAttribute attribute; | ||
private AuthorizationFilterContext filterContext; | ||
|
||
private object? result; | ||
|
||
public MinimumRoleAuthorizeAttributeSteps GivenCurrentUserHasRole(Role role) | ||
{ | ||
var roleClaim = new Claim(ClaimTypes.Role, role.ToString()); | ||
var identity = new ClaimsIdentity([roleClaim]); | ||
var user = new ClaimsPrincipal(identity); | ||
|
||
var httpContext = new DefaultHttpContext | ||
{ | ||
User = user, | ||
}; | ||
var actionContext = new ActionContext(httpContext, new(), new()); | ||
this.filterContext = new AuthorizationFilterContext(actionContext, new List<IFilterMetadata>()); | ||
|
||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps GivenCurrentUserHasRole(string role) | ||
{ | ||
var roleClaim = new Claim(ClaimTypes.Role, role); | ||
var identity = new ClaimsIdentity([roleClaim]); | ||
var user = new ClaimsPrincipal(identity); | ||
|
||
var httpContext = new DefaultHttpContext | ||
{ | ||
User = user, | ||
}; | ||
var actionContext = new ActionContext(httpContext, new(), new()); | ||
this.filterContext = new AuthorizationFilterContext(actionContext, new List<IFilterMetadata>()); | ||
|
||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps GivenCurrentUserHasNoRole() | ||
{ | ||
var identity = new ClaimsIdentity(); | ||
var user = new ClaimsPrincipal(identity); | ||
|
||
var httpContext = new DefaultHttpContext | ||
{ | ||
User = user, | ||
}; | ||
var actionContext = new ActionContext(httpContext, new(), new()); | ||
this.filterContext = new AuthorizationFilterContext(actionContext, new List<IFilterMetadata>()); | ||
|
||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps GivenMinimumRoleRequiredIs(Role role) | ||
{ | ||
this.attribute = new MinimumRoleAuthorizeAttribute(role); | ||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps WhenIAuthorize() | ||
{ | ||
this.attribute.OnAuthorization(this.filterContext); | ||
this.result = this.filterContext.Result; | ||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps ThenIExpectResultToBe(IActionResult result) | ||
{ | ||
this.result.Should().BeAssignableTo<IActionResult>(); | ||
this.result.Should().Be(result); | ||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps ThenIExpectToBeAuthorized() | ||
{ | ||
this.result.Should().BeNull(); | ||
return this; | ||
} | ||
|
||
public MinimumRoleAuthorizeAttributeSteps ThenIExpectToBeUnauthorized() | ||
{ | ||
this.result.Should().BeOfType<UnauthorizedResult>(); | ||
return this; | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
src/Linker.WebApi.UnitTests/Filters/MinimumRoleAuthorizeAttributeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
namespace Linker.WebApi.UnitTests.Filters; | ||
|
||
using Xunit; | ||
using Linker.Core.Models; | ||
|
||
public sealed class MinimumRoleAuthorizeAttributeTests | ||
{ | ||
private readonly MinimumRoleAuthorizeAttributeSteps steps = new(); | ||
|
||
[Theory] | ||
[InlineData(Role.Owner, Role.Owner)] | ||
[InlineData(Role.Administrator, Role.Owner)] | ||
[InlineData(Role.Administrator, Role.Administrator)] | ||
[InlineData(Role.Moderator, Role.Owner)] | ||
[InlineData(Role.Moderator, Role.Administrator)] | ||
[InlineData(Role.Moderator, Role.Moderator)] | ||
[InlineData(Role.User, Role.Owner)] | ||
[InlineData(Role.User, Role.Administrator)] | ||
[InlineData(Role.User, Role.Moderator)] | ||
[InlineData(Role.User, Role.User)] | ||
[InlineData(Role.ReadOnlyUser, Role.Owner)] | ||
[InlineData(Role.ReadOnlyUser, Role.Administrator)] | ||
[InlineData(Role.ReadOnlyUser, Role.Moderator)] | ||
[InlineData(Role.ReadOnlyUser, Role.User)] | ||
[InlineData(Role.ReadOnlyUser, Role.ReadOnlyUser)] | ||
[InlineData(Role.Guest, Role.Owner)] | ||
[InlineData(Role.Guest, Role.Administrator)] | ||
[InlineData(Role.Guest, Role.Moderator)] | ||
[InlineData(Role.Guest, Role.User)] | ||
[InlineData(Role.Guest, Role.ReadOnlyUser)] | ||
[InlineData(Role.Guest, Role.Guest)] | ||
public void OnAuthorize_MetRequiredRoles_ReturnsSuccess(Role required, Role current) | ||
{ | ||
this.steps | ||
.GivenCurrentUserHasRole(current) | ||
.GivenMinimumRoleRequiredIs(required) | ||
.WhenIAuthorize() | ||
.ThenIExpectToBeAuthorized(); | ||
} | ||
|
||
[Theory] | ||
[InlineData(Role.Owner, Role.Administrator)] | ||
[InlineData(Role.Owner, Role.Moderator)] | ||
[InlineData(Role.Owner, Role.User)] | ||
[InlineData(Role.Owner, Role.ReadOnlyUser)] | ||
[InlineData(Role.Owner, Role.Guest)] | ||
[InlineData(Role.Administrator, Role.Moderator)] | ||
[InlineData(Role.Administrator, Role.User)] | ||
[InlineData(Role.Administrator, Role.ReadOnlyUser)] | ||
[InlineData(Role.Administrator, Role.Guest)] | ||
[InlineData(Role.Moderator, Role.ReadOnlyUser)] | ||
[InlineData(Role.Moderator, Role.User)] | ||
[InlineData(Role.Moderator, Role.Guest)] | ||
[InlineData(Role.User, Role.ReadOnlyUser)] | ||
[InlineData(Role.User, Role.Guest)] | ||
[InlineData(Role.ReadOnlyUser, Role.Guest)] | ||
public void OnAuthorize_FailedRequiredRoles_ReturnsUnauthorized(Role required, Role current) | ||
{ | ||
this.steps | ||
.GivenCurrentUserHasRole(current) | ||
.GivenMinimumRoleRequiredIs(required) | ||
.WhenIAuthorize() | ||
.ThenIExpectToBeUnauthorized(); | ||
} | ||
|
||
[Fact] | ||
public void OnAuthorize_InvalidRoleProvided_ReturnsUnauthorized() | ||
{ | ||
this.steps | ||
.GivenCurrentUserHasRole("invalid role") | ||
.GivenMinimumRoleRequiredIs(Role.Guest) | ||
.WhenIAuthorize() | ||
.ThenIExpectToBeUnauthorized(); | ||
} | ||
|
||
[Fact] | ||
public void OnAuthorize_NoRoleProvided_ReturnsUnauthorized() | ||
{ | ||
this.steps | ||
.GivenCurrentUserHasNoRole() | ||
.GivenMinimumRoleRequiredIs(Role.Guest) | ||
.WhenIAuthorize() | ||
.ThenIExpectToBeUnauthorized(); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/Linker.WebApi/Filters/MinimumRoleAuthorizeAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
namespace Linker.WebApi.Filters; | ||
|
||
using Linker.Core.Models; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
using System.Security.Claims; | ||
|
||
/// <summary> | ||
/// Authorize the minimum allowed role. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] | ||
public sealed class MinimumRoleAuthorizeAttribute : Attribute, IAuthorizationFilter | ||
{ | ||
private readonly Role minimumRole; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MinimumRoleAuthorizeAttribute"/> class. | ||
/// </summary> | ||
/// <param name="minimumRole">The minimum role required.</param> | ||
public MinimumRoleAuthorizeAttribute(Role minimumRole) | ||
{ | ||
this.minimumRole = minimumRole; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void OnAuthorization(AuthorizationFilterContext context) | ||
{ | ||
var stringRole = context.HttpContext.User.FindFirstValue(ClaimTypes.Role); | ||
|
||
if (stringRole is not null | ||
&& Enum.TryParse(typeof(Role), stringRole, out var currentRole) | ||
&& (Role)currentRole <= this.minimumRole) | ||
{ | ||
return; | ||
} | ||
|
||
context.Result = new UnauthorizedResult(); | ||
} | ||
} |