mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 16:10:50 +08:00 
			
		
		
		
	
				
					committed by
					
						
						Thiago Loureiro
					
				
			
			
				
	
			
			
			
						parent
						
							340d0de233
						
					
				
				
					commit
					639011bc62
				
			@@ -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}"));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user