From 639011bc62a21dfec92ee59b6c65186a2313dead Mon Sep 17 00:00:00 2001 From: Michel Bretschneider Date: Mon, 15 Apr 2019 10:51:34 +0200 Subject: [PATCH] +dynamic claim variables (#855) incl. tests --- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 64 ++++++++++++++++--- src/Ocelot/Authorisation/IClaimsAuthoriser.cs | 10 ++- .../Middleware/AuthorisationMiddleware.cs | 4 +- .../AuthorisationMiddlewareTests.cs | 12 +++- .../Authorization/ClaimsAuthoriserTests.cs | 52 ++++++++++++++- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 8fd910c8..75a166f2 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -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 Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) - { + public Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List 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, @"^{(?.+)}$"); + if (match.Success) { - return new ErrorResponse(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(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(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(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(new ClaimValueNotAuthorisedError( + $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + } } } else diff --git a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs index 4abd799e..9f9ce3f8 100644 --- a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs @@ -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 Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement); + Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues + ); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f3e4e21d..8e906d5c 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -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) { diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index a2c2fadd..eaa2441f 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -69,15 +69,21 @@ namespace Ocelot.UnitTests.Authorization private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) + .Setup(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>())) .Returns(expected); } private void ThenTheAuthServiceIsCalledCorrectly() { _authService - .Verify(x => x.Authorise(It.IsAny(), - It.IsAny>()), Times.Once); + .Verify(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>()) + , Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index e2990864..11fb341d 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -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 _requirement; + private List _urlPathPlaceholderNameAndValues; private Response _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 + { + new Claim("userid", "14"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + 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 + { + new Claim("userid", "15"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + 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 urlPathPlaceholderNameAndValues) + { + _urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues; + } + private void WhenICallTheAuthoriser() { - _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); + _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues); } private void ThenTheUserIsAuthorised()