+dynamic claim variables (#855)

incl. tests
This commit is contained in:
Michel Bretschneider 2019-04-15 10:51:34 +02:00 committed by Thiago Loureiro
parent 340d0de233
commit 639011bc62
5 changed files with 127 additions and 15 deletions

View File

@ -1,6 +1,13 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Text.RegularExpressions;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.Authorisation namespace Ocelot.Authorisation
{ {
@ -15,8 +22,11 @@ namespace Ocelot.Authorisation
_claimsParser = claimsParser; _claimsParser = claimsParser;
} }
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Dictionary<string, string> routeClaimsRequirement) public Response<bool> Authorise(
{ ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
){
foreach (var required in routeClaimsRequirement) foreach (var required in routeClaimsRequirement)
{ {
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
@ -28,6 +38,43 @@ namespace Ocelot.Authorisation
if (values.Data != null) if (values.Data != null)
{ {
// dynamic claim
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
if (match.Success)
{
var variableName = match.Captures[0].Value;
var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray();
if (matchingPlaceholders.Length == 1)
{
// match
var actualValue = matchingPlaceholders[0].Value;
var authorised = values.Data.Contains(actualValue);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}"));
}
}
else
{
// config error
if (matchingPlaceholders.Length == 0)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p=>p.Name))}"));
}
else
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p=>p.Name.Equals(variableName)).Select(p => p.Value))}"));
}
}
}
else
{
// static claim
var authorised = values.Data.Contains(required.Value); var authorised = values.Data.Contains(required.Value);
if (!authorised) if (!authorised)
{ {
@ -35,6 +82,7 @@ namespace Ocelot.Authorisation
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
} }
} }
}
else else
{ {
return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")); return new ErrorResponse<bool>(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}"));

View File

@ -1,5 +1,9 @@
using System.Security.Claims; using System.Security.Claims;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.Authorisation namespace Ocelot.Authorisation
{ {
@ -7,6 +11,10 @@ namespace Ocelot.Authorisation
public interface IClaimsAuthoriser public interface IClaimsAuthoriser
{ {
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Dictionary<string, string> routeClaimsRequirement); Response<bool> Authorise(
ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
);
} }
} }

View File

@ -58,7 +58,7 @@
{ {
Logger.LogInformation("route is authorised"); Logger.LogInformation("route is authorised");
var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues);
if (authorised.IsError) if (authorised.IsError)
{ {

View File

@ -69,15 +69,21 @@ namespace Ocelot.UnitTests.Authorization
private void GivenTheAuthServiceReturns(Response<bool> expected) private void GivenTheAuthServiceReturns(Response<bool> expected)
{ {
_authService _authService
.Setup(x => x.Authorise(It.IsAny<ClaimsPrincipal>(), It.IsAny<Dictionary<string, string>>())) .Setup(x => x.Authorise(
It.IsAny<ClaimsPrincipal>(),
It.IsAny<Dictionary<string, string>>(),
It.IsAny<List<PlaceholderNameAndValue>>()))
.Returns(expected); .Returns(expected);
} }
private void ThenTheAuthServiceIsCalledCorrectly() private void ThenTheAuthServiceIsCalledCorrectly()
{ {
_authService _authService
.Verify(x => x.Authorise(It.IsAny<ClaimsPrincipal>(), .Verify(x => x.Authorise(
It.IsAny<Dictionary<string, string>>()), Times.Once); It.IsAny<ClaimsPrincipal>(),
It.IsAny<Dictionary<string, string>>(),
It.IsAny<List<PlaceholderNameAndValue>>())
, Times.Once);
} }
} }
} }

View File

@ -1,7 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using Ocelot.Authorisation; using Ocelot.Authorisation;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Values;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -15,6 +19,7 @@ namespace Ocelot.UnitTests.Authorization
private readonly ClaimsAuthoriser _claimsAuthoriser; private readonly ClaimsAuthoriser _claimsAuthoriser;
private ClaimsPrincipal _claimsPrincipal; private ClaimsPrincipal _claimsPrincipal;
private Dictionary<string, string> _requirement; private Dictionary<string, string> _requirement;
private List<PlaceholderNameAndValue> _urlPathPlaceholderNameAndValues;
private Response<bool> _result; private Response<bool> _result;
public ClaimsAuthoriserTests() public ClaimsAuthoriserTests()
@ -38,6 +43,46 @@ namespace Ocelot.UnitTests.Authorization
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_authorize_dynamic_user()
{
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim("userid", "14"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
{
{"userid", "{userId}"}
}))
.And(x => x.GivenAPlaceHolderNameAndValueList(new List<PlaceholderNameAndValue>
{
new PlaceholderNameAndValue("{userId}", "14")
}))
.When(x => x.WhenICallTheAuthoriser())
.Then(x => x.ThenTheUserIsAuthorised())
.BDDfy();
}
[Fact]
public void should_not_authorize_dynamic_user()
{
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim("userid", "15"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
{
{"userid", "{userId}"}
}))
.And(x => x.GivenAPlaceHolderNameAndValueList(new List<PlaceholderNameAndValue>
{
new PlaceholderNameAndValue("{userId}", "14")
}))
.When(x => x.WhenICallTheAuthoriser())
.Then(x => x.ThenTheUserIsntAuthorised())
.BDDfy();
}
[Fact] [Fact]
public void should_authorise_user_multiple_claims_of_same_type() public void should_authorise_user_multiple_claims_of_same_type()
{ {
@ -78,9 +123,14 @@ namespace Ocelot.UnitTests.Authorization
_requirement = requirement; _requirement = requirement;
} }
private void GivenAPlaceHolderNameAndValueList(List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{
_urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues;
}
private void WhenICallTheAuthoriser() private void WhenICallTheAuthoriser()
{ {
_result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues);
} }
private void ThenTheUserIsAuthorised() private void ThenTheUserIsAuthorised()