mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-04 22:30:50 +08:00 
			
		
		
		
	Added ability to strip claims and forward to downstream service as headers
This commit is contained in:
		
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							@@ -38,12 +38,25 @@ Priorities
 | 
			
		||||
TBC really but example configuration for a route below.
 | 
			
		||||
 | 
			
		||||
ReRoutes:
 | 
			
		||||
- DownstreamTemplate: http://localhost:51876/
 | 
			
		||||
# the url we are forwarding the request to
 | 
			
		||||
- DownstreamTemplate: http://localhost:52876/
 | 
			
		||||
# the path we are listening on for this re route
 | 
			
		||||
  UpstreamTemplate: /
 | 
			
		||||
  UpstreamHttpMethod: Post
 | 
			
		||||
# the method we are listening for on this re route
 | 
			
		||||
  UpstreamHttpMethod: Get
 | 
			
		||||
# only support identity server at the moment
 | 
			
		||||
  AuthenticationOptions:
 | 
			
		||||
    Provider: IdentityServer
 | 
			
		||||
    ProviderRootUrl: http://localhost:51888
 | 
			
		||||
    ProviderRootUrl: http://localhost:52888
 | 
			
		||||
    ScopeName: api
 | 
			
		||||
    AdditionalScopes: []
 | 
			
		||||
    AdditionalScopes:
 | 
			
		||||
    - openid
 | 
			
		||||
    - offline_access
 | 
			
		||||
#require if using reference tokens
 | 
			
		||||
    ScopeSecret: secret
 | 
			
		||||
# WARNING - will overwrite any headers already in the request with these values
 | 
			
		||||
  AddHeadersToRequest:
 | 
			
		||||
    CustomerId: Claims[CustomerId] > value
 | 
			
		||||
    LocationId: Claims[LocationId] > value
 | 
			
		||||
    UserType: Claims[sub] > value[0] > |
 | 
			
		||||
    UserId: Claims[sub] > value[1] > |
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
namespace Ocelot.Library.Builder
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.Builder
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
    using Configuration;
 | 
			
		||||
@@ -16,6 +18,7 @@
 | 
			
		||||
        private List<string> _additionalScopes;
 | 
			
		||||
        private bool _requireHttps;
 | 
			
		||||
        private string _scopeSecret;
 | 
			
		||||
        private List<ConfigurationHeaderExtractorProperties> _configHeaderExtractorProperties;
 | 
			
		||||
 | 
			
		||||
        public ReRouteBuilder()
 | 
			
		||||
        {
 | 
			
		||||
@@ -86,9 +89,15 @@
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReRouteBuilder WithConfigurationHeaderExtractorProperties(List<ConfigurationHeaderExtractorProperties> input)
 | 
			
		||||
        {
 | 
			
		||||
            _configHeaderExtractorProperties = input;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ReRoute Build()
 | 
			
		||||
        {
 | 
			
		||||
            return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret));
 | 
			
		||||
            return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,8 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.Configuration
 | 
			
		||||
{
 | 
			
		||||
    using System.Collections.Generic;
 | 
			
		||||
@@ -11,11 +16,18 @@ namespace Ocelot.Library.Configuration
 | 
			
		||||
        private readonly List<ReRoute> _reRoutes;
 | 
			
		||||
        private const string RegExMatchEverything = ".*";
 | 
			
		||||
        private const string RegExMatchEndString = "$";
 | 
			
		||||
        private readonly IConfigurationHeaderExtrator _configurationHeaderExtrator;
 | 
			
		||||
        private readonly ILogger<OcelotConfiguration> _logger;
 | 
			
		||||
 | 
			
		||||
        public OcelotConfiguration(IOptions<YamlConfiguration> options, IConfigurationValidator configurationValidator)
 | 
			
		||||
        public OcelotConfiguration(IOptions<YamlConfiguration> options, 
 | 
			
		||||
            IConfigurationValidator configurationValidator, 
 | 
			
		||||
            IConfigurationHeaderExtrator configurationHeaderExtrator, 
 | 
			
		||||
            ILogger<OcelotConfiguration> logger)
 | 
			
		||||
        {
 | 
			
		||||
            _options = options;
 | 
			
		||||
            _configurationValidator = configurationValidator;
 | 
			
		||||
            _configurationHeaderExtrator = configurationHeaderExtrator;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
            _reRoutes = new List<ReRoute>();
 | 
			
		||||
            SetUpConfiguration();
 | 
			
		||||
        }
 | 
			
		||||
@@ -43,7 +55,7 @@ namespace Ocelot.Library.Configuration
 | 
			
		||||
 | 
			
		||||
            var placeholders = new List<string>();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < upstreamTemplate.Length; i++)
 | 
			
		||||
            for (var i = 0; i < upstreamTemplate.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (IsPlaceHolder(upstreamTemplate, i))
 | 
			
		||||
                {
 | 
			
		||||
@@ -70,17 +82,41 @@ namespace Ocelot.Library.Configuration
 | 
			
		||||
                    reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes,
 | 
			
		||||
                    reRoute.AuthenticationOptions.ScopeSecret);
 | 
			
		||||
 | 
			
		||||
                var configHeaders = GetHeadersToAddToRequest(reRoute);
 | 
			
		||||
 | 
			
		||||
                _reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate,
 | 
			
		||||
                    reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute
 | 
			
		||||
                    reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, 
 | 
			
		||||
                    authOptionsForRoute, configHeaders
 | 
			
		||||
                    ));
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                _reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod,
 | 
			
		||||
                    upstreamTemplate, isAuthenticated, null));
 | 
			
		||||
                    upstreamTemplate, isAuthenticated, null, new List<ConfigurationHeaderExtractorProperties>()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private List<ConfigurationHeaderExtractorProperties> GetHeadersToAddToRequest(YamlReRoute reRoute)
 | 
			
		||||
        {
 | 
			
		||||
            var configHeaders = new List<ConfigurationHeaderExtractorProperties>();
 | 
			
		||||
 | 
			
		||||
            foreach (var add in reRoute.AddHeadersToRequest)
 | 
			
		||||
            {
 | 
			
		||||
                var configurationHeader = _configurationHeaderExtrator.Extract(add.Key, add.Value);
 | 
			
		||||
 | 
			
		||||
                if (configurationHeader.IsError)
 | 
			
		||||
                {
 | 
			
		||||
                    _logger.LogCritical(new EventId(1, "Application Failed to start"),
 | 
			
		||||
                        $"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect");
 | 
			
		||||
 | 
			
		||||
                    throw new Exception(configurationHeader.Errors[0].Message);
 | 
			
		||||
                }
 | 
			
		||||
                configHeaders.Add(configurationHeader.Data);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return configHeaders;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsPlaceHolder(string upstreamTemplate, int i)
 | 
			
		||||
        {
 | 
			
		||||
            return upstreamTemplate[i] == '{';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
namespace Ocelot.Library.Configuration
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.Configuration
 | 
			
		||||
{
 | 
			
		||||
    public class ReRoute
 | 
			
		||||
    {
 | 
			
		||||
        public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions)
 | 
			
		||||
        public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List<ConfigurationHeaderExtractorProperties> configurationHeaderExtractorProperties)
 | 
			
		||||
        {
 | 
			
		||||
            DownstreamTemplate = downstreamTemplate;
 | 
			
		||||
            UpstreamTemplate = upstreamTemplate;
 | 
			
		||||
@@ -10,6 +13,8 @@
 | 
			
		||||
            UpstreamTemplatePattern = upstreamTemplatePattern;
 | 
			
		||||
            IsAuthenticated = isAuthenticated;
 | 
			
		||||
            AuthenticationOptions = authenticationOptions;
 | 
			
		||||
            ConfigurationHeaderExtractorProperties = configurationHeaderExtractorProperties 
 | 
			
		||||
                ?? new List<ConfigurationHeaderExtractorProperties>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string DownstreamTemplate { get; private set; }
 | 
			
		||||
@@ -18,5 +23,6 @@
 | 
			
		||||
        public string UpstreamHttpMethod { get; private set; }
 | 
			
		||||
        public bool IsAuthenticated { get; private set; }
 | 
			
		||||
        public AuthenticationOptions AuthenticationOptions { get; private set; }
 | 
			
		||||
        public List<ConfigurationHeaderExtractorProperties> ConfigurationHeaderExtractorProperties { get; private set; } 
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,9 @@
 | 
			
		||||
            services.Configure<YamlConfiguration>(configurationRoot);
 | 
			
		||||
 | 
			
		||||
            // Add framework services.
 | 
			
		||||
            services.AddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
 | 
			
		||||
            services.AddSingleton<IClaimsParser, ClaimsParser>();
 | 
			
		||||
            services.AddSingleton<IConfigurationHeaderExtrator, ConfigurationHeaderExtrator>();
 | 
			
		||||
            services.AddSingleton<IConfigurationValidator, ConfigurationValidator>();
 | 
			
		||||
            services.AddSingleton<IOcelotConfiguration, OcelotConfiguration>();
 | 
			
		||||
            services.AddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,10 @@
 | 
			
		||||
        CannotFindDataError,
 | 
			
		||||
        UnableToCompleteRequestError,
 | 
			
		||||
        UnableToCreateAuthenticationHandlerError,
 | 
			
		||||
        UnsupportedAuthenticationProviderError
 | 
			
		||||
        UnsupportedAuthenticationProviderError,
 | 
			
		||||
        CannotFindClaimError,
 | 
			
		||||
        ParsingConfigurationHeaderError,
 | 
			
		||||
        NoInstructionsError,
 | 
			
		||||
        InstructionNotForClaimsError
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
namespace Ocelot.Library.Middleware
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Repository;
 | 
			
		||||
 | 
			
		||||
    public class ClaimsParserMiddleware : OcelotMiddleware
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
 | 
			
		||||
        public ClaimsParserMiddleware(RequestDelegate next, IScopedRequestDataRepository scopedRequestDataRepository) 
 | 
			
		||||
            : base(scopedRequestDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            
 | 
			
		||||
            await _next.Invoke(context);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.Extensions.Primitives;
 | 
			
		||||
using Ocelot.Library.DownstreamRouteFinder;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.Middleware
 | 
			
		||||
{
 | 
			
		||||
    using System.Threading.Tasks;
 | 
			
		||||
    using Microsoft.AspNetCore.Http;
 | 
			
		||||
    using Repository;
 | 
			
		||||
 | 
			
		||||
    public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware
 | 
			
		||||
    {
 | 
			
		||||
        private readonly RequestDelegate _next;
 | 
			
		||||
        private readonly IAddHeadersToRequest _addHeadersToRequest;
 | 
			
		||||
        private readonly IScopedRequestDataRepository _scopedRequestDataRepository;
 | 
			
		||||
 | 
			
		||||
        public HttpRequestHeadersBuilderMiddleware(RequestDelegate next, 
 | 
			
		||||
            IScopedRequestDataRepository scopedRequestDataRepository,
 | 
			
		||||
            IAddHeadersToRequest addHeadersToRequest) 
 | 
			
		||||
            : base(scopedRequestDataRepository)
 | 
			
		||||
        {
 | 
			
		||||
            _next = next;
 | 
			
		||||
            _addHeadersToRequest = addHeadersToRequest;
 | 
			
		||||
            _scopedRequestDataRepository = scopedRequestDataRepository;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Invoke(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            var downstreamRoute = _scopedRequestDataRepository.Get<DownstreamRoute>("DownstreamRoute");
 | 
			
		||||
 | 
			
		||||
            if (downstreamRoute.Data.ReRoute.ConfigurationHeaderExtractorProperties.Any())
 | 
			
		||||
            {
 | 
			
		||||
                _addHeadersToRequest.SetHeadersOnContext(downstreamRoute.Data.ReRoute.ConfigurationHeaderExtractorProperties, context);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            await _next.Invoke(context);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
namespace Ocelot.Library.Middleware
 | 
			
		||||
{
 | 
			
		||||
    using Microsoft.AspNetCore.Builder;
 | 
			
		||||
 | 
			
		||||
    public static class HttpRequestHeadersBuilderMiddlewareExtensions
 | 
			
		||||
    {
 | 
			
		||||
        public static IApplicationBuilder UseHttpRequestHeadersBuilderMiddleware(this IApplicationBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            return builder.UseMiddleware<HttpRequestHeadersBuilderMiddleware>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,8 @@
 | 
			
		||||
 | 
			
		||||
            builder.UseAuthenticationMiddleware();
 | 
			
		||||
 | 
			
		||||
            builder.UseHttpRequestHeadersBuilderMiddleware();
 | 
			
		||||
 | 
			
		||||
            builder.UseDownstreamUrlCreatorMiddleware();
 | 
			
		||||
 | 
			
		||||
            builder.UseHttpRequestBuilderMiddleware();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/Ocelot.Library/RequestBuilder/AddHeadersToRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Ocelot.Library/RequestBuilder/AddHeadersToRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.Extensions.Primitives;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class AddHeadersToRequest : IAddHeadersToRequest
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IClaimsParser _claimsParser;
 | 
			
		||||
 | 
			
		||||
        public AddHeadersToRequest(IClaimsParser claimsParser)
 | 
			
		||||
        {
 | 
			
		||||
            _claimsParser = claimsParser;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Response SetHeadersOnContext(List<ConfigurationHeaderExtractorProperties> configurationHeaderExtractorProperties, HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var config in configurationHeaderExtractorProperties)
 | 
			
		||||
            {
 | 
			
		||||
                var value = _claimsParser.GetValue(context.User.Claims, config.ClaimKey, config.Delimiter, config.Index);
 | 
			
		||||
 | 
			
		||||
                if (value.IsError)
 | 
			
		||||
                {
 | 
			
		||||
                    return new ErrorResponse(value.Errors);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var exists = context.Request.Headers.FirstOrDefault(x => x.Key == config.HeaderKey);
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrEmpty(exists.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    context.Request.Headers.Remove(exists);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                context.Request.Headers.Add(config.HeaderKey, new StringValues(value.Data));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new OkResponse();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/CannotFindClaimError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/CannotFindClaimError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class CannotFindClaimError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public CannotFindClaimError(string message) 
 | 
			
		||||
            : base(message, OcelotErrorCode.CannotFindClaimError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/Ocelot.Library/RequestBuilder/ClaimsParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/Ocelot.Library/RequestBuilder/ClaimsParser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ClaimsParser : IClaimsParser
 | 
			
		||||
    {
 | 
			
		||||
        public Response<string> GetValue(IEnumerable<Claim> claims, string key, string delimiter, int index)
 | 
			
		||||
        {
 | 
			
		||||
            var claimResponse = GetValue(claims, key);
 | 
			
		||||
 | 
			
		||||
            if (claimResponse.IsError)
 | 
			
		||||
            {
 | 
			
		||||
                return claimResponse;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (string.IsNullOrEmpty(delimiter))
 | 
			
		||||
            {
 | 
			
		||||
                return claimResponse;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var splits = claimResponse.Data.Split(delimiter.ToCharArray());
 | 
			
		||||
 | 
			
		||||
            if (splits.Length < index || index < 0)
 | 
			
		||||
            {
 | 
			
		||||
                return new ErrorResponse<string>(new List<Error>
 | 
			
		||||
                {
 | 
			
		||||
                    new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}")
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var value = splits[index];
 | 
			
		||||
 | 
			
		||||
            return new OkResponse<string>(value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Response<string> GetValue(IEnumerable<Claim> claims, string key)
 | 
			
		||||
        {
 | 
			
		||||
            var claim = claims.FirstOrDefault(c => c.Type == key);
 | 
			
		||||
 | 
			
		||||
            if (claim != null)
 | 
			
		||||
            {
 | 
			
		||||
                return new OkResponse<string>(claim.Value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new ErrorResponse<string>(new List<Error>
 | 
			
		||||
            {
 | 
			
		||||
                new CannotFindClaimError($"Cannot find claim for key: {key}")
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ConfigurationHeaderExtractorProperties
 | 
			
		||||
    {
 | 
			
		||||
        public ConfigurationHeaderExtractorProperties(string headerKey, string claimKey, string delimiter, int index)
 | 
			
		||||
        {
 | 
			
		||||
            ClaimKey = claimKey;
 | 
			
		||||
            Delimiter = delimiter;
 | 
			
		||||
            Index = index;
 | 
			
		||||
            HeaderKey = headerKey;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string HeaderKey { get; private set; }
 | 
			
		||||
        public string ClaimKey { get; private set; }
 | 
			
		||||
        public string Delimiter { get; private set; }
 | 
			
		||||
        public int Index { get; private set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								src/Ocelot.Library/RequestBuilder/HeaderExtrator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/Ocelot.Library/RequestBuilder/HeaderExtrator.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ConfigurationHeaderExtrator : IConfigurationHeaderExtrator
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Regex _claimRegex = new Regex("Claims\\[.*\\]");
 | 
			
		||||
        private readonly Regex _indexRegex = new Regex("value\\[.*\\]");
 | 
			
		||||
        private const string SplitToken = ">";
 | 
			
		||||
 | 
			
		||||
        public Response<ConfigurationHeaderExtractorProperties> Extract(string headerKey, string value)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var instructions = value.Split(SplitToken.ToCharArray());
 | 
			
		||||
 | 
			
		||||
                if (instructions.Length <= 1)
 | 
			
		||||
                {
 | 
			
		||||
                    return new ErrorResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                        new List<Error>
 | 
			
		||||
                    {
 | 
			
		||||
                        new NoInstructionsError(SplitToken)
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var claimMatch = _claimRegex.IsMatch(instructions[0]);
 | 
			
		||||
 | 
			
		||||
                if (!claimMatch)
 | 
			
		||||
                {
 | 
			
		||||
                    return new ErrorResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                        new List<Error>
 | 
			
		||||
                        {
 | 
			
		||||
                            new InstructionNotForClaimsError()
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var claimKey = GetIndexValue(instructions[0]);
 | 
			
		||||
                var index = 0;
 | 
			
		||||
                var delimiter = string.Empty;
 | 
			
		||||
 | 
			
		||||
                if (instructions.Length > 2 && _indexRegex.IsMatch(instructions[1]))
 | 
			
		||||
                {
 | 
			
		||||
                    index = int.Parse(GetIndexValue(instructions[1]));
 | 
			
		||||
                    delimiter = instructions[2].Trim();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return new OkResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                               new ConfigurationHeaderExtractorProperties(headerKey, claimKey, delimiter, index));
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception exception)
 | 
			
		||||
            {
 | 
			
		||||
                return new ErrorResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                    new List<Error>
 | 
			
		||||
                    {
 | 
			
		||||
                        new ParsingConfigurationHeaderError(exception)
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetIndexValue(string instruction)
 | 
			
		||||
        {
 | 
			
		||||
            var firstIndexer = instruction.IndexOf("[", StringComparison.Ordinal);
 | 
			
		||||
            var lastIndexer = instruction.IndexOf("]", StringComparison.Ordinal);
 | 
			
		||||
            var length = lastIndexer - firstIndexer;
 | 
			
		||||
            var claimKey = instruction.Substring(firstIndexer + 1, length - 1);
 | 
			
		||||
            return claimKey;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/IAddHeadersToRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/IAddHeadersToRequest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public interface IAddHeadersToRequest
 | 
			
		||||
    {
 | 
			
		||||
        Response SetHeadersOnContext(List<ConfigurationHeaderExtractorProperties> configurationHeaderExtractorProperties,
 | 
			
		||||
            HttpContext context);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/Ocelot.Library/RequestBuilder/IClaimsParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Ocelot.Library/RequestBuilder/IClaimsParser.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public interface IClaimsParser
 | 
			
		||||
    {
 | 
			
		||||
        Response<string> GetValue(IEnumerable<Claim> claims, string key, string delimiter, int index);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public interface IConfigurationHeaderExtrator
 | 
			
		||||
    {
 | 
			
		||||
        Response<ConfigurationHeaderExtractorProperties> Extract(string headerKey, string value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class InstructionNotForClaimsError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public InstructionNotForClaimsError() 
 | 
			
		||||
            : base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/NoInstructionsError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Ocelot.Library/RequestBuilder/NoInstructionsError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class NoInstructionsError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public NoInstructionsError(string splitToken) 
 | 
			
		||||
            : base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.Library.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ParsingConfigurationHeaderError : Error
 | 
			
		||||
    {
 | 
			
		||||
        public ParsingConfigurationHeaderError(Exception exception) 
 | 
			
		||||
            : base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,8 +11,6 @@
 | 
			
		||||
    public class HttpContextResponder : IHttpResponder
 | 
			
		||||
    {
 | 
			
		||||
        public async Task<HttpContext> CreateResponse(HttpContext context, HttpResponseMessage response)
 | 
			
		||||
        {
 | 
			
		||||
            if (response.IsSuccessStatusCode)
 | 
			
		||||
        {
 | 
			
		||||
            context.Response.OnStarting(x =>
 | 
			
		||||
            {
 | 
			
		||||
@@ -23,8 +21,6 @@
 | 
			
		||||
            await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
 | 
			
		||||
            return context;       
 | 
			
		||||
        }
 | 
			
		||||
            return context;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<HttpContext> CreateErrorResponse(HttpContext context, int statusCode)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -144,49 +144,6 @@ namespace Ocelot.AcceptanceTests
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_response_200_and_foward_claim_as_header()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
 | 
			
		||||
                .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura"))
 | 
			
		||||
                .And(x => x.GivenIHaveAToken("http://localhost:51888"))
 | 
			
		||||
                .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration
 | 
			
		||||
                {
 | 
			
		||||
                    ReRoutes = new List<YamlReRoute>
 | 
			
		||||
                    {
 | 
			
		||||
                        new YamlReRoute
 | 
			
		||||
                        {
 | 
			
		||||
                            DownstreamTemplate = "http://localhost:51876/",
 | 
			
		||||
                            UpstreamTemplate = "/",
 | 
			
		||||
                            UpstreamHttpMethod = "Get",
 | 
			
		||||
                            AuthenticationOptions = new YamlAuthenticationOptions
 | 
			
		||||
                            {
 | 
			
		||||
                                AdditionalScopes =  new List<string>(),
 | 
			
		||||
                                Provider = "IdentityServer",
 | 
			
		||||
                                ProviderRootUrl = "http://localhost:51888",
 | 
			
		||||
                                RequireHttps = false,
 | 
			
		||||
                                ScopeName = "api",
 | 
			
		||||
                                ScopeSecret = "secret"
 | 
			
		||||
                            },
 | 
			
		||||
                            AddHeadersToRequest =
 | 
			
		||||
                            {
 | 
			
		||||
                                { "CustomerId", "Claims[CustomerId] -> value" },
 | 
			
		||||
                                { "LocationId", "Claims[LocationId] -> value"},
 | 
			
		||||
                                { "UserId", "Claims[Subject] -> delimiter(|) -> value[0]" },
 | 
			
		||||
                                { "UserId", "Claims[Subject] -> delimiter(|) -> value[1]" }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }))
 | 
			
		||||
                .And(x => x.GivenTheApiGatewayIsRunning())
 | 
			
		||||
                .And(x => x.GivenIHaveAddedATokenToMyRequest())
 | 
			
		||||
                .When(x => x.WhenIGetUrlOnTheApiGateway("/"))
 | 
			
		||||
                .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
 | 
			
		||||
                .And(x => x.ThenTheResponseBodyShouldBe("Hello from Laura"))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_201_using_identity_server_access_token()
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										291
									
								
								test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Http.Headers;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using IdentityServer4.Models;
 | 
			
		||||
using IdentityServer4.Services.InMemory;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.TestHost;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Ocelot.Library.Configuration.Yaml;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
 | 
			
		||||
namespace Ocelot.AcceptanceTests
 | 
			
		||||
{
 | 
			
		||||
    public class ClaimsToHeadersForwardingTests : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private TestServer _ocelotServer;
 | 
			
		||||
        private HttpClient _ocelotClient;
 | 
			
		||||
        private HttpResponseMessage _response;
 | 
			
		||||
        private readonly string _configurationPath;
 | 
			
		||||
        private IWebHost _servicebuilder;
 | 
			
		||||
 | 
			
		||||
        // Sadly we need to change this when we update the netcoreapp version to make the test update the config correctly
 | 
			
		||||
        private double _netCoreAppVersion = 1.4;
 | 
			
		||||
        private BearerToken _token;
 | 
			
		||||
        private IWebHost _identityServerBuilder;
 | 
			
		||||
 | 
			
		||||
        public ClaimsToHeadersForwardingTests()
 | 
			
		||||
        {
 | 
			
		||||
            _configurationPath = $"./bin/Debug/netcoreapp{_netCoreAppVersion}/configuration.yaml";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_response_200_and_foward_claim_as_header()
 | 
			
		||||
        {
 | 
			
		||||
            var user = new InMemoryUser
 | 
			
		||||
            {
 | 
			
		||||
                Username = "test",
 | 
			
		||||
                Password = "test",
 | 
			
		||||
                Enabled = true,
 | 
			
		||||
                Subject = "registered|1231231",
 | 
			
		||||
                Claims = new List<Claim>
 | 
			
		||||
                {
 | 
			
		||||
                    new Claim("CustomerId", "123"),
 | 
			
		||||
                    new Claim("LocationId", "1")
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:52888", "api", AccessTokenType.Jwt, user))
 | 
			
		||||
                .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200))
 | 
			
		||||
                .And(x => x.GivenIHaveAToken("http://localhost:52888"))
 | 
			
		||||
                .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration
 | 
			
		||||
                {
 | 
			
		||||
                    ReRoutes = new List<YamlReRoute>
 | 
			
		||||
                    {
 | 
			
		||||
                        new YamlReRoute
 | 
			
		||||
                        {
 | 
			
		||||
                            DownstreamTemplate = "http://localhost:52876/",
 | 
			
		||||
                            UpstreamTemplate = "/",
 | 
			
		||||
                            UpstreamHttpMethod = "Get",
 | 
			
		||||
                            AuthenticationOptions = new YamlAuthenticationOptions
 | 
			
		||||
                            {
 | 
			
		||||
                                AdditionalScopes = new List<string>
 | 
			
		||||
                                {
 | 
			
		||||
                                    "openid", "offline_access"
 | 
			
		||||
                                },
 | 
			
		||||
                                Provider = "IdentityServer",
 | 
			
		||||
                                ProviderRootUrl = "http://localhost:52888",
 | 
			
		||||
                                RequireHttps = false,
 | 
			
		||||
                                ScopeName = "api",
 | 
			
		||||
                                ScopeSecret = "secret",
 | 
			
		||||
                            },
 | 
			
		||||
                            AddHeadersToRequest =
 | 
			
		||||
                            {
 | 
			
		||||
                                {"CustomerId", "Claims[CustomerId] > value"},
 | 
			
		||||
                                {"LocationId", "Claims[LocationId] > value"},
 | 
			
		||||
                                {"UserType", "Claims[sub] > value[0] > |"},
 | 
			
		||||
                                {"UserId", "Claims[sub] > value[1] > |"}
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }))
 | 
			
		||||
                .And(x => x.GivenTheApiGatewayIsRunning())
 | 
			
		||||
                .And(x => x.GivenIHaveAddedATokenToMyRequest())
 | 
			
		||||
                .When(x => x.WhenIGetUrlOnTheApiGateway("/"))
 | 
			
		||||
                .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
 | 
			
		||||
                .And(x => x.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231"))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenIGetUrlOnTheApiGateway(string url)
 | 
			
		||||
        {
 | 
			
		||||
            _response = _ocelotClient.GetAsync(url).Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResponseBodyShouldBe(string expectedBody)
 | 
			
		||||
        {
 | 
			
		||||
            _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// This is annoying cos it should be in the constructor but we need to set up the yaml file before calling startup so its a step.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private void GivenTheApiGatewayIsRunning()
 | 
			
		||||
        {
 | 
			
		||||
            _ocelotServer = new TestServer(new WebHostBuilder()
 | 
			
		||||
                .UseStartup<Startup>());
 | 
			
		||||
 | 
			
		||||
            _ocelotClient = _ocelotServer.CreateClient();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAConfiguration(YamlConfiguration yamlConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var serializer = new Serializer();
 | 
			
		||||
 | 
			
		||||
            if (File.Exists(_configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(_configurationPath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (TextWriter writer = File.CreateText(_configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                serializer.Serialize(writer, yamlConfiguration);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAServiceRunningOn(string url, int statusCode)
 | 
			
		||||
        {
 | 
			
		||||
            _servicebuilder = new WebHostBuilder()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .UseKestrel()
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .UseIISIntegration()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.Run(async context =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First();
 | 
			
		||||
                        var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First();
 | 
			
		||||
                        var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First();
 | 
			
		||||
                        var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First();
 | 
			
		||||
 | 
			
		||||
                        var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}";
 | 
			
		||||
                        context.Response.StatusCode = statusCode;
 | 
			
		||||
                        await context.Response.WriteAsync(responseBody);
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            _servicebuilder.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType, InMemoryUser user)
 | 
			
		||||
        {
 | 
			
		||||
            _identityServerBuilder = new WebHostBuilder()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .UseKestrel()
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .UseIISIntegration()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .ConfigureServices(services =>
 | 
			
		||||
                {
 | 
			
		||||
                    services.AddLogging();
 | 
			
		||||
                    services.AddDeveloperIdentityServer()
 | 
			
		||||
                        .AddInMemoryScopes(new List<Scope>
 | 
			
		||||
                        {
 | 
			
		||||
                            new Scope
 | 
			
		||||
                            {
 | 
			
		||||
                                Name = scopeName,
 | 
			
		||||
                                Description = "My API",
 | 
			
		||||
                                Enabled = true,
 | 
			
		||||
                                AllowUnrestrictedIntrospection = true,
 | 
			
		||||
                                ScopeSecrets = new List<Secret>()
 | 
			
		||||
                                {
 | 
			
		||||
                                    new Secret
 | 
			
		||||
                                    {
 | 
			
		||||
                                        Value = "secret".Sha256()
 | 
			
		||||
                                    }
 | 
			
		||||
                                },
 | 
			
		||||
                                IncludeAllClaimsForUser = true
 | 
			
		||||
                            },
 | 
			
		||||
 | 
			
		||||
                            StandardScopes.OpenId,
 | 
			
		||||
                            StandardScopes.OfflineAccess
 | 
			
		||||
                        })
 | 
			
		||||
                        .AddInMemoryClients(new List<Client>
 | 
			
		||||
                        {
 | 
			
		||||
                            new Client
 | 
			
		||||
                            {
 | 
			
		||||
                                ClientId = "client",
 | 
			
		||||
                                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
 | 
			
		||||
                                ClientSecrets = new List<Secret> {new Secret("secret".Sha256())},
 | 
			
		||||
                                AllowedScopes = new List<string> { scopeName, "openid", "offline_access" },
 | 
			
		||||
                                AccessTokenType = tokenType,
 | 
			
		||||
                                Enabled = true,
 | 
			
		||||
                                RequireClientSecret = false
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                        .AddInMemoryUsers(new List<InMemoryUser>
 | 
			
		||||
                        {
 | 
			
		||||
                            user
 | 
			
		||||
                        });
 | 
			
		||||
                })
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.UseIdentityServer();
 | 
			
		||||
                })
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            _identityServerBuilder.Start();
 | 
			
		||||
 | 
			
		||||
            VerifyIdentiryServerStarted(url);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void VerifyIdentiryServerStarted(string url)
 | 
			
		||||
        {
 | 
			
		||||
            using (var httpClient = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result;
 | 
			
		||||
                response.EnsureSuccessStatusCode();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenIHaveAToken(string url)
 | 
			
		||||
        {
 | 
			
		||||
            var tokenUrl = $"{url}/connect/token";
 | 
			
		||||
            var formData = new List<KeyValuePair<string, string>>
 | 
			
		||||
            {
 | 
			
		||||
                new KeyValuePair<string, string>("client_id", "client"),
 | 
			
		||||
                new KeyValuePair<string, string>("client_secret", "secret"),
 | 
			
		||||
                new KeyValuePair<string, string>("scope", "api"),
 | 
			
		||||
                new KeyValuePair<string, string>("username", "test"),
 | 
			
		||||
                new KeyValuePair<string, string>("password", "test"),
 | 
			
		||||
                new KeyValuePair<string, string>("grant_type", "password")
 | 
			
		||||
            };
 | 
			
		||||
            var content = new FormUrlEncodedContent(formData);
 | 
			
		||||
 | 
			
		||||
            using (var httpClient = new HttpClient())
 | 
			
		||||
            {
 | 
			
		||||
                var response = httpClient.PostAsync(tokenUrl, content).Result;
 | 
			
		||||
                response.EnsureSuccessStatusCode();
 | 
			
		||||
                var responseContent = response.Content.ReadAsStringAsync().Result;
 | 
			
		||||
                _token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenIHaveAddedATokenToMyRequest()
 | 
			
		||||
        {
 | 
			
		||||
            _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
 | 
			
		||||
        {
 | 
			
		||||
            _response.StatusCode.ShouldBe(expectedHttpStatusCode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            _servicebuilder?.Dispose();
 | 
			
		||||
            _ocelotClient?.Dispose();
 | 
			
		||||
            _ocelotServer?.Dispose();
 | 
			
		||||
            _identityServerBuilder?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // ReSharper disable once ClassNeverInstantiated.Local
 | 
			
		||||
        class BearerToken
 | 
			
		||||
        {
 | 
			
		||||
            [JsonProperty("access_token")]
 | 
			
		||||
            public string AccessToken { get; set; }
 | 
			
		||||
 | 
			
		||||
            [JsonProperty("expires_in")]
 | 
			
		||||
            public int ExpiresIn { get; set; }
 | 
			
		||||
 | 
			
		||||
            [JsonProperty("token_type")]
 | 
			
		||||
            public string TokenType { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Builder;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.AspNetCore.TestHost;
 | 
			
		||||
using Ocelot.Library.Configuration.Yaml;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
using YamlDotNet.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.AcceptanceTests
 | 
			
		||||
{
 | 
			
		||||
    public class ReturnsErrorTests : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private TestServer _ocelotServer;
 | 
			
		||||
        private HttpClient _ocelotClient;
 | 
			
		||||
        private HttpResponseMessage _response;
 | 
			
		||||
        private readonly string _configurationPath;
 | 
			
		||||
        private IWebHost _servicebuilder;
 | 
			
		||||
 | 
			
		||||
        // Sadly we need to change this when we update the netcoreapp version to make the test update the config correctly
 | 
			
		||||
        private double _netCoreAppVersion = 1.4;
 | 
			
		||||
 | 
			
		||||
        public ReturnsErrorTests()
 | 
			
		||||
        {
 | 
			
		||||
            _configurationPath = $"./bin/Debug/netcoreapp{_netCoreAppVersion}/configuration.yaml";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_response_200_and_foward_claim_as_header()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:53876"))
 | 
			
		||||
                .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration
 | 
			
		||||
                {
 | 
			
		||||
                    ReRoutes = new List<YamlReRoute>
 | 
			
		||||
                    {
 | 
			
		||||
                        new YamlReRoute
 | 
			
		||||
                        {
 | 
			
		||||
                            DownstreamTemplate = "http://localhost:53876/",
 | 
			
		||||
                            UpstreamTemplate = "/",
 | 
			
		||||
                            UpstreamHttpMethod = "Get",
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }))
 | 
			
		||||
                .And(x => x.GivenTheApiGatewayIsRunning())
 | 
			
		||||
                .When(x => x.WhenIGetUrlOnTheApiGateway("/"))
 | 
			
		||||
                .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.InternalServerError))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenIGetUrlOnTheApiGateway(string url)
 | 
			
		||||
        {
 | 
			
		||||
            _response = _ocelotClient.GetAsync(url).Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResponseBodyShouldBe(string expectedBody)
 | 
			
		||||
        {
 | 
			
		||||
            _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// This is annoying cos it should be in the constructor but we need to set up the yaml file before calling startup so its a step.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private void GivenTheApiGatewayIsRunning()
 | 
			
		||||
        {
 | 
			
		||||
            _ocelotServer = new TestServer(new WebHostBuilder()
 | 
			
		||||
                .UseStartup<Startup>());
 | 
			
		||||
 | 
			
		||||
            _ocelotClient = _ocelotServer.CreateClient();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAConfiguration(YamlConfiguration yamlConfiguration)
 | 
			
		||||
        {
 | 
			
		||||
            var serializer = new Serializer();
 | 
			
		||||
 | 
			
		||||
            if (File.Exists(_configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(_configurationPath);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using (TextWriter writer = File.CreateText(_configurationPath))
 | 
			
		||||
            {
 | 
			
		||||
                serializer.Serialize(writer, yamlConfiguration);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenThereIsAServiceRunningOn(string url)
 | 
			
		||||
        {
 | 
			
		||||
            _servicebuilder = new WebHostBuilder()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .UseKestrel()
 | 
			
		||||
                .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
                .UseIISIntegration()
 | 
			
		||||
                .UseUrls(url)
 | 
			
		||||
                .Configure(app =>
 | 
			
		||||
                {
 | 
			
		||||
                    app.Run(context =>
 | 
			
		||||
                    {
 | 
			
		||||
                        throw new Exception("BLAMMMM");
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
            _servicebuilder.Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
 | 
			
		||||
        {
 | 
			
		||||
            _response.StatusCode.ShouldBe(expectedHttpStatusCode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            _servicebuilder?.Dispose();
 | 
			
		||||
            _ocelotClient?.Dispose();
 | 
			
		||||
            _ocelotServer?.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Microsoft.Extensions.Options;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
@@ -18,9 +20,13 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
        private readonly Mock<IConfigurationValidator> _validator;
 | 
			
		||||
        private OcelotConfiguration _config;
 | 
			
		||||
        private YamlConfiguration _yamlConfiguration;
 | 
			
		||||
        private readonly Mock<IConfigurationHeaderExtrator> _configExtractor;
 | 
			
		||||
        private readonly Mock<ILogger<OcelotConfiguration>> _logger;
 | 
			
		||||
 | 
			
		||||
        public OcelotConfigurationTests()
 | 
			
		||||
        {
 | 
			
		||||
            _logger = new Mock<ILogger<OcelotConfiguration>>();
 | 
			
		||||
            _configExtractor = new Mock<IConfigurationHeaderExtrator>();
 | 
			
		||||
            _validator = new Mock<IConfigurationValidator>();
 | 
			
		||||
            _yamlConfig = new Mock<IOptions<YamlConfiguration>>();
 | 
			
		||||
        }
 | 
			
		||||
@@ -54,6 +60,114 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_create_with_headers_to_extract()
 | 
			
		||||
        {
 | 
			
		||||
            var expected = new List<ReRoute>
 | 
			
		||||
            {
 | 
			
		||||
                new ReRouteBuilder()
 | 
			
		||||
                    .WithDownstreamTemplate("/products/{productId}")
 | 
			
		||||
                    .WithUpstreamTemplate("/api/products/{productId}")
 | 
			
		||||
                    .WithUpstreamHttpMethod("Get")
 | 
			
		||||
                    .WithUpstreamTemplatePattern("/api/products/.*$")
 | 
			
		||||
                    .WithAuthenticationProvider("IdentityServer")
 | 
			
		||||
                    .WithAuthenticationProviderUrl("http://localhost:51888")
 | 
			
		||||
                    .WithRequireHttps(false)
 | 
			
		||||
                    .WithScopeSecret("secret")
 | 
			
		||||
                    .WithAuthenticationProviderScopeName("api")
 | 
			
		||||
                    .WithConfigurationHeaderExtractorProperties(new List<ConfigurationHeaderExtractorProperties>
 | 
			
		||||
                    {
 | 
			
		||||
                        new ConfigurationHeaderExtractorProperties("CustomerId", "CustomerId", "", 0),
 | 
			
		||||
                    })
 | 
			
		||||
                    .Build()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(x => x.GivenTheYamlConfigIs(new YamlConfiguration
 | 
			
		||||
            {
 | 
			
		||||
                ReRoutes = new List<YamlReRoute>
 | 
			
		||||
                {
 | 
			
		||||
                    new YamlReRoute
 | 
			
		||||
                    {
 | 
			
		||||
                        UpstreamTemplate = "/api/products/{productId}",
 | 
			
		||||
                        DownstreamTemplate = "/products/{productId}",
 | 
			
		||||
                        UpstreamHttpMethod = "Get",
 | 
			
		||||
                        AuthenticationOptions = new YamlAuthenticationOptions
 | 
			
		||||
                            {
 | 
			
		||||
                                AdditionalScopes =  new List<string>(),
 | 
			
		||||
                                Provider = "IdentityServer",
 | 
			
		||||
                                ProviderRootUrl = "http://localhost:51888",
 | 
			
		||||
                                RequireHttps = false,
 | 
			
		||||
                                ScopeName = "api",
 | 
			
		||||
                                ScopeSecret = "secret"
 | 
			
		||||
                            },
 | 
			
		||||
                        AddHeadersToRequest =
 | 
			
		||||
                        {
 | 
			
		||||
                            {"CustomerId", "Claims[CustomerId] > value"},
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }))
 | 
			
		||||
                .And(x => x.GivenTheYamlConfigIsValid())
 | 
			
		||||
                .And(x => x.GivenTheConfigHeaderExtractorReturns(new ConfigurationHeaderExtractorProperties("CustomerId", "CustomerId", "", 0)))
 | 
			
		||||
                .When(x => x.WhenIInstanciateTheOcelotConfig())
 | 
			
		||||
                .Then(x => x.ThenTheReRoutesAre(expected))
 | 
			
		||||
                .And(x => x.ThenTheAuthenticationOptionsAre(expected))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheConfigHeaderExtractorReturns(ConfigurationHeaderExtractorProperties expected)
 | 
			
		||||
        {
 | 
			
		||||
            _configExtractor
 | 
			
		||||
                .Setup(x => x.Extract(It.IsAny<string>(), It.IsAny<string>()))
 | 
			
		||||
                .Returns(new OkResponse<ConfigurationHeaderExtractorProperties>(expected));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_create_with_authentication_properties()
 | 
			
		||||
        {
 | 
			
		||||
            var expected = new List<ReRoute>
 | 
			
		||||
            {
 | 
			
		||||
                new ReRouteBuilder()
 | 
			
		||||
                    .WithDownstreamTemplate("/products/{productId}")
 | 
			
		||||
                    .WithUpstreamTemplate("/api/products/{productId}")
 | 
			
		||||
                    .WithUpstreamHttpMethod("Get")
 | 
			
		||||
                    .WithUpstreamTemplatePattern("/api/products/.*$")
 | 
			
		||||
                    .WithAuthenticationProvider("IdentityServer")
 | 
			
		||||
                    .WithAuthenticationProviderUrl("http://localhost:51888")
 | 
			
		||||
                    .WithRequireHttps(false)
 | 
			
		||||
                    .WithScopeSecret("secret")
 | 
			
		||||
                    .WithAuthenticationProviderScopeName("api")
 | 
			
		||||
                    .Build()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(x => x.GivenTheYamlConfigIs(new YamlConfiguration
 | 
			
		||||
            {
 | 
			
		||||
                ReRoutes = new List<YamlReRoute>
 | 
			
		||||
                {
 | 
			
		||||
                    new YamlReRoute
 | 
			
		||||
                    {
 | 
			
		||||
                        UpstreamTemplate = "/api/products/{productId}",
 | 
			
		||||
                        DownstreamTemplate = "/products/{productId}",
 | 
			
		||||
                        UpstreamHttpMethod = "Get",
 | 
			
		||||
                        AuthenticationOptions = new YamlAuthenticationOptions
 | 
			
		||||
                            {
 | 
			
		||||
                                AdditionalScopes =  new List<string>(),
 | 
			
		||||
                                Provider = "IdentityServer",
 | 
			
		||||
                                ProviderRootUrl = "http://localhost:51888",
 | 
			
		||||
                                RequireHttps = false,
 | 
			
		||||
                                ScopeName = "api",
 | 
			
		||||
                                ScopeSecret = "secret"
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }))
 | 
			
		||||
                .And(x => x.GivenTheYamlConfigIsValid())
 | 
			
		||||
                .When(x => x.WhenIInstanciateTheOcelotConfig())
 | 
			
		||||
                .Then(x => x.ThenTheReRoutesAre(expected))
 | 
			
		||||
                .And(x => x.ThenTheAuthenticationOptionsAre(expected))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_create_template_pattern_that_matches_more_than_one_placeholder()
 | 
			
		||||
        {
 | 
			
		||||
@@ -158,7 +272,8 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
 | 
			
		||||
        private void WhenIInstanciateTheOcelotConfig()
 | 
			
		||||
        {
 | 
			
		||||
            _config = new OcelotConfiguration(_yamlConfig.Object, _validator.Object);
 | 
			
		||||
            _config = new OcelotConfiguration(_yamlConfig.Object, _validator.Object,
 | 
			
		||||
                _configExtractor.Object, _logger.Object);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
 | 
			
		||||
@@ -174,5 +289,22 @@ namespace Ocelot.UnitTests.Configuration
 | 
			
		||||
                result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheAuthenticationOptionsAre(List<ReRoute> expectedReRoutes)
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < _config.ReRoutes.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var result = _config.ReRoutes[i].AuthenticationOptions;
 | 
			
		||||
                var expected = expectedReRoutes[i].AuthenticationOptions;
 | 
			
		||||
 | 
			
		||||
                result.AdditionalScopes.ShouldBe(expected.AdditionalScopes);
 | 
			
		||||
                result.Provider.ShouldBe(expected.Provider);
 | 
			
		||||
                result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl);
 | 
			
		||||
                result.RequireHttps.ShouldBe(expected.RequireHttps);
 | 
			
		||||
                result.ScopeName.ShouldBe(expected.ScopeName);
 | 
			
		||||
                result.ScopeSecret.ShouldBe(expected.ScopeSecret);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,110 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Microsoft.AspNetCore.Hosting;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.AspNetCore.TestHost;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Library.Builder;
 | 
			
		||||
using Ocelot.Library.DownstreamRouteFinder;
 | 
			
		||||
using Ocelot.Library.Middleware;
 | 
			
		||||
using Ocelot.Library.Repository;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
using Ocelot.Library.UrlMatcher;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.Middleware
 | 
			
		||||
{
 | 
			
		||||
    public class HttpRequestHeadersBuilderMiddlewareTests : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        private readonly Mock<IScopedRequestDataRepository> _scopedRepository;
 | 
			
		||||
        private readonly Mock<IAddHeadersToRequest> _addHeaders;
 | 
			
		||||
        private readonly string _url;
 | 
			
		||||
        private readonly TestServer _server;
 | 
			
		||||
        private readonly HttpClient _client;
 | 
			
		||||
        private Response<DownstreamRoute> _downstreamRoute;
 | 
			
		||||
        private HttpResponseMessage _result;
 | 
			
		||||
 | 
			
		||||
        public HttpRequestHeadersBuilderMiddlewareTests()
 | 
			
		||||
        {
 | 
			
		||||
            _url = "http://localhost:51879";
 | 
			
		||||
            _scopedRepository = new Mock<IScopedRequestDataRepository>();
 | 
			
		||||
            _addHeaders = new Mock<IAddHeadersToRequest>();
 | 
			
		||||
            var builder = new WebHostBuilder()
 | 
			
		||||
              .ConfigureServices(x =>
 | 
			
		||||
              {
 | 
			
		||||
                  x.AddSingleton(_addHeaders.Object);
 | 
			
		||||
                  x.AddSingleton(_scopedRepository.Object);
 | 
			
		||||
              })
 | 
			
		||||
              .UseUrls(_url)
 | 
			
		||||
              .UseKestrel()
 | 
			
		||||
              .UseContentRoot(Directory.GetCurrentDirectory())
 | 
			
		||||
              .UseIISIntegration()
 | 
			
		||||
              .UseUrls(_url)
 | 
			
		||||
              .Configure(app =>
 | 
			
		||||
              {
 | 
			
		||||
                  app.UseHttpRequestHeadersBuilderMiddleware();
 | 
			
		||||
              });
 | 
			
		||||
 | 
			
		||||
            _server = new TestServer(builder);
 | 
			
		||||
            _client = _server.CreateClient();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void happy_path()
 | 
			
		||||
        {
 | 
			
		||||
            var downstreamRoute = new DownstreamRoute(new List<TemplateVariableNameAndValue>(),
 | 
			
		||||
                new ReRouteBuilder()
 | 
			
		||||
                    .WithDownstreamTemplate("any old string")
 | 
			
		||||
                    .WithConfigurationHeaderExtractorProperties(new List<ConfigurationHeaderExtractorProperties>
 | 
			
		||||
                    {
 | 
			
		||||
                        new ConfigurationHeaderExtractorProperties("UserId", "Subject", "", 0)
 | 
			
		||||
                    })
 | 
			
		||||
                    .Build());
 | 
			
		||||
 | 
			
		||||
            this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute))
 | 
			
		||||
                .And(x => x.GivenTheAddHeadersToRequestReturns("123"))
 | 
			
		||||
                .When(x => x.WhenICallTheMiddleware())
 | 
			
		||||
                .Then(x => x.ThenTheAddHeadersToRequestIsCalledCorrectly())
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheAddHeadersToRequestReturns(string claimValue)
 | 
			
		||||
        {
 | 
			
		||||
            _addHeaders
 | 
			
		||||
                .Setup(x => x.SetHeadersOnContext(It.IsAny<List<ConfigurationHeaderExtractorProperties>>(), 
 | 
			
		||||
                It.IsAny<HttpContext>()))
 | 
			
		||||
                .Returns(new OkResponse());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheAddHeadersToRequestIsCalledCorrectly()
 | 
			
		||||
        {
 | 
			
		||||
            _addHeaders
 | 
			
		||||
                .Verify(x => x.SetHeadersOnContext(It.IsAny<List<ConfigurationHeaderExtractorProperties>>(),
 | 
			
		||||
                It.IsAny<HttpContext>()), Times.Once);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenICallTheMiddleware()
 | 
			
		||||
        {
 | 
			
		||||
            _result = _client.GetAsync(_url).Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute)
 | 
			
		||||
        {
 | 
			
		||||
            _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute);
 | 
			
		||||
            _scopedRepository
 | 
			
		||||
                .Setup(x => x.Get<DownstreamRoute>(It.IsAny<string>()))
 | 
			
		||||
                .Returns(_downstreamRoute);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            _client.Dispose();
 | 
			
		||||
            _server.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										152
									
								
								test/Ocelot.UnitTests/RequestBuilder/AddHeadersToRequestTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								test/Ocelot.UnitTests/RequestBuilder/AddHeadersToRequestTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
using Microsoft.Extensions.Primitives;
 | 
			
		||||
using Moq;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class AddHeadersToRequestTests
 | 
			
		||||
    {
 | 
			
		||||
        private readonly AddHeadersToRequest _addHeadersToRequest;
 | 
			
		||||
        private readonly Mock<IClaimsParser> _parser;
 | 
			
		||||
        private List<ConfigurationHeaderExtractorProperties> _configuration;
 | 
			
		||||
        private HttpContext _context;
 | 
			
		||||
        private Response _result;
 | 
			
		||||
        private Response<string> _claimValue;
 | 
			
		||||
 | 
			
		||||
        public AddHeadersToRequestTests()
 | 
			
		||||
        {
 | 
			
		||||
            _parser = new Mock<IClaimsParser>();
 | 
			
		||||
            _addHeadersToRequest = new AddHeadersToRequest(_parser.Object);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_add_headers_to_context()
 | 
			
		||||
        {
 | 
			
		||||
            var context = new DefaultHttpContext
 | 
			
		||||
            {
 | 
			
		||||
                User = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
 | 
			
		||||
                {
 | 
			
		||||
                    new Claim("test", "data")
 | 
			
		||||
                }))
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.Given(
 | 
			
		||||
                x => x.GivenConfigurationHeaderExtractorProperties(new List<ConfigurationHeaderExtractorProperties>
 | 
			
		||||
                {
 | 
			
		||||
                    new ConfigurationHeaderExtractorProperties("header-key", "", "", 0)
 | 
			
		||||
                }))
 | 
			
		||||
                .Given(x => x.GivenHttpContext(context))
 | 
			
		||||
                .And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value")))
 | 
			
		||||
                .When(x => x.WhenIAddHeadersToTheRequest())
 | 
			
		||||
                .Then(x => x.ThenTheResultIsSuccess())
 | 
			
		||||
                .And(x => x.ThenTheHeaderIsAdded())
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void if_header_exists_should_replace_it()
 | 
			
		||||
        {
 | 
			
		||||
            var context = new DefaultHttpContext
 | 
			
		||||
            {
 | 
			
		||||
                User = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
 | 
			
		||||
                {
 | 
			
		||||
                    new Claim("test", "data")
 | 
			
		||||
                })),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            context.Request.Headers.Add("header-key", new StringValues("initial"));
 | 
			
		||||
 | 
			
		||||
            this.Given(
 | 
			
		||||
                x => x.GivenConfigurationHeaderExtractorProperties(new List<ConfigurationHeaderExtractorProperties>
 | 
			
		||||
                {
 | 
			
		||||
                    new ConfigurationHeaderExtractorProperties("header-key", "", "", 0)
 | 
			
		||||
                }))
 | 
			
		||||
                .Given(x => x.GivenHttpContext(context))
 | 
			
		||||
                .And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value")))
 | 
			
		||||
                .When(x => x.WhenIAddHeadersToTheRequest())
 | 
			
		||||
                .Then(x => x.ThenTheResultIsSuccess())
 | 
			
		||||
                .And(x => x.ThenTheHeaderIsAdded())
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_error()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(
 | 
			
		||||
               x => x.GivenConfigurationHeaderExtractorProperties(new List<ConfigurationHeaderExtractorProperties>
 | 
			
		||||
               {
 | 
			
		||||
                    new ConfigurationHeaderExtractorProperties("", "", "", 0)
 | 
			
		||||
               }))
 | 
			
		||||
               .Given(x => x.GivenHttpContext(new DefaultHttpContext()))
 | 
			
		||||
               .And(x => x.GivenTheClaimParserReturns(new ErrorResponse<string>(new List<Error>
 | 
			
		||||
               {
 | 
			
		||||
                   new AnyError()
 | 
			
		||||
               })))
 | 
			
		||||
               .When(x => x.WhenIAddHeadersToTheRequest())
 | 
			
		||||
               .Then(x => x.ThenTheResultIsError())
 | 
			
		||||
               .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheHeaderIsAdded()
 | 
			
		||||
        {
 | 
			
		||||
            var header = _context.Request.Headers.First(x => x.Key == "header-key");
 | 
			
		||||
            header.Value.First().ShouldBe(_claimValue.Data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenConfigurationHeaderExtractorProperties(List<ConfigurationHeaderExtractorProperties> configuration)
 | 
			
		||||
        {
 | 
			
		||||
            _configuration = configuration;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenHttpContext(HttpContext context)
 | 
			
		||||
        {
 | 
			
		||||
            _context = context;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheClaimParserReturns(Response<string> claimValue)
 | 
			
		||||
        {
 | 
			
		||||
            _claimValue = claimValue;
 | 
			
		||||
            _parser
 | 
			
		||||
                .Setup(
 | 
			
		||||
                    x =>
 | 
			
		||||
                        x.GetValue(It.IsAny<IEnumerable<Claim>>(), 
 | 
			
		||||
                        It.IsAny<string>(), 
 | 
			
		||||
                        It.IsAny<string>(),
 | 
			
		||||
                        It.IsAny<int>()))
 | 
			
		||||
                .Returns(_claimValue);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenIAddHeadersToTheRequest()
 | 
			
		||||
        {
 | 
			
		||||
            _result = _addHeadersToRequest.SetHeadersOnContext(_configuration, _context);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResultIsSuccess()
 | 
			
		||||
        {
 | 
			
		||||
            _result.IsError.ShouldBe(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResultIsError()
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            _result.IsError.ShouldBe(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        class AnyError : Error
 | 
			
		||||
        {
 | 
			
		||||
            public AnyError() 
 | 
			
		||||
                : base("blahh", OcelotErrorCode.UnknownError)
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								test/Ocelot.UnitTests/RequestBuilder/ClaimParserTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								test/Ocelot.UnitTests/RequestBuilder/ClaimParserTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ClaimParserTests
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IClaimsParser _claimsParser;
 | 
			
		||||
        private readonly List<Claim> _claims;
 | 
			
		||||
        private string _key;
 | 
			
		||||
        private Response<string> _result;
 | 
			
		||||
        private string _delimiter;
 | 
			
		||||
        private int _index;
 | 
			
		||||
 | 
			
		||||
        public ClaimParserTests()
 | 
			
		||||
        {
 | 
			
		||||
            _claims = new List<Claim>();
 | 
			
		||||
            _claimsParser = new Library.RequestBuilder.ClaimsParser();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void can_parse_claims_dictionary_access_string_returning_value_to_function()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenAClaimOf(new Claim("CustomerId", "1234")))
 | 
			
		||||
                .And(x => x.GivenTheKeyIs("CustomerId"))
 | 
			
		||||
                .When(x => x.WhenICallTheParser())
 | 
			
		||||
                .Then(x => x.ThenTheResultIs(new OkResponse<string>("1234")))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_error_response_when_cannot_find_requested_claim()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenAClaimOf(new Claim("BallsId", "1234")))
 | 
			
		||||
                .And(x => x.GivenTheKeyIs("CustomerId"))
 | 
			
		||||
                .When(x => x.WhenICallTheParser())
 | 
			
		||||
                .Then(x => x.ThenTheResultIs(new ErrorResponse<string>(new List<Error>
 | 
			
		||||
                {
 | 
			
		||||
                    new CannotFindClaimError($"Cannot find claim for key: {_key}")
 | 
			
		||||
                })))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void can_parse_claims_dictionary_access_string_using_delimiter_and_retuning_at_correct_index()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321")))
 | 
			
		||||
                .And(x => x.GivenTheDelimiterIs("|"))
 | 
			
		||||
                .And(x => x.GivenTheIndexIs(1))
 | 
			
		||||
                .And(x => x.GivenTheKeyIs("Subject"))
 | 
			
		||||
                .When(x => x.WhenICallTheParser())
 | 
			
		||||
                .Then(x => x.ThenTheResultIs(new OkResponse<string>("4321")))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_error_response_if_index_too_large()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321")))
 | 
			
		||||
                .And(x => x.GivenTheDelimiterIs("|"))
 | 
			
		||||
                .And(x => x.GivenTheIndexIs(24))
 | 
			
		||||
                .And(x => x.GivenTheKeyIs("Subject"))
 | 
			
		||||
                .When(x => x.WhenICallTheParser())
 | 
			
		||||
                .Then(x => x.ThenTheResultIs(new ErrorResponse<string>(new List<Error>
 | 
			
		||||
                {
 | 
			
		||||
                    new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}")
 | 
			
		||||
                })))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void should_return_error_response_if_index_too_small()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenAClaimOf(new Claim("Subject", "registered|4321")))
 | 
			
		||||
                .And(x => x.GivenTheDelimiterIs("|"))
 | 
			
		||||
                .And(x => x.GivenTheIndexIs(-1))
 | 
			
		||||
                .And(x => x.GivenTheKeyIs("Subject"))
 | 
			
		||||
                .When(x => x.WhenICallTheParser())
 | 
			
		||||
                .Then(x => x.ThenTheResultIs(new ErrorResponse<string>(new List<Error>
 | 
			
		||||
                {
 | 
			
		||||
                    new CannotFindClaimError($"Cannot find claim for key: {_key}, delimiter: {_delimiter}, index: {_index}")
 | 
			
		||||
                })))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheIndexIs(int index)
 | 
			
		||||
        {
 | 
			
		||||
            _index = index;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheDelimiterIs(string delimiter)
 | 
			
		||||
        {
 | 
			
		||||
            _delimiter = delimiter;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenAClaimOf(Claim claim)
 | 
			
		||||
        {
 | 
			
		||||
            _claims.Add(claim);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheKeyIs(string key)
 | 
			
		||||
        {
 | 
			
		||||
            _key = key;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenICallTheParser()
 | 
			
		||||
        {
 | 
			
		||||
            _result = _claimsParser.GetValue(_claims, _key, _delimiter, _index);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheResultIs(Response<string> expected)
 | 
			
		||||
        {
 | 
			
		||||
            _result.Data.ShouldBe(expected.Data);
 | 
			
		||||
            _result.IsError.ShouldBe(expected.IsError);
 | 
			
		||||
        } 
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,116 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Ocelot.Library.Errors;
 | 
			
		||||
using Ocelot.Library.RequestBuilder;
 | 
			
		||||
using Ocelot.Library.Responses;
 | 
			
		||||
using Shouldly;
 | 
			
		||||
using TestStack.BDDfy;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Ocelot.UnitTests.RequestBuilder
 | 
			
		||||
{
 | 
			
		||||
    public class ConfigurationHeadersExtractorTests
 | 
			
		||||
    {
 | 
			
		||||
        private Dictionary<string, string> _dictionary;
 | 
			
		||||
        private readonly IConfigurationHeaderExtrator _configurationHeaderExtrator;
 | 
			
		||||
        private Response<ConfigurationHeaderExtractorProperties> _result;
 | 
			
		||||
 | 
			
		||||
        public ConfigurationHeadersExtractorTests()
 | 
			
		||||
        {
 | 
			
		||||
            _configurationHeaderExtrator = new ConfigurationHeaderExtrator();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void returns_no_instructions_error()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenTheDictionaryIs(new Dictionary<string, string>()
 | 
			
		||||
            {
 | 
			
		||||
                {"CustomerId", ""},
 | 
			
		||||
            }))
 | 
			
		||||
                .When(x => x.WhenICallTheExtractor())
 | 
			
		||||
                .Then(
 | 
			
		||||
                    x =>
 | 
			
		||||
                        x.ThenAnErrorIsReturned(new ErrorResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                            new List<Error>
 | 
			
		||||
                            {
 | 
			
		||||
                                new NoInstructionsError(">")
 | 
			
		||||
                            })))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void returns_no_instructions_not_for_claims_error()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenTheDictionaryIs(new Dictionary<string, string>()
 | 
			
		||||
            {
 | 
			
		||||
                {"CustomerId", "Cheese[CustomerId] > value"},
 | 
			
		||||
            }))
 | 
			
		||||
                .When(x => x.WhenICallTheExtractor())
 | 
			
		||||
                .Then(
 | 
			
		||||
                    x =>
 | 
			
		||||
                        x.ThenAnErrorIsReturned(new ErrorResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                            new List<Error>
 | 
			
		||||
                            {
 | 
			
		||||
                                new InstructionNotForClaimsError()
 | 
			
		||||
                            })))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void can_parse_entry_to_work_out_properties_with_key()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenTheDictionaryIs(new Dictionary<string, string>()
 | 
			
		||||
            {
 | 
			
		||||
                {"CustomerId", "Claims[CustomerId] > value"},
 | 
			
		||||
            }))
 | 
			
		||||
                .When(x => x.WhenICallTheExtractor())
 | 
			
		||||
                .Then(
 | 
			
		||||
                    x =>
 | 
			
		||||
                        x.ThenTheClaimParserPropertiesAreReturned(
 | 
			
		||||
                            new OkResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                                new ConfigurationHeaderExtractorProperties("CustomerId", "CustomerId", "", 0))))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public void can_parse_entry_to_work_out_properties_with_key_delimiter_and_index()
 | 
			
		||||
        {
 | 
			
		||||
            this.Given(x => x.GivenTheDictionaryIs(new Dictionary<string, string>()
 | 
			
		||||
            {
 | 
			
		||||
                {"UserId", "Claims[Subject] > value[0] > |"},
 | 
			
		||||
            }))
 | 
			
		||||
                .When(x => x.WhenICallTheExtractor())
 | 
			
		||||
                .Then(
 | 
			
		||||
                    x =>
 | 
			
		||||
                        x.ThenTheClaimParserPropertiesAreReturned(
 | 
			
		||||
                            new OkResponse<ConfigurationHeaderExtractorProperties>(
 | 
			
		||||
                                new ConfigurationHeaderExtractorProperties("UserId", "Subject", "|", 0))))
 | 
			
		||||
                .BDDfy();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenAnErrorIsReturned(Response<ConfigurationHeaderExtractorProperties> expected)
 | 
			
		||||
        {
 | 
			
		||||
            _result.IsError.ShouldBe(expected.IsError);
 | 
			
		||||
            _result.Errors[0].ShouldBeOfType(expected.Errors[0].GetType());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ThenTheClaimParserPropertiesAreReturned(Response<ConfigurationHeaderExtractorProperties> expected)
 | 
			
		||||
        {
 | 
			
		||||
            _result.Data.ClaimKey.ShouldBe(expected.Data.ClaimKey);
 | 
			
		||||
            _result.Data.Delimiter.ShouldBe(expected.Data.Delimiter);
 | 
			
		||||
            _result.Data.Index.ShouldBe(expected.Data.Index);
 | 
			
		||||
            _result.IsError.ShouldBe(expected.IsError);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void WhenICallTheExtractor()
 | 
			
		||||
        {
 | 
			
		||||
            var first = _dictionary.First();
 | 
			
		||||
            _result = _configurationHeaderExtrator.Extract(first.Key, first.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void GivenTheDictionaryIs(Dictionary<string, string> dictionary)
 | 
			
		||||
        {
 | 
			
		||||
            _dictionary = dictionary;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user