+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.Text.RegularExpressions;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.Authorisation
{
@ -15,8 +22,11 @@ namespace Ocelot.Authorisation
_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)
{
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
@ -27,12 +37,50 @@ namespace Ocelot.Authorisation
}
if (values.Data != null)
{
var authorised = values.Data.Contains(required.Value);
if (!authorised)
{
// dynamic claim
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
if (match.Success)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
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);
if (!authorised)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorisedError(
$"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}"));
}
}
}
else

View File

@ -1,5 +1,9 @@
using System.Security.Claims;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.Authorisation
{
@ -7,6 +11,10 @@ namespace Ocelot.Authorisation
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

@ -57,8 +57,8 @@
if (IsAuthorisedRoute(context.DownstreamReRoute))
{
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)
{

View File

@ -69,15 +69,21 @@ namespace Ocelot.UnitTests.Authorization
private void GivenTheAuthServiceReturns(Response<bool> expected)
{
_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);
}
private void ThenTheAuthServiceIsCalledCorrectly()
{
_authService
.Verify(x => x.Authorise(It.IsAny<ClaimsPrincipal>(),
It.IsAny<Dictionary<string, string>>()), Times.Once);
.Verify(x => x.Authorise(
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.Security.Claims;
using Ocelot.Authorisation;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using Ocelot.Values;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
@ -15,6 +19,7 @@ namespace Ocelot.UnitTests.Authorization
private readonly ClaimsAuthoriser _claimsAuthoriser;
private ClaimsPrincipal _claimsPrincipal;
private Dictionary<string, string> _requirement;
private List<PlaceholderNameAndValue> _urlPathPlaceholderNameAndValues;
private Response<bool> _result;
public ClaimsAuthoriserTests()
@ -38,6 +43,46 @@ namespace Ocelot.UnitTests.Authorization
.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]
public void should_authorise_user_multiple_claims_of_same_type()
{
@ -78,9 +123,14 @@ namespace Ocelot.UnitTests.Authorization
_requirement = requirement;
}
private void GivenAPlaceHolderNameAndValueList(List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{
_urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues;
}
private void WhenICallTheAuthoriser()
{
_result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement);
_result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues);
}
private void ThenTheUserIsAuthorised()