Skip to content

Commit

Permalink
Added Module extension RequiresRoles(string[] roles) and ... (NancyFx…
Browse files Browse the repository at this point in the history
…#2982)

Added Module Security extensions RequiresRoles(string[] roles) and RequiresAnyRole(string[] roles)

Co-authored-by: Peter Schueffler <peter.schueffler@paige.ai>
Co-authored-by: Kristian Hellang <kristian@hellang.com>
  • Loading branch information
3 people committed Sep 16, 2019
1 parent 680823d commit 11ac0fe
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 1 deletion.
22 changes: 22 additions & 0 deletions src/Nancy/Security/ClaimsPrincipalExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,27 @@ public static bool HasValidClaims(this ClaimsPrincipal user, Func<IEnumerable<Cl
&& user.Claims != null
&& isValid(user.Claims);
}

/// <summary>
/// Tests if the user is in all of the required roles.
/// </summary>
/// <param name="user">User to be verified</param>
/// <param name="requiredRoles">Roles the user needs to be in</param>
/// <returns>True if the user is in all of the required roles, false otherwise</returns>
public static bool IsInRoles(this ClaimsPrincipal user, params string[] requiredRoles)
{
return user != null && requiredRoles != null && requiredRoles.All(user.IsInRole);
}

/// <summary>
/// Tests if the user is in at least one of the required roles.
/// </summary>
/// <param name="user">User to be verified</param>
/// <param name="requiredRoles">Roles the user needs to be in at least one of</param>
/// <returns>True if the user is in at least one of the required roles, false otherwise</returns>
public static bool IsInAnyRole(this ClaimsPrincipal user, params string[] requiredRoles)
{
return user != null && requiredRoles != null && requiredRoles.Any(user.IsInRole);
}
}
}
22 changes: 22 additions & 0 deletions src/Nancy/Security/ModuleSecurity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ public static void RequiresAnyClaim(this INancyModule module, params Predicate<C
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAnyClaim(requiredClaims), "Requires Any Claim");
}

/// <summary>
/// This module requires authentication and certain roles to be present.
/// </summary>
/// <param name="module">Module to enable</param>
/// <param name="requiredRoles">Role(s) required</param>
public static void RequiresRoles(this INancyModule module, params string[] requiredRoles)
{
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
module.AddBeforeHookOrExecute(SecurityHooks.RequiresRoles(requiredRoles), "Requires Roles");
}

/// <summary>
/// This module requires authentication and any one of certain roles to be present.
/// </summary>
/// <param name="module">Module to enable</param>
/// <param name="requiredRoles">Role(s) at least one of which is required</param>
public static void RequiresAnyRole(this INancyModule module, params string[] requiredRoles)
{
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAnyRole(requiredRoles), "Requires Any Role");
}

/// <summary>
/// This module requires https.
Expand Down
30 changes: 29 additions & 1 deletion src/Nancy/Security/SecurityHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,34 @@ public static Func<NancyContext, Response> RequiresValidatedClaims(Func<IEnumera
return ForbiddenIfNot(ctx => ctx.CurrentUser.HasValidClaims(isValid));
}

/// <summary>
/// Creates a hook to be used in a pipeline before a route handler to ensure
/// that the request was made by an authenticated user being in all of
/// the required roles.
/// </summary>
/// <param name="roles">Roles the authenticated user needs to be in</param>
/// <returns>Hook that returns an Unauthorized response if the user is not
/// authenticated or is not in all of the required roles, null
/// otherwise</returns>
public static Func<NancyContext, Response> RequiresRoles(params string[] roles)
{
return ForbiddenIfNot(ctx => ctx.CurrentUser.IsInRoles(roles));
}

/// <summary>
/// Creates a hook to be used in a pipeline before a route handler to ensure
/// that the request was made by an authenticated user being in at least one of
/// the required roles.
/// </summary>
/// <param name="roles">Roles the authenticated user needs to be in at least one of</param>
/// <returns>Hook that returns an Unauthorized response if the user is not
/// authenticated or is not in at least one of the required roles, null
/// otherwise</returns>
public static Func<NancyContext, Response> RequiresAnyRole(params string[] roles)
{
return ForbiddenIfNot(ctx => ctx.CurrentUser.IsInAnyRole(roles));
}

/// <summary>
/// Creates a hook to be used in a pipeline before a route handler to ensure that
/// the request satisfies a specific test.
Expand Down Expand Up @@ -140,4 +168,4 @@ public static Func<NancyContext, Response> RequiresHttps(bool redirect, int? htt
};
}
}
}
}
174 changes: 174 additions & 0 deletions test/Nancy.Tests/Unit/Security/ClaimsPrincipalExtensionsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,180 @@ public void Should_call_validation_with_users_claims()
validatedClaims.ShouldEqualSequence(user.Claims);
}

[Fact]
public void Should_return_false_for_required_role_if_the_roles_are_null()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake");
var requiredRole = "not-present-role";

// When
var result = user.IsInRole(requiredRole);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_false_for_required_role_if_the_user_does_not_have_role()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake", new Claim(ClaimTypes.Role, string.Empty));
var requiredRole = "not-present-role";

// When
var result = user.IsInRole(requiredRole);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_true_for_required_role_if_the_user_does_have_role()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake", new Claim(ClaimTypes.Role, "present-role"));
var requiredRole = "present-role";

// When
var result = user.IsInRole(requiredRole);

// Then
result.ShouldBeTrue();
}

[Fact]
public void Should_return_false_for_required_roles_if_the_user_is_null()
{
// Given
ClaimsPrincipal user = null;
var requiredRoles = new string[] { "not-present-role1", "not-present-role2" };

// When
var result = user.IsInRoles(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_false_for_required_roles_if_the_roles_are_null()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake");
string[] requiredRoles = null;

// When
var result = user.IsInRoles(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_false_for_required_roles_if_the_user_does_not_have_all_roles()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake",
new Claim(ClaimTypes.Role, "present-role1"),
new Claim(ClaimTypes.Role, "present-role2"),
new Claim(ClaimTypes.Role, "present-role3"));
var requiredRoles = new string[]
{
"present-role1",
"not-present-role1"
};

// When
var result = user.IsInRoles(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_true_for_required_roles_if_the_user_does_have_all_roles()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake",
new Claim(ClaimTypes.Role, "present-role1"),
new Claim(ClaimTypes.Role, "present-role2"),
new Claim(ClaimTypes.Role, "present-role3"));
var requiredRoles = new string[]
{
"present-role1",
"present-role2"
};

// When
var result = user.IsInRoles(requiredRoles);

// Then
result.ShouldBeTrue();
}

[Fact]
public void Should_return_false_for_any_required_role_if_the_user_is_null()
{
// Given
ClaimsPrincipal user = null;
var requiredRoles = new string[] { "not-present-role1", "not-present-role2" };

// When
var result = user.IsInAnyRole(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_false_for_any_required_role_if_the_roles_are_null()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake");
string[] requiredRoles = null;

// When
var result = user.IsInAnyRole(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_false_for_any_required_role_if_the_user_does_not_have_any_role()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake",
new Claim(ClaimTypes.Role, "present-role1"),
new Claim(ClaimTypes.Role, "present-role2"),
new Claim(ClaimTypes.Role, "present-role3"));
var requiredRoles = new string[] { "not-present-role1", "not-present-role2" };

// When
var result = user.IsInAnyRole(requiredRoles);

// Then
result.ShouldBeFalse();
}

[Fact]
public void Should_return_true_for_any_required_role_if_the_user_does_have_any_of_role()
{
// Given
ClaimsPrincipal user = GetFakeUser("Fake",
new Claim(ClaimTypes.Role, "present-role1"),
new Claim(ClaimTypes.Role, "present-role2"),
new Claim(ClaimTypes.Role, "present-role3"));
var requiredRoles = new string[] { "present-role1", "not-present-role1" };

// When
var result = user.IsInAnyRole(requiredRoles);

// Then
result.ShouldBeTrue();
}

private static ClaimsPrincipal GetFakeUser(string userName, params Claim[] claims)
{
var claimsList = (claims ?? Enumerable.Empty<Claim>()).ToList();
Expand Down

0 comments on commit 11ac0fe

Please sign in to comment.