diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 4790afc5..dfe30c5a 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -107,7 +107,7 @@ Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddOcelot() + .AddOcelot(hostingContext.HostingEnvironment) .AddEnvironmentVariables(); }) @@ -117,6 +117,22 @@ The way Ocelot merges the files is basically load them, loop over them, add any At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. +You can also give Ocelot a specific path to look in for the configuration files like below. + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot("/foo/bar", hostingContext.HostingEnvironment) + .AddEnvironmentVariables(); + }) + +Ocelot needs the HostingEnvironment so it know's to exclude anything environment specific from the algorithm. + Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 744227e2..8c1f1459 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -81,6 +81,7 @@ Placeholders Ocelot allows placeholders that can be used in header transformation. +{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. {TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. @@ -120,6 +121,17 @@ finally if you are using a load balancer with Ocelot you will get multiple downs "AllowAutoRedirect": false, }, +X-Forwarded-For +^^^^^^^^^^^^^^^ + +An example of using {RemoteIpAddress} placeholder... + +.. code-block:: json + + "UpstreamHeaderTransform": { + "X-Forwarded-For": "{RemoteIpAddress}" + } + Future ^^^^^^ diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 77f9775e..af7f5791 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -22,12 +22,12 @@ namespace Ocelot.Authentication.Middleware public async Task Invoke(DownstreamContext context) { - if (IsAuthenticatedRoute(context.DownstreamReRoute)) + if (context.HttpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(context.DownstreamReRoute)) { Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - + var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); - + context.HttpContext.User = result.Principal; if (context.HttpContext.User.Identity.IsAuthenticated) @@ -41,7 +41,7 @@ namespace Ocelot.Authentication.Middleware $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); - + SetPipelineError(context, error); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index fb4f7a26..f3e4e21d 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -82,7 +82,7 @@ } else { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} route does not require user to be authorised"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); await _next.Invoke(context); } } diff --git a/src/Ocelot/Cache/CachedResponse.cs b/src/Ocelot/Cache/CachedResponse.cs index e4360e0d..538a4c35 100644 --- a/src/Ocelot/Cache/CachedResponse.cs +++ b/src/Ocelot/Cache/CachedResponse.cs @@ -1,30 +1,33 @@ -using System.Collections.Generic; -using System.Net; - -namespace Ocelot.Cache -{ - public class CachedResponse - { - public CachedResponse( - HttpStatusCode statusCode, - Dictionary> headers, - string body, - Dictionary> contentHeaders - - ) - { - StatusCode = statusCode; - Headers = headers ?? new Dictionary>(); - ContentHeaders = contentHeaders ?? new Dictionary>(); - Body = body ?? ""; - } - - public HttpStatusCode StatusCode { get; private set; } - - public Dictionary> Headers { get; private set; } - - public Dictionary> ContentHeaders { get; private set; } - - public string Body { get; private set; } - } -} +using System.Collections.Generic; +using System.Net; + +namespace Ocelot.Cache +{ + public class CachedResponse + { + public CachedResponse( + HttpStatusCode statusCode, + Dictionary> headers, + string body, + Dictionary> contentHeaders, + string reasonPhrase + ) + { + StatusCode = statusCode; + Headers = headers ?? new Dictionary>(); + ContentHeaders = contentHeaders ?? new Dictionary>(); + Body = body ?? ""; + ReasonPhrase = reasonPhrase; + } + + public HttpStatusCode StatusCode { get; private set; } + + public Dictionary> Headers { get; private set; } + + public Dictionary> ContentHeaders { get; private set; } + + public string Body { get; private set; } + + public string ReasonPhrase { get; private set; } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 00c2e322..5b96e79c 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -87,7 +87,7 @@ streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value); } - return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList()); + return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase); } internal async Task CreateCachedResponse(DownstreamResponse response) @@ -109,7 +109,7 @@ var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value); - var cached = new CachedResponse(statusCode, headers, body, contentHeaders); + var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase); return cached; } } diff --git a/src/Ocelot/Claims/AddClaimsToRequest.cs b/src/Ocelot/Claims/AddClaimsToRequest.cs index aa1237a8..9ff0254f 100644 --- a/src/Ocelot/Claims/AddClaimsToRequest.cs +++ b/src/Ocelot/Claims/AddClaimsToRequest.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; - -namespace Ocelot.Claims -{ - public class AddClaimsToRequest : IAddClaimsToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddClaimsToRequest(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetClaimsOnContext(List claimsToThings, HttpContext context) - { - foreach (var config in claimsToThings) - { - var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey); - - var identity = context.User.Identity as ClaimsIdentity; - - if (exists != null) - { - identity?.RemoveClaim(exists); - } - - identity?.AddClaim(new System.Security.Claims.Claim(config.ExistingKey, value.Data)); - } - - return new OkResponse(); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; + +namespace Ocelot.Claims +{ + public class AddClaimsToRequest : IAddClaimsToRequest + { + private readonly IClaimsParser _claimsParser; + + public AddClaimsToRequest(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response SetClaimsOnContext(List claimsToThings, HttpContext context) + { + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey); + + var identity = context.User.Identity as ClaimsIdentity; + + if (exists != null) + { + identity?.RemoveClaim(exists); + } + + identity?.AddClaim(new System.Security.Claims.Claim(config.ExistingKey, value.Data)); + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs index 0790ec7c..a1d929ec 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs @@ -1,13 +1,13 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Claims.Middleware -{ - public static class ClaimsBuilderMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseClaimsBuilderMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } -} +using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; + +namespace Ocelot.Claims.Middleware +{ + public static class ClaimsToClaimsMiddlewareExtensions + { + public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs similarity index 83% rename from src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs rename to src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs index 2dfc3dc0..7e1a301e 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs @@ -1,45 +1,45 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Claims.Middleware -{ - public class ClaimsBuilderMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddClaimsToRequest _addClaimsToRequest; - - public ClaimsBuilderMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddClaimsToRequest addClaimsToRequest) - :base(loggerFactory.CreateLogger()) - { - _next = next; - _addClaimsToRequest = addClaimsToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToClaims.Any()) - { - Logger.LogDebug("this route has instructions to convert claims to other claims"); - - var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); - - if (result.IsError) - { - Logger.LogDebug("error converting claims to other claims, setting pipeline error"); - - SetPipelineError(context, result.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Claims.Middleware +{ + public class ClaimsToClaimsMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IAddClaimsToRequest _addClaimsToRequest; + + public ClaimsToClaimsMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddClaimsToRequest addClaimsToRequest) + :base(loggerFactory.CreateLogger()) + { + _next = next; + _addClaimsToRequest = addClaimsToRequest; + } + + public async Task Invoke(DownstreamContext context) + { + if (context.DownstreamReRoute.ClaimsToClaims.Any()) + { + Logger.LogDebug("this route has instructions to convert claims to other claims"); + + var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); + + if (result.IsError) + { + Logger.LogDebug("error converting claims to other claims, setting pipeline error"); + + SetPipelineError(context, result.Errors); + return; + } + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index cbe2e910..8e2583d2 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -33,13 +33,12 @@ namespace Ocelot.Configuration.Builder private List _upstreamHeaderFindAndReplace; private List _downstreamHeaderFindAndReplace; private readonly List _downstreamAddresses; - private string _upstreamHost; private string _key; private List _delegatingHandlers; private List _addHeadersToDownstream; private List _addHeadersToUpstream; private bool _dangerousAcceptAnyServerCertificateValidator; - + private SecurityOptions _securityOptions; public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); @@ -54,12 +53,6 @@ namespace Ocelot.Configuration.Builder return this; } - public DownstreamReRouteBuilder WithUpstreamHost(string upstreamAddresses) - { - _upstreamHost = upstreamAddresses; - return this; - } - public DownstreamReRouteBuilder WithLoadBalancerOptions(LoadBalancerOptions loadBalancerOptions) { _loadBalancerOptions = loadBalancerOptions; @@ -234,6 +227,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithSecurityOptions(SecurityOptions securityOptions) + { + _securityOptions = securityOptions; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -265,7 +264,8 @@ namespace Ocelot.Configuration.Builder _delegatingHandlers, _addHeadersToDownstream, _addHeadersToUpstream, - _dangerousAcceptAnyServerCertificateValidator); + _dangerousAcceptAnyServerCertificateValidator, + _securityOptions); } } } diff --git a/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs index 525f653e..53fc54c7 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs @@ -1,39 +1,46 @@ -namespace Ocelot.Configuration.Builder -{ - public class ReRouteOptionsBuilder - { - private bool _isAuthenticated; - private bool _isAuthorised; - private bool _isCached; - private bool _enableRateLimiting; - - public ReRouteOptionsBuilder WithIsCached(bool isCached) - { - _isCached = isCached; - return this; - } - - public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) - { - _isAuthenticated = isAuthenticated; - return this; - } - - public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised) - { - _isAuthorised = isAuthorised; - return this; - } - - public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting) - { - _enableRateLimiting = enableRateLimiting; - return this; - } - - public ReRouteOptions Build() - { - return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _enableRateLimiting); - } - } -} +namespace Ocelot.Configuration.Builder +{ + public class ReRouteOptionsBuilder + { + private bool _isAuthenticated; + private bool _isAuthorised; + private bool _isCached; + private bool _enableRateLimiting; + private bool _useServiceDiscovery; + + public ReRouteOptionsBuilder WithIsCached(bool isCached) + { + _isCached = isCached; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) + { + _isAuthenticated = isAuthenticated; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised) + { + _isAuthorised = isAuthorised; + return this; + } + + public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting) + { + _enableRateLimiting = enableRateLimiting; + return this; + } + + public ReRouteOptionsBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + + public ReRouteOptions Build() + { + return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _enableRateLimiting, _useServiceDiscovery); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs new file mode 100644 index 00000000..ed43f598 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -0,0 +1,50 @@ +namespace Ocelot.Configuration.Creator +{ + using System.Collections.Generic; + using System.Linq; + using Builder; + using File; + + public class AggregatesCreator : IAggregatesCreator + { + private readonly IUpstreamTemplatePatternCreator _creator; + + public AggregatesCreator(IUpstreamTemplatePatternCreator creator) + { + _creator = creator; + } + + public List Create(FileConfiguration fileConfiguration, List reRoutes) + { + return fileConfiguration.Aggregates + .Select(aggregate => SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration)) + .Where(aggregate => aggregate != null) + .ToList(); + } + + private ReRoute SetUpAggregateReRoute(IEnumerable reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + { + var applicableReRoutes = reRoutes + .SelectMany(x => x.DownstreamReRoute) + .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) + .ToList(); + + if (applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + { + return null; + } + + var upstreamTemplatePattern = _creator.Create(aggregateReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) + .WithUpstreamPathTemplate(upstreamTemplatePattern) + .WithDownstreamReRoutes(applicableReRoutes) + .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .WithAggregator(aggregateReRoute.Aggregator) + .Build(); + + return reRoute; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ConfigurationCreator.cs new file mode 100644 index 00000000..e6fe13ac --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ConfigurationCreator.cs @@ -0,0 +1,55 @@ +namespace Ocelot.Configuration.Creator +{ + using System; + using System.Collections.Generic; + using File; + using DependencyInjection; + using Microsoft.Extensions.DependencyInjection; + + public class ConfigurationCreator : IConfigurationCreator + { + private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; + private readonly IQoSOptionsCreator _qosOptionsCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private readonly IAdministrationPath _adminPath; + private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator; + + public ConfigurationCreator( + IServiceProviderConfigurationCreator serviceProviderConfigCreator, + IQoSOptionsCreator qosOptionsCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator, + IServiceProvider serviceProvider, + ILoadBalancerOptionsCreator loadBalancerOptionsCreator + ) + { + _adminPath = serviceProvider.GetService(); + _loadBalancerOptionsCreator = loadBalancerOptionsCreator; + _serviceProviderConfigCreator = serviceProviderConfigCreator; + _qosOptionsCreator = qosOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; + } + + public InternalConfiguration Create(FileConfiguration fileConfiguration, List reRoutes) + { + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); + + var lbOptions = _loadBalancerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); + + var qosOptions = _qosOptionsCreator.Create(fileConfiguration.GlobalConfiguration.QoSOptions); + + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions); + + var adminPath = _adminPath != null ? _adminPath.Path : null; + + return new InternalConfiguration(reRoutes, + adminPath, + serviceProviderConfiguration, + fileConfiguration.GlobalConfiguration.RequestIdKey, + lbOptions, + fileConfiguration.GlobalConfiguration.DownstreamScheme, + qosOptions, + httpHandlerOptions + ); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/DynamicsCreator.cs b/src/Ocelot/Configuration/Creator/DynamicsCreator.cs new file mode 100644 index 00000000..96d56287 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/DynamicsCreator.cs @@ -0,0 +1,42 @@ +namespace Ocelot.Configuration.Creator +{ + using System.Collections.Generic; + using System.Linq; + using Builder; + using File; + + public class DynamicsCreator : IDynamicsCreator + { + private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; + + public DynamicsCreator(IRateLimitOptionsCreator rateLimitOptionsCreator) + { + _rateLimitOptionsCreator = rateLimitOptionsCreator; + } + + public List Create(FileConfiguration fileConfiguration) + { + return fileConfiguration.DynamicReRoutes + .Select(dynamic => SetUpDynamicReRoute(dynamic, fileConfiguration.GlobalConfiguration)) + .ToList(); + } + + private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration) + { + var rateLimitOption = _rateLimitOptionsCreator + .Create(fileDynamicReRoute.RateLimitRule, globalConfiguration); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(rateLimitOption.EnableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .WithServiceName(fileDynamicReRoute.ServiceName) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + return reRoute; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 1acbe0fb..f59f8a80 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -1,84 +1,35 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Ocelot.Cache; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.DependencyInjection; -using Ocelot.Logging; -using Ocelot.Responses; -using Microsoft.Extensions.DependencyInjection; - namespace Ocelot.Configuration.Creator { - using LoadBalancer.LoadBalancers; + using System.Linq; + using System.Threading.Tasks; + using File; + using Validator; + using Responses; - /// - /// Register as singleton - /// public class FileInternalConfigurationCreator : IInternalConfigurationCreator { private readonly IConfigurationValidator _configurationValidator; - private readonly IOcelotLogger _logger; - private readonly IClaimsToThingCreator _claimsToThingCreator; - private readonly IAuthenticationOptionsCreator _authOptionsCreator; - private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private readonly IRequestIdKeyCreator _requestIdKeyCreator; - private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; - private readonly IQoSOptionsCreator _qosOptionsCreator; - private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; - private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; - private readonly IRegionCreator _regionCreator; - private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private readonly IAdministrationPath _adminPath; - private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; - private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; + private readonly IConfigurationCreator _configCreator; + private readonly IDynamicsCreator _dynamicsCreator; + private readonly IReRoutesCreator _reRoutesCreator; + private readonly IAggregatesCreator _aggregatesCreator; public FileInternalConfigurationCreator( IConfigurationValidator configurationValidator, - IOcelotLoggerFactory loggerFactory, - IClaimsToThingCreator claimsToThingCreator, - IAuthenticationOptionsCreator authOptionsCreator, - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IRequestIdKeyCreator requestIdKeyCreator, - IServiceProviderConfigurationCreator serviceProviderConfigCreator, - IQoSOptionsCreator qosOptionsCreator, - IReRouteOptionsCreator fileReRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator, - IRegionCreator regionCreator, - IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IServiceProvider serviceProvider, - IHeaderFindAndReplaceCreator headerFAndRCreator, - IDownstreamAddressesCreator downstreamAddressesCreator + IReRoutesCreator reRoutesCreator, + IAggregatesCreator aggregatesCreator, + IDynamicsCreator dynamicsCreator, + IConfigurationCreator configCreator ) { - _downstreamAddressesCreator = downstreamAddressesCreator; - _headerFAndRCreator = headerFAndRCreator; - _adminPath = serviceProvider.GetService(); - _regionCreator = regionCreator; - _rateLimitOptionsCreator = rateLimitOptionsCreator; - _requestIdKeyCreator = requestIdKeyCreator; - _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; - _authOptionsCreator = authOptionsCreator; + _configCreator = configCreator; + _dynamicsCreator = dynamicsCreator; + _aggregatesCreator = aggregatesCreator; + _reRoutesCreator = reRoutesCreator; _configurationValidator = configurationValidator; - _logger = loggerFactory.CreateLogger(); - _claimsToThingCreator = claimsToThingCreator; - _serviceProviderConfigCreator = serviceProviderConfigCreator; - _qosOptionsCreator = qosOptionsCreator; - _fileReRouteOptionsCreator = fileReRouteOptionsCreator; - _httpHandlerOptionsCreator = httpHandlerOptionsCreator; - } - - public async Task> Create(FileConfiguration fileConfiguration) - { - var config = await SetUpConfiguration(fileConfiguration); - return config; } - private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) + public async Task> Create(FileConfiguration fileConfiguration) { var response = await _configurationValidator.IsValid(fileConfiguration); @@ -87,197 +38,20 @@ namespace Ocelot.Configuration.Creator return new ErrorResponse(response.Data.Errors); } - var reRoutes = new List(); + var reRoutes = _reRoutesCreator.Create(fileConfiguration); - foreach (var reRoute in fileConfiguration.ReRoutes) - { - var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); + var aggregates = _aggregatesCreator.Create(fileConfiguration, reRoutes); - var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); - - reRoutes.Add(ocelotReRoute); - } + var dynamicReRoute = _dynamicsCreator.Create(fileConfiguration); - foreach (var aggregate in fileConfiguration.Aggregates) - { - var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); - reRoutes.Add(ocelotReRoute); - } + var mergedReRoutes = reRoutes + .Union(aggregates) + .Union(dynamicReRoute) + .ToList(); - foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes) - { - var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration); - reRoutes.Add(reRoute); - } - - var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); - - var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); - - var qosOptions = _qosOptionsCreator.Create(fileConfiguration.GlobalConfiguration.QoSOptions); - - var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions); - - var adminPath = _adminPath != null ? _adminPath.Path : null; - - var config = new InternalConfiguration(reRoutes, - adminPath, - serviceProviderConfiguration, - fileConfiguration.GlobalConfiguration.RequestIdKey, - lbOptions, - fileConfiguration.GlobalConfiguration.DownstreamScheme, - qosOptions, - httpHandlerOptions - ); + var config = _configCreator.Create(fileConfiguration, mergedReRoutes); return new OkResponse(config); } - - private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration) - { - var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithEnableRateLimiting(true) - .WithRateLimitOptions(rateLimitOption) - .WithServiceName(fileDynamicReRoute.ServiceName) - .Build(); - - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build(); - - return reRoute; - } - - private ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) - { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); - - if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) - { - //todo - log or throw or return error whatever? - } - - //make another re route out of these - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) - .WithUpstreamPathTemplate(upstreamTemplatePattern) - .WithDownstreamReRoutes(applicableReRoutes) - .WithUpstreamHost(aggregateReRoute.UpstreamHost) - .WithAggregator(aggregateReRoute.Aggregator) - .Build(); - - return reRoute; - } - - private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) - { - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamPathTemplate(upstreamTemplatePattern) - .WithDownstreamReRoute(downstreamReRoutes) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .Build(); - - return reRoute; - } - - private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); - - var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); - - var reRouteKey = CreateReRouteKey(fileReRoute); - - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); - - var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); - - var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); - - var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); - - var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray()); - - var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration); - - var region = _regionCreator.Create(fileReRoute); - - var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute.HttpHandlerOptions); - - var hAndRs = _headerFAndRCreator.Create(fileReRoute); - - var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); - - var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions); - - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName); - - var reRoute = new DownstreamReRouteBuilder() - .WithKey(fileReRoute.Key) - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamPathTemplate(upstreamTemplatePattern) - .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(fileReRouteOptions.IsAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(fileReRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancerOptions(lbOptions) - .WithDownstreamAddresses(downstreamAddresses) - .WithLoadBalancerKey(reRouteKey) - .WithQosOptions(qosOptions) - .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) - .WithRateLimitOptions(rateLimitOption) - .WithHttpHandlerOptions(httpHandlerOptions) - .WithServiceName(fileReRoute.ServiceName) - .WithUseServiceDiscovery(useServiceDiscovery) - .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) - .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) - .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) - .WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator) - .Build(); - - return reRoute; - } - - private LoadBalancerOptions CreateLoadBalancerOptions(FileLoadBalancerOptions options) - { - return new LoadBalancerOptionsBuilder() - .WithType(options.Type) - .WithKey(options.Key) - .WithExpiryInMs(options.Expiry) - .Build(); - } - - private string CreateReRouteKey(FileReRoute fileReRoute) - { - if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type) && !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key) && fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions)) - { - return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}"; - } - - return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}|{string.Join(",", fileReRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}"; - } } } diff --git a/src/Ocelot/Configuration/Creator/IAggregatesCreator.cs b/src/Ocelot/Configuration/Creator/IAggregatesCreator.cs new file mode 100644 index 00000000..1586a2be --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IAggregatesCreator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IAggregatesCreator + { + List Create(FileConfiguration fileConfiguration, List reRoutes); + } +} diff --git a/src/Ocelot/Configuration/Creator/IConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IConfigurationCreator.cs new file mode 100644 index 00000000..aed2db54 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IConfigurationCreator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IConfigurationCreator + { + InternalConfiguration Create(FileConfiguration fileConfiguration, List reRoutes); + } +} diff --git a/src/Ocelot/Configuration/Creator/IDynamicsCreator.cs b/src/Ocelot/Configuration/Creator/IDynamicsCreator.cs new file mode 100644 index 00000000..f6c7e16c --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IDynamicsCreator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IDynamicsCreator + { + List Create(FileConfiguration fileConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/ILoadBalancerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ILoadBalancerOptionsCreator.cs new file mode 100644 index 00000000..387a0ca7 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ILoadBalancerOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface ILoadBalancerOptionsCreator + { + LoadBalancerOptions Create(FileLoadBalancerOptions options); + } +} diff --git a/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs index 3ad42d9f..77ada9c6 100644 --- a/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs @@ -1,11 +1,12 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IQoSOptionsCreator - { - QoSOptions Create(FileQoSOptions options); - QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods); - QoSOptions Create(QoSOptions options, string pathTemplate, string[] httpMethods); - } -} +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IQoSOptionsCreator + { + QoSOptions Create(FileQoSOptions options); + QoSOptions Create(FileQoSOptions options, string pathTemplate, List httpMethods); + QoSOptions Create(QoSOptions options, string pathTemplate, List httpMethods); + } +} diff --git a/src/Ocelot/Configuration/Creator/IReRouteKeyCreator.cs b/src/Ocelot/Configuration/Creator/IReRouteKeyCreator.cs new file mode 100644 index 00000000..05f78354 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IReRouteKeyCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IReRouteKeyCreator + { + string Create(FileReRoute fileReRoute); + } +} diff --git a/src/Ocelot/Configuration/Creator/IReRoutesCreator.cs b/src/Ocelot/Configuration/Creator/IReRoutesCreator.cs new file mode 100644 index 00000000..608e2af5 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IReRoutesCreator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IReRoutesCreator + { + List Create(FileConfiguration fileConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs new file mode 100644 index 00000000..792c8530 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ISecurityOptionsCreator.cs @@ -0,0 +1,12 @@ +using Ocelot.Configuration.File; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.Creator +{ + public interface ISecurityOptionsCreator + { + SecurityOptions Create(FileSecurityOptions securityOptions); + } +} diff --git a/src/Ocelot/Configuration/Creator/LoadBalancerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/LoadBalancerOptionsCreator.cs new file mode 100644 index 00000000..c7770c24 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/LoadBalancerOptionsCreator.cs @@ -0,0 +1,17 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + + public class LoadBalancerOptionsCreator : ILoadBalancerOptionsCreator + { + public LoadBalancerOptions Create(FileLoadBalancerOptions options) + { + return new LoadBalancerOptionsBuilder() + .WithType(options.Type) + .WithKey(options.Key) + .WithExpiryInMs(options.Expiry) + .Build(); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs index 6d0af409..27ae2544 100644 --- a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs @@ -1,47 +1,48 @@ -namespace Ocelot.Configuration.Creator -{ - using Ocelot.Configuration.Builder; - using Ocelot.Configuration.File; - using System.Linq; - - public class QoSOptionsCreator : IQoSOptionsCreator - { - public QoSOptions Create(FileQoSOptions options) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(options.ExceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(options.DurationOfBreak) - .WithTimeoutValue(options.TimeoutValue) - .Build(); - } - - public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods) - { - var key = CreateKey(pathTemplate, httpMethods); - - return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); - } - - public QoSOptions Create(QoSOptions options, string pathTemplate, string[] httpMethods) - { - var key = CreateKey(pathTemplate, httpMethods); - - return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); - } - - private QoSOptions Map(string key, int timeoutValue, int durationOfBreak, int exceptionsAllowedBeforeBreaking) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(exceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(durationOfBreak) - .WithTimeoutValue(timeoutValue) - .WithKey(key) - .Build(); - } - - private string CreateKey(string pathTemplate, string[] httpMethods) - { - return $"{pathTemplate.FirstOrDefault()}|{string.Join(",", httpMethods)}"; - } - } -} +namespace Ocelot.Configuration.Creator +{ + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.File; + using System.Collections.Generic; + using System.Linq; + + public class QoSOptionsCreator : IQoSOptionsCreator + { + public QoSOptions Create(FileQoSOptions options) + { + return new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(options.ExceptionsAllowedBeforeBreaking) + .WithDurationOfBreak(options.DurationOfBreak) + .WithTimeoutValue(options.TimeoutValue) + .Build(); + } + + public QoSOptions Create(FileQoSOptions options, string pathTemplate, List httpMethods) + { + var key = CreateKey(pathTemplate, httpMethods); + + return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); + } + + public QoSOptions Create(QoSOptions options, string pathTemplate, List httpMethods) + { + var key = CreateKey(pathTemplate, httpMethods); + + return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); + } + + private QoSOptions Map(string key, int timeoutValue, int durationOfBreak, int exceptionsAllowedBeforeBreaking) + { + return new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(exceptionsAllowedBeforeBreaking) + .WithDurationOfBreak(durationOfBreak) + .WithTimeoutValue(timeoutValue) + .WithKey(key) + .Build(); + } + + private string CreateKey(string pathTemplate, List httpMethods) + { + return $"{pathTemplate.FirstOrDefault()}|{string.Join(",", httpMethods)}"; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index 10e63f6f..8f300dd2 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -1,5 +1,4 @@ -using System; -using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; namespace Ocelot.Configuration.Creator @@ -8,11 +7,9 @@ namespace Ocelot.Configuration.Creator { public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration) { - RateLimitOptions rateLimitOption = null; - if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting) { - rateLimitOption = new RateLimitOptionsBuilder() + return new RateLimitOptionsBuilder() .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) .WithClientWhiteList(fileRateLimitRule.ClientWhitelist) .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) @@ -26,7 +23,7 @@ namespace Ocelot.Configuration.Creator .Build(); } - return rateLimitOption; + return new RateLimitOptionsBuilder().WithEnableRateLimiting(false).Build(); } } } diff --git a/src/Ocelot/Configuration/Creator/ReRouteKeyCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteKeyCreator.cs new file mode 100644 index 00000000..4c7d276d --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ReRouteKeyCreator.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Ocelot.Configuration.File; +using Ocelot.LoadBalancer.LoadBalancers; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteKeyCreator : IReRouteKeyCreator + { + public string Create(FileReRoute fileReRoute) + { + if (IsStickySession(fileReRoute)) + { + return $"{nameof(CookieStickySessions)}:{fileReRoute.LoadBalancerOptions.Key}"; + } + + return $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}|{string.Join(",", fileReRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}"; + } + + private bool IsStickySession(FileReRoute fileReRoute) + { + if (!string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Type) + && !string.IsNullOrEmpty(fileReRoute.LoadBalancerOptions.Key) + && fileReRoute.LoadBalancerOptions.Type == nameof(CookieStickySessions)) + { + return true; + } + + return false; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index 3a8d1811..1eb4e372 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -11,12 +11,14 @@ namespace Ocelot.Configuration.Creator var isAuthorised = IsAuthorised(fileReRoute); var isCached = IsCached(fileReRoute); var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName); var options = new ReRouteOptionsBuilder() .WithIsAuthenticated(isAuthenticated) .WithIsAuthorised(isAuthorised) .WithIsCached(isCached) .WithRateLimiting(enableRateLimiting) + .WithUseServiceDiscovery(useServiceDiscovery) .Build(); return options; diff --git a/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs b/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs new file mode 100644 index 00000000..d2ec6619 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ReRoutesCreator.cs @@ -0,0 +1,156 @@ +namespace Ocelot.Configuration.Creator +{ + using System.Collections.Generic; + using System.Linq; + using Cache; + using Builder; + using File; + + public class ReRoutesCreator : IReRoutesCreator + { + private readonly ILoadBalancerOptionsCreator _loadBalancerOptionsCreator; + private readonly IClaimsToThingCreator _claimsToThingCreator; + private readonly IAuthenticationOptionsCreator _authOptionsCreator; + private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IRequestIdKeyCreator _requestIdKeyCreator; + private readonly IQoSOptionsCreator _qosOptionsCreator; + private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; + private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; + private readonly IRegionCreator _regionCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; + private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; + private readonly IReRouteKeyCreator _reRouteKeyCreator; + private readonly ISecurityOptionsCreator _securityOptionsCreator; + + public ReRoutesCreator( + IClaimsToThingCreator claimsToThingCreator, + IAuthenticationOptionsCreator authOptionsCreator, + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IRequestIdKeyCreator requestIdKeyCreator, + IQoSOptionsCreator qosOptionsCreator, + IReRouteOptionsCreator fileReRouteOptionsCreator, + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator, + IHeaderFindAndReplaceCreator headerFAndRCreator, + IDownstreamAddressesCreator downstreamAddressesCreator, + ILoadBalancerOptionsCreator loadBalancerOptionsCreator, + IReRouteKeyCreator reRouteKeyCreator, + ISecurityOptionsCreator securityOptionsCreator + ) + { + _reRouteKeyCreator = reRouteKeyCreator; + _loadBalancerOptionsCreator = loadBalancerOptionsCreator; + _downstreamAddressesCreator = downstreamAddressesCreator; + _headerFAndRCreator = headerFAndRCreator; + _regionCreator = regionCreator; + _rateLimitOptionsCreator = rateLimitOptionsCreator; + _requestIdKeyCreator = requestIdKeyCreator; + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _authOptionsCreator = authOptionsCreator; + _claimsToThingCreator = claimsToThingCreator; + _qosOptionsCreator = qosOptionsCreator; + _fileReRouteOptionsCreator = fileReRouteOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; + _loadBalancerOptionsCreator = loadBalancerOptionsCreator; + _securityOptionsCreator = securityOptionsCreator; + } + + public List Create(FileConfiguration fileConfiguration) + { + return fileConfiguration.ReRoutes + .Select(reRoute => + { + var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); + return SetUpReRoute(reRoute, downstreamReRoute); + }) + .ToList(); + } + + private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); + + var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); + + var reRouteKey = _reRouteKeyCreator.Create(fileReRoute); + + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); + + var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); + + var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); + + var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); + + var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod); + + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration); + + var region = _regionCreator.Create(fileReRoute); + + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute.HttpHandlerOptions); + + var hAndRs = _headerFAndRCreator.Create(fileReRoute); + + var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); + + var lbOptions = _loadBalancerOptionsCreator.Create(fileReRoute.LoadBalancerOptions); + + var securityOptions = _securityOptionsCreator.Create(fileReRoute.SecurityOptions); + + var reRoute = new DownstreamReRouteBuilder() + .WithKey(fileReRoute.Key) + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamPathTemplate(upstreamTemplatePattern) + .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(fileReRouteOptions.IsAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(fileReRouteOptions.IsCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancerOptions(lbOptions) + .WithDownstreamAddresses(downstreamAddresses) + .WithLoadBalancerKey(reRouteKey) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .WithHttpHandlerOptions(httpHandlerOptions) + .WithServiceName(fileReRoute.ServiceName) + .WithUseServiceDiscovery(fileReRouteOptions.UseServiceDiscovery) + .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) + .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) + .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) + .WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator) + .WithSecurityOptions(securityOptions) + .Build(); + + return reRoute; + } + + private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) + { + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamPathTemplate(upstreamTemplatePattern) + .WithDownstreamReRoute(downstreamReRoutes) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .Build(); + + return reRoute; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs new file mode 100644 index 00000000..d1ee3508 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/SecurityOptionsCreator.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class SecurityOptionsCreator : ISecurityOptionsCreator + { + public SecurityOptions Create(FileSecurityOptions securityOptions) + { + return new SecurityOptions(securityOptions.IPAllowedList, securityOptions.IPBlockedList); + } + } +} diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 7e3c707c..b8ec926c 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -30,12 +30,13 @@ namespace Ocelot.Configuration bool isAuthenticated, bool isAuthorised, AuthenticationOptions authenticationOptions, - DownstreamPathTemplate downstreamDownstreamPathTemplate, + DownstreamPathTemplate downstreamPathTemplate, string loadBalancerKey, List delegatingHandlers, List addHeadersToDownstream, List addHeadersToUpstream, - bool dangerousAcceptAnyServerCertificateValidator) + bool dangerousAcceptAnyServerCertificateValidator, + SecurityOptions securityOptions) { DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; @@ -63,9 +64,10 @@ namespace Ocelot.Configuration IsAuthenticated = isAuthenticated; IsAuthorised = isAuthorised; AuthenticationOptions = authenticationOptions; - DownstreamDownstreamPathTemplate = downstreamDownstreamPathTemplate; + DownstreamPathTemplate = downstreamPathTemplate; LoadBalancerKey = loadBalancerKey; AddHeadersToUpstream = addHeadersToUpstream; + SecurityOptions = securityOptions; } public string Key { get; } @@ -91,11 +93,12 @@ namespace Ocelot.Configuration public bool IsAuthenticated { get; } public bool IsAuthorised { get; } public AuthenticationOptions AuthenticationOptions { get; } - public DownstreamPathTemplate DownstreamDownstreamPathTemplate { get; } + public DownstreamPathTemplate DownstreamPathTemplate { get; } public string LoadBalancerKey { get; } public List DelegatingHandlers { get; } public List AddHeadersToDownstream { get; } public List AddHeadersToUpstream { get; } public bool DangerousAcceptAnyServerCertificateValidator { get; } + public SecurityOptions SecurityOptions { get; } } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index f03156e4..1a8f2a1a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -21,6 +21,7 @@ namespace Ocelot.Configuration.File DownstreamHostAndPorts = new List(); DelegatingHandlers = new List(); LoadBalancerOptions = new FileLoadBalancerOptions(); + SecurityOptions = new FileSecurityOptions(); Priority = 1; } @@ -50,5 +51,6 @@ namespace Ocelot.Configuration.File public int Priority { get;set; } public int Timeout { get; set; } public bool DangerousAcceptAnyServerCertificateValidator { get; set; } + public FileSecurityOptions SecurityOptions { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileSecurityOptions.cs b/src/Ocelot/Configuration/File/FileSecurityOptions.cs new file mode 100644 index 00000000..1f383a5b --- /dev/null +++ b/src/Ocelot/Configuration/File/FileSecurityOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.File +{ + public class FileSecurityOptions + { + public FileSecurityOptions() + { + IPAllowedList = new List(); + IPBlockedList = new List(); + } + + public List IPAllowedList { get; set; } + + public List IPBlockedList { get; set; } + } +} diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 3eb907eb..83309c27 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -24,7 +24,7 @@ public string TimeoutStrategy { get; } - public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0; + public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 || TimeoutValue > 0; public string Key { get; } } diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs index 074e45b6..e2382d5c 100644 --- a/src/Ocelot/Configuration/ReRouteOptions.cs +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -2,17 +2,19 @@ namespace Ocelot.Configuration { public class ReRouteOptions { - public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting, bool useServiceDiscovery) { IsAuthenticated = isAuthenticated; IsAuthorised = isAuthorised; IsCached = isCached; EnableRateLimiting = isEnableRateLimiting; + UseServiceDiscovery = useServiceDiscovery; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } public bool IsCached { get; private set; } public bool EnableRateLimiting { get; private set; } + public bool UseServiceDiscovery { get; private set; } } } diff --git a/src/Ocelot/Configuration/SecurityOptions.cs b/src/Ocelot/Configuration/SecurityOptions.cs new file mode 100644 index 00000000..88d4d08a --- /dev/null +++ b/src/Ocelot/Configuration/SecurityOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration +{ + public class SecurityOptions + { + public SecurityOptions(List allowedList, List blockedList) + { + this.IPAllowedList = allowedList; + this.IPBlockedList = blockedList; + } + + public List IPAllowedList { get; private set; } + + public List IPBlockedList { get; private set; } + } +} diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index d3409ee8..02b5a17b 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -9,6 +9,7 @@ namespace Ocelot.DependencyInjection using System.Text.RegularExpressions; using Configuration.File; using Newtonsoft.Json; + using Microsoft.AspNetCore.Hosting; public static class ConfigurationBuilderExtensions { @@ -28,37 +29,42 @@ namespace Ocelot.DependencyInjection return builder; } - public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, IHostingEnvironment env) { - return builder.AddOcelot("."); + return builder.AddOcelot(".", env); } - public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder) + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder, IHostingEnvironment env) { - const string pattern = "(?i)ocelot\\.([a-zA-Z0-9]*)(\\.json)$"; + const string primaryConfigFile = "ocelot.json"; - var reg = new Regex(pattern); + const string globalConfigFile = "ocelot.global.json"; - var files = Directory.GetFiles(folder) - .Where(path => reg.IsMatch(path)) + const string subConfigPattern = @"^ocelot\.[a-zA-Z0-9]+\.json$"; + + string excludeConfigName = env?.EnvironmentName != null ? $"ocelot.{env.EnvironmentName}.json" : string.Empty; + + var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); + + var files = new DirectoryInfo(folder) + .EnumerateFiles() + .Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName)) .ToList(); var fileConfiguration = new FileConfiguration(); foreach (var file in files) { - // windows and unix sigh... - if(files.Count > 1 && (Path.GetFileName(file) == "ocelot.json")) + if(files.Count > 1 && file.Name.Equals(primaryConfigFile, StringComparison.OrdinalIgnoreCase)) { continue; } - var lines = File.ReadAllText(file); + var lines = File.ReadAllText(file.FullName); var config = JsonConvert.DeserializeObject(lines); - // windows and unix sigh... - if (Path.GetFileName(file) == "ocelot.global.json") + if (file.Name.Equals(globalConfigFile, StringComparison.OrdinalIgnoreCase)) { fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; } @@ -69,9 +75,9 @@ namespace Ocelot.DependencyInjection var json = JsonConvert.SerializeObject(fileConfiguration); - File.WriteAllText("ocelot.json", json); + File.WriteAllText(primaryConfigFile, json); - builder.AddJsonFile("ocelot.json", false, false); + builder.AddJsonFile(primaryConfigFile, false, false); return builder; } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index d2f017c1..c90d0ed9 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -35,6 +35,8 @@ namespace Ocelot.DependencyInjection using Ocelot.Infrastructure; using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Creator; + using Ocelot.Security.IPSecurity; + using Ocelot.Security; public class OcelotBuilder : IOcelotBuilder { @@ -55,10 +57,16 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -86,8 +94,8 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -108,7 +116,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.AddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -119,6 +127,9 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); + //add security + this.AddSecurity(); + //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; @@ -133,22 +144,28 @@ namespace Ocelot.DependencyInjection Services.AddWebEncoders(); } - public IOcelotBuilder AddSingletonDefinedAggregator() + public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { Services.AddSingleton(); return this; } - public IOcelotBuilder AddTransientDefinedAggregator() + public IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator { Services.AddTransient(); return this; } - public IOcelotBuilder AddDelegatingHandler(bool global = false) - where THandler : DelegatingHandler + private void AddSecurity() + { + Services.TryAddSingleton(); + Services.TryAddSingleton(); + } + + public IOcelotBuilder AddDelegatingHandler(bool global = false) + where THandler : DelegatingHandler { if(global) { diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index f2c8628b..b032555d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -42,7 +42,7 @@ return downstreamRoute; } - var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); + var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new List { upstreamHttpMethod }); var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index cf777056..c9b534ed 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -13,17 +13,14 @@ namespace Ocelot.DownstreamRouteFinder.Middleware { private readonly OcelotRequestDelegate _next; private readonly IDownstreamRouteProviderFactory _factory; - private readonly IInternalConfigurationRepository _repo; private readonly IMultiplexer _multiplexer; public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IDownstreamRouteProviderFactory downstreamRouteFinder, - IInternalConfigurationRepository repo, IMultiplexer multiplexer) :base(loggerFactory.CreateLogger()) { - _repo = repo; _multiplexer = multiplexer; _next = next; _factory = downstreamRouteFinder; @@ -51,7 +48,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware return; } - var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamDownstreamPathTemplate.Value)); + var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index e8efc60d..02b40c07 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -27,7 +27,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public async Task Invoke(DownstreamContext context) { var response = _replacer - .Replace(context.DownstreamReRoute.DownstreamDownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); + .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); if (response.IsError) { diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs index 8597a1a6..76588596 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs @@ -8,11 +8,11 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { var downstreamPath = new StringBuilder(); - downstreamPath.Append(downstreamDownstreamPathTemplate.Value); + downstreamPath.Append(downstreamPathTemplate.Value); foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs index 8455d048..dbfeaa89 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs @@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public interface IDownstreamPathPlaceholderReplacer { - Response Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index a7b5b689..d24f549a 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -1,60 +1,83 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using System.Net.Http; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.Creator; -using Ocelot.Request.Middleware; - -namespace Ocelot.Headers -{ - public class AddHeadersToRequest : IAddHeadersToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddHeadersToRequest(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest) - { - foreach (var config in claimsToThings) - { - var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = downstreamRequest.Headers.FirstOrDefault(x => x.Key == config.ExistingKey); - - if (!string.IsNullOrEmpty(exists.Key)) - { - downstreamRequest.Headers.Remove(exists.Key); - } - - downstreamRequest.Headers.Add(config.ExistingKey, value.Data); - } - - return new OkResponse(); - } - - public void SetHeadersOnDownstreamRequest(IEnumerable headers, HttpContext context) - { - var requestHeader = context.Request.Headers; - foreach (var header in headers) - { - if (requestHeader.ContainsKey(header.Key)) - { - requestHeader.Remove(header.Key); - } - - requestHeader.Add(header.Key, header.Value); - } - } - } -} +namespace Ocelot.Headers +{ + using System.Collections.Generic; + using System.Linq; + using Infrastructure; + using Logging; + using Ocelot.Configuration; + using Ocelot.Infrastructure.Claims.Parser; + using Ocelot.Responses; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Primitives; + using Ocelot.Configuration.Creator; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + + public class AddHeadersToRequest : IAddHeadersToRequest + { + private readonly IClaimsParser _claimsParser; + private readonly IPlaceholders _placeholders; + private readonly IOcelotLogger _logger; + + public AddHeadersToRequest(IClaimsParser claimsParser, IPlaceholders placeholders, IOcelotLoggerFactory factory) + { + _logger = factory.CreateLogger(); + _claimsParser = claimsParser; + _placeholders = placeholders; + } + + public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest) + { + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = downstreamRequest.Headers.FirstOrDefault(x => x.Key == config.ExistingKey); + + if (!string.IsNullOrEmpty(exists.Key)) + { + downstreamRequest.Headers.Remove(exists.Key); + } + + downstreamRequest.Headers.Add(config.ExistingKey, value.Data); + } + + return new OkResponse(); + } + + public void SetHeadersOnDownstreamRequest(IEnumerable headers, HttpContext context) + { + var requestHeader = context.Request.Headers; + + foreach (var header in headers) + { + if (requestHeader.ContainsKey(header.Key)) + { + requestHeader.Remove(header.Key); + } + + if (header.Value.StartsWith("{") && header.Value.EndsWith("}")) + { + var value = _placeholders.Get(header.Value); + + if (value.IsError) + { + _logger.LogWarning($"Unable to add header to response {header.Key}: {header.Value}"); + continue; + } + + requestHeader.Add(header.Key, new StringValues(value.Data)); + } + else + { + requestHeader.Add(header.Key, header.Value); + } + } + } + } +} diff --git a/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs b/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs index 02779ca6..2323258a 100644 --- a/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Headers -{ - public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer - { - public Response Replace(HttpContext context, List fAndRs) - { - foreach (var f in fAndRs) - { - if(context.Request.Headers.TryGetValue(f.Key, out var values)) - { - var replaced = values[f.Index].Replace(f.Find, f.Replace); - context.Request.Headers.Remove(f.Key); - context.Request.Headers.Add(f.Key, replaced); - } - } - - return new OkResponse(); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Headers +{ + public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer + { + public Response Replace(HttpContext context, List fAndRs) + { + foreach (var f in fAndRs) + { + if(context.Request.Headers.TryGetValue(f.Key, out var values)) + { + var replaced = values[f.Index].Replace(f.Find, f.Replace); + context.Request.Headers.Remove(f.Key); + context.Request.Headers.Add(f.Key, replaced); + } + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index d1fb295f..eb88af7a 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -1,15 +1,13 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.Configuration; -using Ocelot.Infrastructure; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; -using Ocelot.Responses; - namespace Ocelot.Headers { + using System.Collections.Generic; + using System.Linq; + using Ocelot.Configuration; + using Ocelot.Infrastructure; + using Ocelot.Infrastructure.Extensions; + using Ocelot.Middleware; + using Ocelot.Responses; + public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer { private readonly IPlaceholders _placeholders; @@ -19,8 +17,11 @@ namespace Ocelot.Headers _placeholders = placeholders; } - public Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest request) + public Response Replace(DownstreamContext context, List fAndRs) { + var response = context.DownstreamResponse; + var request = context.DownstreamRequest; + foreach (var f in fAndRs) { var dict = response.Headers.ToDictionary(x => x.Key); diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index b871ac19..7a180196 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,14 +1,12 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; -using Ocelot.Responses; - namespace Ocelot.Headers { + using System.Collections.Generic; + using Ocelot.Configuration; + using Ocelot.Middleware; + using Ocelot.Responses; + public interface IHttpResponseHeaderReplacer { - Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest httpRequestMessage); + Response Replace(DownstreamContext context, List fAndRs); } } diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs similarity index 78% rename from src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs rename to src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs index 12332cb2..1faf4fa6 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs @@ -1,47 +1,47 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Headers.Middleware -{ - public class HttpRequestHeadersBuilderMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddHeadersToRequest _addHeadersToRequest; - - public HttpRequestHeadersBuilderMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddHeadersToRequest addHeadersToRequest) - :base(loggerFactory.CreateLogger()) - { - _next = next; - _addHeadersToRequest = addHeadersToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToHeaders.Any()) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to headers"); - - var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); - - if (response.IsError) - { - Logger.LogWarning("Error setting headers on context, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - - Logger.LogInformation("headers have been set on context"); - } - - await _next.Invoke(context); - } - } -} +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Headers.Middleware +{ + public class ClaimsToHeadersMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IAddHeadersToRequest _addHeadersToRequest; + + public ClaimsToHeadersMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddHeadersToRequest addHeadersToRequest) + :base(loggerFactory.CreateLogger()) + { + _next = next; + _addHeadersToRequest = addHeadersToRequest; + } + + public async Task Invoke(DownstreamContext context) + { + if (context.DownstreamReRoute.ClaimsToHeaders.Any()) + { + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); + + var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); + + if (response.IsError) + { + Logger.LogWarning("Error setting headers on context, setting pipeline error"); + + SetPipelineError(context, response.Errors); + return; + } + + Logger.LogInformation("headers have been set on context"); + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs new file mode 100644 index 00000000..91a5fe90 --- /dev/null +++ b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware.Pipeline; + +namespace Ocelot.Headers.Middleware +{ + public static class ClaimsToHeadersMiddlewareExtensions + { + public static IOcelotPipelineBuilder UseClaimsToHeadersMiddleware(this IOcelotPipelineBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index bead85c5..4ac0fc8c 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -45,7 +45,7 @@ namespace Ocelot.Headers.Middleware var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; - _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); + _postReplacer.Replace(context, postFAndRs); _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); } diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs deleted file mode 100644 index 69f23860..00000000 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddlewareExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Headers.Middleware -{ - public static class HttpRequestHeadersBuilderMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseHttpRequestHeadersBuilderMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } -} diff --git a/src/Ocelot/Infrastructure/Placeholders.cs b/src/Ocelot/Infrastructure/Placeholders.cs index c43dea14..3aecec31 100644 --- a/src/Ocelot/Infrastructure/Placeholders.cs +++ b/src/Ocelot/Infrastructure/Placeholders.cs @@ -1,47 +1,38 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Responses; - namespace Ocelot.Infrastructure { + using System; + using System.Collections.Generic; + using Microsoft.AspNetCore.Http; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + public class Placeholders : IPlaceholders { private readonly Dictionary>> _placeholders; private readonly Dictionary> _requestPlaceholders; private readonly IBaseUrlFinder _finder; private readonly IRequestScopedDataRepository _repo; + private readonly IHttpContextAccessor _httpContextAccessor; - public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo) + public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo, IHttpContextAccessor httpContextAccessor) { _repo = repo; + _httpContextAccessor = httpContextAccessor; _finder = finder; - _placeholders = new Dictionary>>(); - _placeholders.Add("{BaseUrl}", () => new OkResponse(_finder.Find())); - _placeholders.Add("{TraceId}", () => { - var traceId = _repo.Get("TraceId"); - if(traceId.IsError) - { - return new ErrorResponse(traceId.Errors); - } + _placeholders = new Dictionary>> + { + { "{BaseUrl}", GetBaseUrl() }, + { "{TraceId}", GetTraceId() }, + { "{RemoteIpAddress}", GetRemoteIpAddress() } - return new OkResponse(traceId.Data); - }); + }; - _requestPlaceholders = new Dictionary>(); - _requestPlaceholders.Add("{DownstreamBaseUrl}", x => { - var downstreamUrl = $"{x.Scheme}://{x.Host}"; - - if(x.Port != 80 && x.Port != 443) - { - downstreamUrl = $"{downstreamUrl}:{x.Port}"; - } - - return $"{downstreamUrl}/"; - }); + _requestPlaceholders = new Dictionary> + { + { "{DownstreamBaseUrl}", GetDownstreamBaseUrl() } + }; } public Response Get(string key) @@ -67,5 +58,56 @@ namespace Ocelot.Infrastructure return new ErrorResponse(new CouldNotFindPlaceholderError(key)); } + + private Func> GetRemoteIpAddress() + { + return () => + { + // this can blow up so adding try catch and return error + try + { + var remoteIdAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(); + return new OkResponse(remoteIdAddress); + } + catch (Exception e) + { + return new ErrorResponse(new CouldNotFindPlaceholderError("{RemoteIpAddress}")); + } + }; + } + + private Func GetDownstreamBaseUrl() + { + return x => + { + var downstreamUrl = $"{x.Scheme}://{x.Host}"; + + if (x.Port != 80 && x.Port != 443) + { + downstreamUrl = $"{downstreamUrl}:{x.Port}"; + } + + return $"{downstreamUrl}/"; + }; + } + + private Func> GetTraceId() + { + return () => + { + var traceId = _repo.Get("TraceId"); + if (traceId.IsError) + { + return new ErrorResponse(traceId.Errors); + } + + return new OkResponse(traceId.Data); + }; + } + + private Func> GetBaseUrl() + { + return () => new OkResponse(_finder.Find()); + } } } diff --git a/src/Ocelot/Middleware/DownstreamResponse.cs b/src/Ocelot/Middleware/DownstreamResponse.cs index 19345735..c58a5198 100644 --- a/src/Ocelot/Middleware/DownstreamResponse.cs +++ b/src/Ocelot/Middleware/DownstreamResponse.cs @@ -7,25 +7,27 @@ namespace Ocelot.Middleware { public class DownstreamResponse { - public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List
headers) + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List
headers, string reasonPhrase) { Content = content; StatusCode = statusCode; Headers = headers ?? new List
(); + ReasonPhrase = reasonPhrase; } public DownstreamResponse(HttpResponseMessage response) - :this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList()) + :this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList(), response.ReasonPhrase) { } - public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable>> headers) - :this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList()) + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable>> headers, string reasonPhrase) + :this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList(), reasonPhrase) { } public HttpContent Content { get; } public HttpStatusCode StatusCode { get; } public List
Headers { get; } + public string ReasonPhrase {get;} } } diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index bfc1351f..ef2a00f5 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -46,7 +46,7 @@ namespace Ocelot.Middleware.Multiplexer Headers = {ContentType = new MediaTypeHeaderValue("application/json")} }; - originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>()); + originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>(), "cannot return from aggregate..which reason phrase would you use?"); } private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index 1153ccd5..a35dc41f 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -15,6 +15,7 @@ using Ocelot.Request.Middleware; using Ocelot.Requester.Middleware; using Ocelot.RequestId.Middleware; using Ocelot.Responder.Middleware; +using Ocelot.Security.Middleware; using Ocelot.WebSockets.Middleware; namespace Ocelot.Middleware.Pipeline @@ -48,6 +49,9 @@ namespace Ocelot.Middleware.Pipeline // Then we get the downstream route information builder.UseDownstreamRouteFinderMiddleware(); + // This security module, IP whitelist blacklist, extended security mechanism + builder.UseSecurityMiddleware(); + //Expand other branch pipes if (pipelineConfiguration.MapWhenOcelotPipeline != null) { @@ -87,7 +91,7 @@ namespace Ocelot.Middleware.Pipeline } // The next thing we do is look at any claims transforms in case this is important for authorisation - builder.UseClaimsBuilderMiddleware(); + builder.UseClaimsToClaimsMiddleware(); // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); @@ -105,14 +109,14 @@ namespace Ocelot.Middleware.Pipeline builder.Use(pipelineConfiguration.AuthorisationMiddleware); } - // Now we can run any header transformation logic - builder.UseHttpRequestHeadersBuilderMiddleware(); + // Now we can run the claims to headers transformation middleware + builder.UseClaimsToHeadersMiddleware(); // Allow the user to implement their own query string manipulation logic builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware); - // Now we can run any query string transformation logic - builder.UseQueryStringBuilderMiddleware(); + // Now we can run any claims to query string transformation middleware + builder.UseClaimsToQueryStringMiddleware(); // Get the load balancer for this request builder.UseLoadBalancingMiddleware(); diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs similarity index 63% rename from src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs rename to src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs index d5ad5002..6af29464 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs @@ -1,45 +1,45 @@ -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.QueryStrings.Middleware -{ - public class QueryStringBuilderMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddQueriesToRequest _addQueriesToRequest; - - public QueryStringBuilderMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddQueriesToRequest addQueriesToRequest) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _addQueriesToRequest = addQueriesToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToQueries.Any()) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to queries"); - - var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); - - if (response.IsError) - { - Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} +namespace Ocelot.QueryStrings.Middleware +{ + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Middleware; + + public class ClaimsToQueryStringMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IAddQueriesToRequest _addQueriesToRequest; + + public ClaimsToQueryStringMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddQueriesToRequest addQueriesToRequest) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _addQueriesToRequest = addQueriesToRequest; + } + + public async Task Invoke(DownstreamContext context) + { + if (context.DownstreamReRoute.ClaimsToQueries.Any()) + { + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); + + var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); + + if (response.IsError) + { + Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); + + SetPipelineError(context, response.Errors); + return; + } + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs new file mode 100644 index 00000000..e54dd976 --- /dev/null +++ b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs @@ -0,0 +1,13 @@ +namespace Ocelot.QueryStrings.Middleware +{ + using Microsoft.AspNetCore.Builder; + using Ocelot.Middleware.Pipeline; + + public static class ClaimsToQueryStringMiddlewareExtensions + { + public static IOcelotPipelineBuilder UseClaimsToQueryStringMiddleware(this IOcelotPipelineBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs deleted file mode 100644 index 0a32f727..00000000 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddlewareExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.QueryStrings.Middleware -{ - public static class QueryStringBuilderMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseQueryStringBuilderMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } -} diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index fea51733..c32fdfb9 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -34,7 +34,7 @@ namespace Ocelot.RateLimit.Middleware // check if rate limiting is enabled if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) { - Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value}"); + Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}"); await _next.Invoke(context); return; } @@ -45,7 +45,7 @@ namespace Ocelot.RateLimit.Middleware // check white list if (IsWhitelisted(identity, options)) { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} is white listed from rate limiting"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting"); await _next.Invoke(context); return; } diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs index b5e5174b..46462d04 100644 --- a/src/Ocelot/RateLimit/RateLimitCounter.cs +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json; namespace Ocelot.RateLimit { @@ -10,10 +11,11 @@ namespace Ocelot.RateLimit /// public struct RateLimitCounter { - public RateLimitCounter(DateTime timestamp, long totalRequest) + [JsonConstructor] + public RateLimitCounter(DateTime timestamp, long totalRequests) { Timestamp = timestamp; - TotalRequests = totalRequest; + TotalRequests = totalRequests; } public DateTime Timestamp { get; private set; } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 651cb2d3..6194a33f 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -51,7 +51,7 @@ namespace Ocelot.Requester handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true; _logger - .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamDownstreamPathTemplate: {context.DownstreamReRoute.DownstreamDownstreamPathTemplate}"); + .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}"); } var timeout = context.DownstreamReRoute.QosOptions.TimeoutValue == 0 diff --git a/src/Ocelot/Requester/QoS/QosFactory.cs b/src/Ocelot/Requester/QoS/QosFactory.cs index 8916d6f2..37211f4b 100644 --- a/src/Ocelot/Requester/QoS/QosFactory.cs +++ b/src/Ocelot/Requester/QoS/QosFactory.cs @@ -27,7 +27,7 @@ namespace Ocelot.Requester.QoS return new OkResponse(handler(request, _ocelotLoggerFactory)); } - return new ErrorResponse(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamDownstreamPathTemplate}")); + return new ErrorResponse(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}")); } } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index b5f4e69e..0e964c7a 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; using Ocelot.Headers; using Ocelot.Middleware; @@ -43,14 +44,9 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); } - context.Response.OnStarting(state => - { - var httpContext = (HttpContext)state; + SetStatusCode(context, (int)response.StatusCode); - httpContext.Response.StatusCode = (int)response.StatusCode; - - return Task.CompletedTask; - }, context); + context.Response.HttpContext.Features.Get().ReasonPhrase = response.ReasonPhrase; using(content) { @@ -63,7 +59,15 @@ namespace Ocelot.Responder public void SetErrorResponseOnContext(HttpContext context, int statusCode) { - context.Response.StatusCode = statusCode; + SetStatusCode(context, statusCode); + } + + private void SetStatusCode(HttpContext context, int statusCode) + { + if (!context.Response.HasStarted) + { + context.Response.StatusCode = statusCode; + } } private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs new file mode 100644 index 00000000..bbf97778 --- /dev/null +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Middleware; +using Ocelot.Responses; + +namespace Ocelot.Security.IPSecurity +{ + public class IPSecurityPolicy : ISecurityPolicy + { + public async Task Security(DownstreamContext context) + { + IPAddress clientIp = context.HttpContext.Connection.RemoteIpAddress; + SecurityOptions securityOptions = context.DownstreamReRoute.SecurityOptions; + if (securityOptions == null) + { + return new OkResponse(); + } + + if (securityOptions.IPBlockedList != null) + { + if (securityOptions.IPBlockedList.Exists(f => f == clientIp.ToString())) + { + var error = new UnauthenticatedError($" This request rejects access to {clientIp.ToString()} IP"); + return new ErrorResponse(error); + } + } + + if (securityOptions.IPAllowedList != null && securityOptions.IPAllowedList.Count > 0) + { + if (!securityOptions.IPAllowedList.Exists(f => f == clientIp.ToString())) + { + var error = new UnauthenticatedError($"{clientIp.ToString()} does not allow access, the request is invalid"); + return new ErrorResponse(error); + } + } + + return await Task.FromResult(new OkResponse()); + } + } +} diff --git a/src/Ocelot/Security/ISecurityPolicy.cs b/src/Ocelot/Security/ISecurityPolicy.cs new file mode 100644 index 00000000..2b3457ec --- /dev/null +++ b/src/Ocelot/Security/ISecurityPolicy.cs @@ -0,0 +1,14 @@ +using Ocelot.Middleware; +using Ocelot.Responses; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Ocelot.Security +{ + public interface ISecurityPolicy + { + Task Security(DownstreamContext context); + } +} diff --git a/src/Ocelot/Security/Middleware/SecurityMiddleware.cs b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs new file mode 100644 index 00000000..1665ba3b --- /dev/null +++ b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs @@ -0,0 +1,43 @@ +using Ocelot.Logging; +using Ocelot.Middleware; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Ocelot.Security.Middleware +{ + public class SecurityMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IEnumerable _securityPolicies; + public SecurityMiddleware(IOcelotLoggerFactory loggerFactory, + IEnumerable securityPolicies, + OcelotRequestDelegate next) + : base(loggerFactory.CreateLogger()) + { + _logger = loggerFactory.CreateLogger(); + _securityPolicies = securityPolicies; + _next = next; + } + + public async Task Invoke(DownstreamContext context) + { + if (_securityPolicies != null) + { + foreach (var policie in _securityPolicies) + { + var result = await policie.Security(context); + if (!result.IsError) + { + continue; + } + + this.SetPipelineError(context, result.Errors); + return; + } + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs b/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs new file mode 100644 index 00000000..1e454d3a --- /dev/null +++ b/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs @@ -0,0 +1,15 @@ +using Ocelot.Middleware.Pipeline; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Security.Middleware +{ + public static class SecurityMiddlewareExtensions + { + public static IOcelotPipelineBuilder UseSecurityMiddleware(this IOcelotPipelineBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs index 033653ca..cb599b35 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -1,4 +1,9 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Modified https://github.com/aspnet/Proxy websockets class to use in Ocelot. + using System; +using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -10,7 +15,10 @@ namespace Ocelot.WebSockets.Middleware { public class WebSocketsProxyMiddleware : OcelotMiddleware { - private OcelotRequestDelegate _next; + private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Connection", "Host", "Upgrade", "Sec-WebSocket-Accept", "Sec-WebSocket-Protocol", "Sec-WebSocket-Key", "Sec-WebSocket-Version", "Sec-WebSocket-Extensions" }; + private const int DefaultWebSocketBufferSize = 4096; + private const int StreamCopyBufferSize = 81920; + private readonly OcelotRequestDelegate _next; public WebSocketsProxyMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) @@ -19,6 +27,37 @@ namespace Ocelot.WebSockets.Middleware _next = next; } + private static async Task PumpWebSocket(WebSocket source, WebSocket destination, int bufferSize, CancellationToken cancellationToken) + { + if (bufferSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + } + + var buffer = new byte[bufferSize]; + while (true) + { + WebSocketReceiveResult result; + try + { + result = await source.ReceiveAsync(new ArraySegment(buffer), cancellationToken); + } + catch (OperationCanceledException) + { + await destination.CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, null, cancellationToken); + return; + } + + if (result.MessageType == WebSocketMessageType.Close) + { + await destination.CloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken); + return; + } + + await destination.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken); + } + } + public async Task Invoke(DownstreamContext context) { await Proxy(context.HttpContext, context.DownstreamRequest.ToUri()); @@ -26,88 +65,42 @@ namespace Ocelot.WebSockets.Middleware private async Task Proxy(HttpContext context, string serverEndpoint) { - var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); - - var wsToDownstreamService = new ClientWebSocket(); - - foreach (var requestHeader in context.Request.Headers) + if (context == null) { - // Do not copy the Sec-Websocket headers because it is specified by the own connection it will fail when you copy this one. - if (requestHeader.Key.StartsWith("Sec-WebSocket")) - { - continue; - } - wsToDownstreamService.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + throw new ArgumentNullException(nameof(context)); } - var uri = new Uri(serverEndpoint); - await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None); - - var receiveFromUpstreamSendToDownstream = Task.Run(async () => + if (serverEndpoint == null) { - var buffer = new byte[1024 * 4]; + throw new ArgumentNullException(nameof(serverEndpoint)); + } - var receiveSegment = new ArraySegment(buffer); - - while (wsToUpstreamClient.State == WebSocketState.Open || wsToUpstreamClient.State == WebSocketState.CloseSent) - { - var result = await wsToUpstreamClient.ReceiveAsync(receiveSegment, CancellationToken.None); - - var sendSegment = new ArraySegment(buffer, 0, result.Count); - - if(result.MessageType == WebSocketMessageType.Close) - { - await wsToUpstreamClient.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", - CancellationToken.None); - - await wsToDownstreamService.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", - CancellationToken.None); - - break; - } - - await wsToDownstreamService.SendAsync(sendSegment, result.MessageType, result.EndOfMessage, - CancellationToken.None); - - if (wsToUpstreamClient.State != WebSocketState.Open) - { - await wsToDownstreamService.CloseAsync(WebSocketCloseStatus.Empty, "", - CancellationToken.None); - break; - } - } - }); - - var receiveFromDownstreamAndSendToUpstream = Task.Run(async () => + if (!context.WebSockets.IsWebSocketRequest) { - var buffer = new byte[1024 * 4]; + throw new InvalidOperationException(); + } - while (wsToDownstreamService.State == WebSocketState.Open || wsToDownstreamService.State == WebSocketState.CloseSent) + var client = new ClientWebSocket(); + foreach (var protocol in context.WebSockets.WebSocketRequestedProtocols) + { + client.Options.AddSubProtocol(protocol); + } + + foreach (var headerEntry in context.Request.Headers) + { + if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase)) { - if (wsToUpstreamClient.State != WebSocketState.Open) - { - break; - } - else - { - var receiveSegment = new ArraySegment(buffer); - var result = await wsToDownstreamService.ReceiveAsync(receiveSegment, CancellationToken.None); - - if (result.MessageType == WebSocketMessageType.Close) - { - break; - } - - var sendSegment = new ArraySegment(buffer, 0, result.Count); - - //send to upstream client - await wsToUpstreamClient.SendAsync(sendSegment, result.MessageType, result.EndOfMessage, - CancellationToken.None); - } + client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value); } - }); + } - await Task.WhenAll(receiveFromDownstreamAndSendToUpstream, receiveFromUpstreamSendToDownstream); + var destinationUri = new Uri(serverEndpoint); + await client.ConnectAsync(destinationUri, context.RequestAborted); + using (var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol)) + { + var bufferSize = DefaultWebSocketBufferSize; + await Task.WhenAll(PumpWebSocket(client, server, bufferSize, context.RequestAborted), PumpWebSocket(server, client, bufferSize, context.RequestAborted)); + } } } } diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index f28d6bc7..a98fd3ef 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -1,446 +1,565 @@ -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.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class AggregateTests : IDisposable - { - private readonly Steps _steps; - private string _downstreamPathOne; - private string _downstreamPathTwo; - private readonly ServiceHandler _serviceHandler; - - public AggregateTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url_user_defined_aggregate() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51885, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51886, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - }, - Aggregator = "FakeDefinedAggregator" - } - } - }; - - var expected = "Bye from Laura, Bye from Tom"; - - this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51875, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51886, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; - - this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51875", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_one_service_404() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51881, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51882, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - var expected = "{\"Laura\":,\"Tom\":{Hello from Tom}}"; - - this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51881", "/", 404, "")) - .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51882", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_both_service_404() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51883, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51884, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - var expected = "{\"Laura\":,\"Tom\":}"; - - this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51883", "/", 404, "")) - .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51884", "/", 404, "")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_be_thread_safe() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51878", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51880", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIMakeLotsOfDifferentRequestsToTheApiGateway()) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if (_downstreamPathOne != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } - - private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if (_downstreamPathTwo != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) - { - _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); - _downstreamPathTwo.ShouldBe(expectedDownstreamPath); - } - - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - } - } - - public class FakeDepdendency - { - } - - public class FakeDefinedAggregator : IDefinedAggregator - { - private readonly FakeDepdendency _dep; - - public FakeDefinedAggregator(FakeDepdendency dep) - { - _dep = dep; - } - - public async Task Aggregate(List responses) - { - var one = await responses[0].Content.ReadAsStringAsync(); - var two = await responses[1].Content.ReadAsStringAsync(); - - var merge = $"{one}, {two}"; - merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); - var headers = responses.SelectMany(x => x.Headers).ToList(); - return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers); - } - } -} +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.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class AggregateTests : IDisposable + { + private readonly Steps _steps; + private string _downstreamPathOne; + private string _downstreamPathTwo; + private readonly ServiceHandler _serviceHandler; + + public AggregateTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_fix_issue_597() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key1data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8571 + } + }, + Key = "key1" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key2data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8571 + } + }, + Key = "key2" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key3data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8571 + } + }, + Key = "key3" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key4data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 8571 + } + }, + Key = "key4" + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List{ + "key1", + "key2", + "key3", + "key4" + }, + UpstreamPathTemplate = "/EmpDetail/IN/{userid}" + }, + new FileAggregateReRoute + { + ReRouteKeys = new List{ + "key1", + "key2", + }, + UpstreamPathTemplate = "/EmpDetail/US/{userid}" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "CorrelationID" + } + }; + + var expected = "{\"key1\":some_data,\"key2\":some_data}"; + + this.Given(x => x.GivenServiceIsRunning("http://localhost:8571", 200, "some_data")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EmpDetail/US/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_user_defined_aggregate() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51885, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51886, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + Aggregator = "FakeDefinedAggregator" + } + } + }; + + var expected = "Bye from Laura, Bye from Tom"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51875, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51886, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51875", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_one_service_404() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51881, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51882, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":,\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51881", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51882", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_both_service_404() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51883, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51884, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + var expected = "{\"Laura\":,\"Tom\":}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51883", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51884", "/", 404, "")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_be_thread_safe() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51878", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51880", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIMakeLotsOfDifferentRequestsToTheApiGateway()) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + private void GivenServiceIsRunning(string baseUrl, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPathOne != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } + + private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPathTwo != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) + { + _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); + _downstreamPathTwo.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + } + } + + public class FakeDepdendency + { + } + + public class FakeDefinedAggregator : IDefinedAggregator + { + private readonly FakeDepdendency _dep; + + public FakeDefinedAggregator(FakeDepdendency dep) + { + _dep = dep; + } + + public async Task Aggregate(List responses) + { + var one = await responses[0].Content.ReadAsStringAsync(); + var two = await responses[1].Content.ReadAsStringAsync(); + + var merge = $"{one}, {two}"; + merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); + var headers = responses.SelectMany(x => x.Headers).ToList(); + return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index c31c8868..839157e5 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -4,6 +4,7 @@ namespace Ocelot.AcceptanceTests using System.Collections.Generic; using System.Linq; using System.Net; + using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; diff --git a/test/Ocelot.AcceptanceTests/ReasonPhraseTests.cs b/test/Ocelot.AcceptanceTests/ReasonPhraseTests.cs new file mode 100644 index 00000000..14742b86 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ReasonPhraseTests.cs @@ -0,0 +1,76 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Net; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Features; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ReasonPhraseTests : IDisposable + { + private readonly Steps _steps; + private string _contentType; + private long? _contentLength; + private bool _contentTypeHeaderExists; + private readonly ServiceHandler _serviceHandler; + + public ReasonPhraseTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_return_reason_phrase() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51339, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51339", "/", "some reason")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(_ => _steps.ThenTheReasonPhraseIs("some reason")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string reasonPhrase) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + context.Response.HttpContext.Features.Get().ReasonPhrase = reasonPhrase; + + await context.Response.WriteAsync("YOYO!"); + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 274f1554..8472b474 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -452,6 +452,11 @@ header.First().ShouldBe(value); } + public void ThenTheReasonPhraseIs(string expected) + { + _response.ReasonPhrase.ShouldBe(expected); + } + /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// diff --git a/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs b/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs new file mode 100644 index 00000000..484e91d3 --- /dev/null +++ b/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using System.Net.Http; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes.Jobs; +using Ocelot.Configuration.Repository; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Errors.Middleware; +using Microsoft.Extensions.DependencyInjection; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.DownstreamRouteFinder.Finder; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Configuration; + +namespace Ocelot.Benchmarks +{ + [SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)] + [Config(typeof(DownstreamRouteFinderMiddlewareBenchmarks))] + public class DownstreamRouteFinderMiddlewareBenchmarks : ManualConfig + { + private DownstreamRouteFinderMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public DownstreamRouteFinderMiddlewareBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var serviceCollection = new ServiceCollection(); + var config = new ConfigurationRoot(new List()); + var builder = new OcelotBuilder(serviceCollection, config); + var services = serviceCollection.BuildServiceProvider(); + var loggerFactory = services.GetService(); + var drpf = services.GetService(); + var multiplexer = services.GetService(); + + _next = async context => { + await Task.CompletedTask; + throw new Exception("BOOM"); + }; + + _middleware = new DownstreamRouteFinderMiddleware(_next, loggerFactory, drpf, multiplexer); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = new PathString("/test"); + httpContext.Request.QueryString = new QueryString("?a=b"); + httpContext.Request.Headers.Add("Host", "most"); + + _downstreamContext = new DownstreamContext(httpContext) + { + Configuration = new InternalConfiguration(new List(), null, null, null, null, null, null, null) + }; + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + await _middleware.Invoke(_downstreamContext); + } + } +} diff --git a/test/Ocelot.Benchmarks/Program.cs b/test/Ocelot.Benchmarks/Program.cs index d830a46b..d642032d 100644 --- a/test/Ocelot.Benchmarks/Program.cs +++ b/test/Ocelot.Benchmarks/Program.cs @@ -1,19 +1,20 @@ -using BenchmarkDotNet.Running; - -namespace Ocelot.Benchmarks -{ - public class Program - { - public static void Main(string[] args) - { - var switcher = new BenchmarkSwitcher(new[] { - typeof(DictionaryBenchmarks), - typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), - typeof(AllTheThingsBenchmarks), - typeof(ExceptionHandlerMiddlewareBenchmarks) - }); - - switcher.Run(args); - } - } -} +using BenchmarkDotNet.Running; + +namespace Ocelot.Benchmarks +{ + public class Program + { + public static void Main(string[] args) + { + var switcher = new BenchmarkSwitcher(new[] { + typeof(DictionaryBenchmarks), + typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), + typeof(AllTheThingsBenchmarks), + typeof(ExceptionHandlerMiddlewareBenchmarks), + typeof(DownstreamRouteFinderMiddlewareBenchmarks) + }); + + switcher.Run(args); + } + } +} diff --git a/test/Ocelot.IntegrationTests/HeaderTests.cs b/test/Ocelot.IntegrationTests/HeaderTests.cs new file mode 100644 index 00000000..c5120813 --- /dev/null +++ b/test/Ocelot.IntegrationTests/HeaderTests.cs @@ -0,0 +1,199 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Ocelot.IntegrationTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Microsoft.AspNetCore.Http; + using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using System.Linq; + using System.Net; + + public class HeaderTests : IDisposable + { + private readonly HttpClient _httpClient; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private IWebHost _downstreamBuilder; + private HttpResponseMessage _response; + + public HeaderTests() + { + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5005"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_pass_remote_ip_address_if_as_x_forwarded_for_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6773, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + UpstreamHeaderTransform = new Dictionary + { + {"X-Forwarded-For", "{RemoteIpAddress}"} + }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false + } + } + } + }; + + this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:6773", 200, "X-Forwarded-For")) + .And(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenXForwardedForIsSet()) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string headerKey) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Headers.TryGetValue(headerKey, out var values)) + { + var result = values.First(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(result); + } + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + public async Task WhenIGetUrlOnTheApiGateway(string url) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + _response = await _httpClient.SendAsync(request); + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode code) + { + _response.StatusCode.ShouldBe(code); + } + + private void ThenXForwardedForIsSet() + { + var windowsOrMac = "::1"; + var linux = "127.0.0.1"; + + var header = _response.Content.ReadAsStringAsync().Result; + + bool passed = false; + + if(header == windowsOrMac || header == linux) + { + passed = true; + } + + passed.ShouldBeTrue(); + } + + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index d9ad9768..c173c82e 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -46,6 +46,20 @@ namespace Ocelot.UnitTests.Authentication .BDDfy(); } + [Fact] + public void should_call_next_middleware_if_route_is_using_options_method() + { + this.Given(x => GivenTheDownStreamRouteIs( + new DownstreamReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Options" }) + .WithIsAuthenticated(true) + .Build())) + .And(x => GivenTheRequestIsUsingOptionsMethod()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheUserIsAuthenticated()) + .BDDfy(); + } + private void WhenICallTheMiddleware() { _next = (context) => { @@ -68,9 +82,14 @@ namespace Ocelot.UnitTests.Authentication }; } + private void GivenTheRequestIsUsingOptionsMethod() + { + _downstreamContext.HttpContext.Request.Method = "OPTIONS"; + } + private void ThenTheUserIsAuthenticated() { - var content = _downstreamContext.HttpContext.Response.Body.AsString(); + var content = _downstreamContext.HttpContext.Response.Body.AsString(); content.ShouldBe("The user is authenticated"); } @@ -84,7 +103,7 @@ namespace Ocelot.UnitTests.Authentication { public static string AsString(this Stream stream) { - using(var reader = new StreamReader(stream)) + using (var reader = new StreamReader(stream)) { string text = reader.ReadToEnd(); return text; diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index d13b22f2..6f32739b 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -53,7 +53,7 @@ { "content-type", new List { "application/json" } } }; - var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders); + var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders, "some reason"); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .When(x => x.WhenICallTheMiddleware()) @@ -69,7 +69,7 @@ { "Expires", new List { "-1" } } }; - var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", contentHeaders); + var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", contentHeaders, "some reason"); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .When(x => x.WhenICallTheMiddleware()) diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs similarity index 87% rename from test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs rename to test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs index 02e9d8fc..5d96884f 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs @@ -1,89 +1,89 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Claims -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Claims; - using Ocelot.Claims.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class ClaimsBuilderMiddlewareTests - { - private readonly Mock _addHeaders; - private Mock _loggerFactory; - private Mock _logger; - private readonly ClaimsBuilderMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ClaimsBuilderMiddlewareTests() - { - _addHeaders = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new ClaimsBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); - } - - [Fact] - public void should_call_claims_to_request_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToClaims(new List - { - new ClaimToThing("sub", "UserType", "|", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddClaimsToRequestReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheAddClaimsToRequestReturns() - { - _addHeaders - .Setup(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheClaimsToRequestIsCalledCorrectly() - { - _addHeaders - .Verify(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny()), Times.Once); - } - } -} +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Claims +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Claims; + using Ocelot.Claims.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsToClaimsMiddlewareTests + { + private readonly Mock _addHeaders; + private Mock _loggerFactory; + private Mock _logger; + private readonly ClaimsToClaimsMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ClaimsToClaimsMiddlewareTests() + { + _addHeaders = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new ClaimsToClaimsMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); + } + + [Fact] + public void should_call_claims_to_request_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToClaims(new List + { + new ClaimToThing("sub", "UserType", "|", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddClaimsToRequestReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; + } + + private void GivenTheAddClaimsToRequestReturns() + { + _addHeaders + .Setup(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheClaimsToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs new file mode 100644 index 00000000..e124fe74 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/AggregatesCreatorTests.cs @@ -0,0 +1,164 @@ +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections.Generic; + using System.Net.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Values; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class AggregatesCreatorTests + { + private readonly AggregatesCreator _creator; + private readonly Mock _utpCreator; + private FileConfiguration _fileConfiguration; + private List _reRoutes; + private List _result; + private UpstreamPathTemplate _aggregate1Utp; + private UpstreamPathTemplate _aggregate2Utp; + + public AggregatesCreatorTests() + { + _utpCreator = new Mock(); + _creator = new AggregatesCreator(_utpCreator.Object); + } + + [Fact] + public void should_return_no_aggregates() + { + var fileConfig = new FileConfiguration + { + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List{"key1"} + } + } + }; + var reRoutes = new List(); + + this.Given(_ => GivenThe(fileConfig)) + .And(_ => GivenThe(reRoutes)) + .When(_ => WhenICreate()) + .Then(_ => TheUtpCreatorIsNotCalled()) + .And(_ => ThenTheResultIsNotNull()) + .And(_ => ThenTheResultIsEmpty()) + .BDDfy(); + } + + [Fact] + public void should_create_aggregates() + { + var fileConfig = new FileConfiguration + { + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List{"key1", "key2"}, + UpstreamHost = "hosty", + UpstreamPathTemplate = "templatey", + Aggregator = "aggregatory", + ReRouteIsCaseSensitive = true + }, + new FileAggregateReRoute + { + ReRouteKeys = new List{"key3", "key4"}, + UpstreamHost = "hosty", + UpstreamPathTemplate = "templatey", + Aggregator = "aggregatory", + ReRouteIsCaseSensitive = true + } + } + }; + + var reRoutes = new List + { + new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key1").Build()).Build(), + new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key2").Build()).Build(), + new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key3").Build()).Build(), + new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().WithKey("key4").Build()).Build() + }; + + this.Given(_ => GivenThe(fileConfig)) + .And(_ => GivenThe(reRoutes)) + .And(_ => GivenTheUtpCreatorReturns()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheUtpCreatorIsCalledCorrectly()) + .And(_ => ThenTheAggregatesAreCreated()) + .BDDfy(); + } + + private void ThenTheAggregatesAreCreated() + { + _result.ShouldNotBeNull(); + _result.Count.ShouldBe(2); + + _result[0].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get); + _result[0].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[0].UpstreamHost); + _result[0].UpstreamTemplatePattern.ShouldBe(_aggregate1Utp); + _result[0].Aggregator.ShouldBe(_fileConfiguration.Aggregates[0].Aggregator); + _result[0].DownstreamReRoute.ShouldContain(x => x == _reRoutes[0].DownstreamReRoute[0]); + _result[0].DownstreamReRoute.ShouldContain(x => x == _reRoutes[1].DownstreamReRoute[0]); + + _result[1].UpstreamHttpMethod.ShouldContain(x => x == HttpMethod.Get); + _result[1].UpstreamHost.ShouldBe(_fileConfiguration.Aggregates[1].UpstreamHost); + _result[1].UpstreamTemplatePattern.ShouldBe(_aggregate2Utp); + _result[1].Aggregator.ShouldBe(_fileConfiguration.Aggregates[1].Aggregator); + _result[1].DownstreamReRoute.ShouldContain(x => x == _reRoutes[2].DownstreamReRoute[0]); + _result[1].DownstreamReRoute.ShouldContain(x => x == _reRoutes[3].DownstreamReRoute[0]); + } + + private void ThenTheUtpCreatorIsCalledCorrectly() + { + _utpCreator.Verify(x => x.Create(_fileConfiguration.Aggregates[0]), Times.Once); + _utpCreator.Verify(x => x.Create(_fileConfiguration.Aggregates[1]), Times.Once); + } + + private void GivenTheUtpCreatorReturns() + { + _aggregate1Utp = new UpstreamPathTemplateBuilder().Build(); + _aggregate2Utp = new UpstreamPathTemplateBuilder().Build(); + + _utpCreator.SetupSequence(x => x.Create(It.IsAny())) + .Returns(_aggregate1Utp) + .Returns(_aggregate2Utp); + } + + private void ThenTheResultIsEmpty() + { + _result.Count.ShouldBe(0); + } + + private void ThenTheResultIsNotNull() + { + _result.ShouldNotBeNull(); + } + + private void TheUtpCreatorIsNotCalled() + { + _utpCreator.Verify(x => x.Create(It.IsAny()), Times.Never); + } + + private void GivenThe(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void GivenThe(List reRoutes) + { + _reRoutes = reRoutes; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileConfiguration, _reRoutes); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationCreatorTests.cs new file mode 100644 index 00000000..1e288265 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationCreatorTests.cs @@ -0,0 +1,124 @@ +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections.Generic; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.DependencyInjection; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ConfigurationCreatorTests + { + private ConfigurationCreator _creator; + private InternalConfiguration _result; + private readonly Mock _spcCreator; + private readonly Mock _qosCreator; + private readonly Mock _hhoCreator; + private readonly Mock _lboCreator; + private FileConfiguration _fileConfig; + private List _reRoutes; + private ServiceProviderConfiguration _spc; + private LoadBalancerOptions _lbo; + private QoSOptions _qoso; + private HttpHandlerOptions _hho; + private AdministrationPath _adminPath; + private readonly ServiceCollection _serviceCollection; + + public ConfigurationCreatorTests() + { + _lboCreator = new Mock(); + _hhoCreator = new Mock(); + _qosCreator = new Mock(); + _spcCreator = new Mock(); + _serviceCollection = new ServiceCollection(); + } + + [Fact] + public void should_build_configuration_with_no_admin_path() + { + this.Given(_ => GivenTheDependenciesAreSetUp()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDepdenciesAreCalledCorrectly()) + .And(_ => ThenThePropertiesAreSetCorrectly()) + .And(_ => ThenTheAdminPathIsNull()) + .BDDfy(); + } + + [Fact] + public void should_build_configuration_with_admin_path() + { + this.Given(_ => GivenTheDependenciesAreSetUp()) + .And(_ => GivenTheAdminPath()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDepdenciesAreCalledCorrectly()) + .And(_ => ThenThePropertiesAreSetCorrectly()) + .And(_ => ThenTheAdminPathIsSet()) + .BDDfy(); + } + + private void ThenTheAdminPathIsNull() + { + _result.AdministrationPath.ShouldBeNull(); + } + + private void ThenThePropertiesAreSetCorrectly() + { + _result.ShouldNotBeNull(); + _result.ServiceProviderConfiguration.ShouldBe(_spc); + _result.LoadBalancerOptions.ShouldBe(_lbo); + _result.QoSOptions.ShouldBe(_qoso); + _result.HttpHandlerOptions.ShouldBe(_hho); + _result.ReRoutes.ShouldBe(_reRoutes); + _result.RequestId.ShouldBe(_fileConfig.GlobalConfiguration.RequestIdKey); + _result.DownstreamScheme.ShouldBe(_fileConfig.GlobalConfiguration.DownstreamScheme); + } + + private void ThenTheAdminPathIsSet() + { + _result.AdministrationPath.ShouldBe("wooty"); + } + + private void ThenTheDepdenciesAreCalledCorrectly() + { + _spcCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration), Times.Once); + _lboCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.LoadBalancerOptions), Times.Once); + _qosCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.QoSOptions), Times.Once); + _hhoCreator.Verify(x => x.Create(_fileConfig.GlobalConfiguration.HttpHandlerOptions), Times.Once); + } + + private void GivenTheAdminPath() + { + _adminPath = new AdministrationPath("wooty"); + _serviceCollection.AddSingleton(_adminPath); + } + + private void GivenTheDependenciesAreSetUp() + { + _fileConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + }; + _reRoutes = new List(); + _spc = new ServiceProviderConfiguration("", "", 1, "", "", 1); + _lbo = new LoadBalancerOptionsBuilder().Build(); + _qoso = new QoSOptions(1, 1, 1, ""); + _hho = new HttpHandlerOptionsBuilder().Build(); + + _spcCreator.Setup(x => x.Create(It.IsAny())).Returns(_spc); + _lboCreator.Setup(x => x.Create(It.IsAny())).Returns(_lbo); + _qosCreator.Setup(x => x.Create(It.IsAny())).Returns(_qoso); + _hhoCreator.Setup(x => x.Create(It.IsAny())).Returns(_hho); + } + + private void WhenICreate() + { + var serviceProvider = _serviceCollection.BuildServiceProvider(); + _creator = new ConfigurationCreator(_spcCreator.Object, _qosCreator.Object, _hhoCreator.Object, serviceProvider, _lboCreator.Object); + _result = _creator.Create(_fileConfig, _reRoutes); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/DynamicsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/DynamicsCreatorTests.cs new file mode 100644 index 00000000..6c6beb9c --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/DynamicsCreatorTests.cs @@ -0,0 +1,126 @@ +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class DynamicsCreatorTests + { + private readonly DynamicsCreator _creator; + private readonly Mock _rloCreator; + private List _result; + private FileConfiguration _fileConfig; + private RateLimitOptions _rlo1; + private RateLimitOptions _rlo2; + + public DynamicsCreatorTests() + { + _rloCreator = new Mock(); + _creator = new DynamicsCreator(_rloCreator.Object); + } + + [Fact] + public void should_return_nothing() + { + var fileConfig = new FileConfiguration(); + + this.Given(_ => GivenThe(fileConfig)) + .When(_ => WhenICreate()) + .Then(_ => ThenNothingIsReturned()) + .And(_ => ThenTheRloCreatorIsNotCalled()) + .BDDfy(); + } + + [Fact] + public void should_return_re_routes() + { + var fileConfig = new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = "1", + RateLimitRule = new FileRateLimitRule + { + EnableRateLimiting = false + } + }, + new FileDynamicReRoute + { + ServiceName = "2", + RateLimitRule = new FileRateLimitRule + { + EnableRateLimiting = true + } + } + } + }; + + this.Given(_ => GivenThe(fileConfig)) + .And(_ => GivenTheRloCreatorReturns()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheReRoutesAreReturned()) + .And(_ => ThenTheRloCreatorIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheRloCreatorIsCalledCorrectly() + { + _rloCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[0].RateLimitRule, + _fileConfig.GlobalConfiguration), Times.Once); + + _rloCreator.Verify(x => x.Create(_fileConfig.DynamicReRoutes[1].RateLimitRule, + _fileConfig.GlobalConfiguration), Times.Once); + } + + private void ThenTheReRoutesAreReturned() + { + _result.Count.ShouldBe(2); + _result[0].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeFalse(); + _result[0].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo1); + _result[0].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[0].ServiceName); + + _result[1].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + _result[1].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo2); + _result[1].DownstreamReRoute[0].ServiceName.ShouldBe(_fileConfig.DynamicReRoutes[1].ServiceName); + } + + private void GivenTheRloCreatorReturns() + { + _rlo1 = new RateLimitOptionsBuilder().Build(); + _rlo2 = new RateLimitOptionsBuilder().WithEnableRateLimiting(true).Build(); + + _rloCreator + .SetupSequence(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(_rlo1) + .Returns(_rlo2); + } + + private void ThenTheRloCreatorIsNotCalled() + { + _rloCreator.Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Never); + } + + private void ThenNothingIsReturned() + { + _result.Count.ShouldBe(0); + } + + private void WhenICreate() + { + _result = _creator.Create(_fileConfig); + } + + private void GivenThe(FileConfiguration fileConfig) + { + _fileConfig = fileConfig; + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index ba4e1497..71d3fd9e 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -2,1115 +2,126 @@ { using System.Collections.Generic; using Moq; - using Ocelot.Cache; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Validator; - using Ocelot.Logging; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; using Ocelot.Errors; - using Ocelot.UnitTests.TestData; - using Ocelot.Values; - using System; + using System.Threading.Tasks; + using Ocelot.UnitTests.Responder; + using System.Linq; public class FileInternalConfigurationCreatorTests { private readonly Mock _validator; + private readonly Mock _reRoutesCreator; + private readonly Mock _aggregatesCreator; + private readonly Mock _dynamicsCreator; + private readonly Mock _configCreator; private Response _config; private FileConfiguration _fileConfiguration; - private readonly Mock _logger; - private readonly FileInternalConfigurationCreator _internalConfigurationCreator; - private readonly Mock _claimsToThingCreator; - private readonly Mock _authOptionsCreator; - private readonly Mock _upstreamTemplatePatternCreator; - private readonly Mock _requestIdKeyCreator; - private readonly Mock _serviceProviderConfigCreator; - private readonly Mock _qosOptionsCreator; - private readonly Mock _fileReRouteOptionsCreator; - private readonly Mock _rateLimitOptions; - private readonly Mock _regionCreator; - private readonly Mock _httpHandlerOptionsCreator; - private readonly Mock _serviceProvider; - private readonly Mock _headerFindAndReplaceCreator; - private readonly Mock _downstreamAddressesCreator; + private readonly FileInternalConfigurationCreator _creator; + private Response _result; + private List _reRoutes; + private List _aggregates; + private List _dynamics; + private InternalConfiguration _internalConfig; public FileInternalConfigurationCreatorTests() { - _logger = new Mock(); _validator = new Mock(); - _claimsToThingCreator = new Mock(); - _authOptionsCreator = new Mock(); - _upstreamTemplatePatternCreator = new Mock(); - _requestIdKeyCreator = new Mock(); - _serviceProviderConfigCreator = new Mock(); - _qosOptionsCreator = new Mock(); - _fileReRouteOptionsCreator = new Mock(); - _rateLimitOptions = new Mock(); - _regionCreator = new Mock(); - _httpHandlerOptionsCreator = new Mock(); - _serviceProvider = new Mock(); - _headerFindAndReplaceCreator = new Mock(); - _downstreamAddressesCreator = new Mock(); + _reRoutesCreator = new Mock(); + _aggregatesCreator = new Mock(); + _dynamicsCreator = new Mock(); + _configCreator = new Mock(); - _internalConfigurationCreator = new FileInternalConfigurationCreator( - _validator.Object, - _logger.Object, - _claimsToThingCreator.Object, - _authOptionsCreator.Object, - _upstreamTemplatePatternCreator.Object, - _requestIdKeyCreator.Object, - _serviceProviderConfigCreator.Object, - _qosOptionsCreator.Object, - _fileReRouteOptionsCreator.Object, - _rateLimitOptions.Object, - _regionCreator.Object, - _httpHandlerOptionsCreator.Object, - _serviceProvider.Object, - _headerFindAndReplaceCreator.Object, - _downstreamAddressesCreator.Object); + _creator = new FileInternalConfigurationCreator(_validator.Object, _reRoutesCreator.Object, _aggregatesCreator.Object, _dynamicsCreator.Object, _configCreator.Object); } [Fact] - public void should_set_up_sticky_sessions_config() + public void should_return_validation_error() { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); + var fileConfiguration = new FileConfiguration(); - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithDownstreamAddresses(new List() { new DownstreamHostAndPort("127.0.0.1", 80) }) - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithLoadBalancerKey("CookieStickySessions:sessionid") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - LoadBalancerOptions = new FileLoadBalancerOptions - { - Expiry = 10, - Key = "sessionid", - Type = "CookieStickySessions" - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) + this.Given(_ => GivenThe(fileConfiguration)) + .And(_ => GivenTheValidationFails()) + .When(_ => WhenICreate()) + .Then(_ => ThenAnErrorIsReturned()) .BDDfy(); } [Fact] - public void should_set_up_aggregate_re_route() + public void should_return_internal_configuration() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura", - UpstreamHost = "localhost" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", - UpstreamHost = "localhost", - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - }, - Aggregator = "asdf" - } - } - }; + var fileConfiguration = new FileConfiguration(); - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - var expected = new List(); - - var lauraDownstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamHost("localhost") - .WithKey("Laura") - .WithDownstreamPathTemplate("/") - .WithDownstreamScheme("http") - .WithUpstreamHttpMethod(new List() {"Get"}) - .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("localhost", 51878)}) - .WithLoadBalancerKey("/laura|Get|localhost:51878") - .Build(); - - var lauraReRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List() { "Get" }) - .WithUpstreamHost("localhost") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/laura").Build()) - .WithDownstreamReRoute(lauraDownstreamReRoute) - .Build(); - - expected.Add(lauraReRoute); - - var tomDownstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamHost("localhost") - .WithKey("Tom") - .WithDownstreamPathTemplate("/") - .WithDownstreamScheme("http") - .WithUpstreamHttpMethod(new List() { "Get" }) - .WithDownstreamAddresses(new List() { new DownstreamHostAndPort("localhost", 51878) }) - .WithLoadBalancerKey("/tom|Get|localhost:51880") - .Build(); - - var tomReRoute = new ReRouteBuilder() - .WithUpstreamHttpMethod(new List() { "Get" }) - .WithUpstreamHost("localhost") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/tom").Build()) - .WithDownstreamReRoute(tomDownstreamReRoute) - .Build(); - - expected.Add(tomReRoute); - - var aggregateReReRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/").Build()) - .WithUpstreamHost("localhost") - .WithDownstreamReRoute(lauraDownstreamReRoute) - .WithDownstreamReRoute(tomDownstreamReRoute) - .WithUpstreamHttpMethod(new List() { "Get" }) - .Build(); - - expected.Add(aggregateReReRoute); - - var tupleList = new List<(string, string)> - { - ("woop", "/laura"), - ("woop", "/laura"), - ("woop", "/tom"), - ("woop", "/tom"), - ("woop", "/"), - ("woop", "/") - }; - - this.Given(x => x.GivenTheConfigIs(configuration)) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns(tupleList.ToArray())) - .And(x => x.GivenTheFollowingOptionsAreReturned(new ReRouteOptionsBuilder().Build())) - .And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig)) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly()) - .Then(x => x.ThenTheAggregateReRoutesAre(expected)) + this.Given(_ => GivenThe(fileConfiguration)) + .And(_ => GivenTheValidationSucceeds()) + .And(_ => GivenTheDependenciesAreSetUp()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDependenciesAreCalledCorrectly()) .BDDfy(); } - [Fact] - public void should_call_service_provider_config_creator() + private void ThenTheDependenciesAreCalledCorrectly() { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = 8500, - } - } - })) - .And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig)) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly()) - .BDDfy(); + _reRoutesCreator.Verify(x => x.Create(_fileConfiguration), Times.Once); + _aggregatesCreator.Verify(x => x.Create(_fileConfiguration, _reRoutes), Times.Once); + _dynamicsCreator.Verify(x => x.Create(_fileConfiguration), Times.Once); + + var mergedReRoutes = _reRoutes + .Union(_aggregates) + .Union(_dynamics) + .ToList(); + + _configCreator.Verify(x => x.Create(_fileConfiguration, It.Is>(y => y.Count == mergedReRoutes.Count)), Times.Once); } - [Fact] - public void should_call_region_creator() + private void GivenTheDependenciesAreSetUp() { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - Region = "region" - } - } - }, - })) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingRegionIsReturned("region")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly()) - .And(x => x.ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly()) - .BDDfy(); + _reRoutes = new List { new ReRouteBuilder().Build() }; + _aggregates = new List { new ReRouteBuilder().Build() }; + _dynamics = new List { new ReRouteBuilder().Build() }; + _internalConfig = new InternalConfiguration(null, "", null, "", null, "", null, null); + + _reRoutesCreator.Setup(x => x.Create(It.IsAny())).Returns(_reRoutes); + _aggregatesCreator.Setup(x => x.Create(It.IsAny(), It.IsAny>())).Returns(_aggregates); + _dynamicsCreator.Setup(x => x.Create(It.IsAny())).Returns(_dynamics); + _configCreator.Setup(x => x.Create(It.IsAny(), It.IsAny>())).Returns(_internalConfig); } - [Fact] - public void should_call_rate_limit_options_creator() + private void GivenTheValidationSucceeds() { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly()) - .BDDfy(); + var ok = new ConfigurationValidationResult(false); + var response = new OkResponse(ok); + _validator.Setup(x => x.IsValid(It.IsAny())).ReturnsAsync(response); } - [Fact] - public void should_call_qos_options_creator() + private void ThenAnErrorIsReturned() { - var expected = new QoSOptionsBuilder() - .WithDurationOfBreak(1) - .WithExceptionsAllowedBeforeBreaking(1) - .WithTimeoutValue(1) - .Build(); - - var serviceOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 1, - DurationOfBreak = 1, - ExceptionsAllowedBeforeBreaking = 1 - } - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) - .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheQosOptionsAre(expected)) - .BDDfy(); + _result.IsError.ShouldBeTrue(); } - [Fact] - public void should_use_downstream_host() + private async Task WhenICreate() { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("127.0.0.1", 80)}) - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamHttpMethod(new List {"Get"}) - .WithLoadBalancerKey("/api/products/{productId}|Get|127.0.0.1:0") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); + _result = await _creator.Create(_fileConfiguration); } - [Fact] - public void should_use_downstream_scheme() + private void GivenTheValidationFails() { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var handlers = new List {"Polly", "Tracer"}; - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("https") - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithDelegatingHandlers(handlers) - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamScheme = "https", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - DelegatingHandlers = handlers - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); + var error = new ConfigurationValidationResult(true, new List { new AnyError() }); + var response = new OkResponse(error); + _validator.Setup(x => x.IsValid(It.IsAny())).ReturnsAsync(response); } - [Fact] - public void should_use_service_discovery_for_downstream_service_host() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithUseServiceDiscovery(true) - .WithServiceName("ProductService") - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false, - ServiceName = "ProductService" - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "127.0.0.1" - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithUseServiceDiscovery(false) - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false, - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_call_template_pattern_creator_correctly() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$", "/api/products/{productId}")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_call_request_id_creator() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithRequestIdKey("blahhhh") - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "blahhhh" - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_call_httpHandler_creator() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - var httpHandlerOptions = new HttpHandlerOptions(true, true,false, true); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - } - }, - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" } - } - }, - })) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingHttpHandlerOptionsAreReturned(httpHandlerOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheHttpHandlerOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Theory] - [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] - public void should_create_with_headers_to_extract(FileConfiguration fileConfig) - { - var reRouteOptions = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .Build(); - - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithAllowedScopes(new List()) - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithAuthenticationOptions(authenticationOptions) - .WithClaimsToHeaders(new List - { - new ClaimToThing("CustomerId", "CustomerId", "", 0), - }) - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - var expected = new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - }; - - this.Given(x => x.GivenTheConfigIs(fileConfig)) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheClaimsToThingCreatorReturns(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0) })) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(expected)) - .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Theory] - [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] - public void should_create_with_authentication_properties(FileConfiguration fileConfig) - { - var reRouteOptions = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .Build(); - - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithAllowedScopes(new List()) - .Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List {"Get"}) - .WithAuthenticationOptions(authenticationOptions) - .WithLoadBalancerKey("/api/products/{productId}|Get|") - .Build(); - - var expected = new List - { - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - }; - - this.Given(x => x.GivenTheConfigIs(fileConfig)) - .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) - .And(x => GivenTheDownstreamAddresses()) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(expected)) - .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_validation_errors() - { - var errors = new List {new FileValidationFailedError("some message")}; - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration())) - .And(x => GivenTheDownstreamAddresses()) - .And(x => x.GivenTheConfigIsInvalid(errors)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheErrorsAreReturned(errors)) - .BDDfy(); - } - - [Fact] - public void should_set_up_dynamic_re_routes() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - var rateLimitOptions = new RateLimitOptionsBuilder() - .WithRateLimitRule(new RateLimitRule("1s", 1, 1)) - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - DynamicReRoutes = new List - { - new FileDynamicReRoute - { - ServiceName = "test", - RateLimitRule = new FileRateLimitRule - { - Period = "1s", - PeriodTimespan = 1, - Limit = 1 - } - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => GivenTheRateLimitCreatorReturns(rateLimitOptions)) - .And(x => GivenTheDownstreamAddresses()) - .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheDynamicReRouteIsSetUp("test", rateLimitOptions.RateLimitRule)) - .BDDfy(); - } - - private void GivenTheRateLimitCreatorReturns(RateLimitOptions rateLimitOptions) - { - _rateLimitOptions - .Setup(x => x.Create(It.IsAny(), It.IsAny())) - .Returns(rateLimitOptions); - } - - private void GivenTheConfigIsInvalid(List errors) - { - _validator - .Setup(x => x.IsValid(It.IsAny())) - .ReturnsAsync(new OkResponse(new ConfigurationValidationResult(true, errors))); - } - - private void ThenTheErrorsAreReturned(List errors) - { - _config.IsError.ShouldBeTrue(); - _config.Errors[0].ShouldBe(errors[0]); - } - - private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions) - { - _fileReRouteOptionsCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(fileReRouteOptions); - } - - private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() - { - _rateLimitOptions - .Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Once); - } - - private void GivenTheConfigIsValid() - { - _validator - .Setup(x => x.IsValid(It.IsAny())) - .ReturnsAsync(new OkResponse(new ConfigurationValidationResult(false))); - } - - private void GivenTheConfigIs(FileConfiguration fileConfiguration) + private void GivenThe(FileConfiguration fileConfiguration) { _fileConfiguration = fileConfiguration; } - - private void WhenICreateTheConfig() - { - _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; - } - - private void ThenTheDynamicReRouteIsSetUp(string serviceName, RateLimitRule rateLimitOptions) - { - var dynamic = _config.Data.ReRoutes[0].DownstreamReRoute[0]; - dynamic.ServiceName.ShouldBe(serviceName); - dynamic.EnableEndpointEndpointRateLimiting.ShouldBeTrue(); - dynamic.RateLimitOptions.RateLimitRule.Period.ShouldBe(rateLimitOptions.Period); - dynamic.RateLimitOptions.RateLimitRule.Limit.ShouldBe(rateLimitOptions.Limit); - dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan); - } - - private void ThenTheAggregateReRoutesAre(List expectedReRoutes) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i]; - var expected = expectedReRoutes[i]; - - result.DownstreamReRoute.Count.ShouldBe(expected.DownstreamReRoute.Count); - - result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamTemplatePattern.OriginalValue.ShouldBe(expected.UpstreamTemplatePattern.OriginalValue); - result.UpstreamTemplatePattern.Template.ShouldBe(expected.UpstreamTemplatePattern.Template); - - result.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); - result.DownstreamReRoute[0].ClaimsToClaims.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToClaims.Count); - result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count); - result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); - result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); - result.DownstreamReRoute[0].LoadBalancerKey.ShouldBe(expected.DownstreamReRoute[0].LoadBalancerKey); - result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); - result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); - result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set"); - } - } - - private void ThenTheReRoutesAre(List expectedReRoutes) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i]; - var expected = expectedReRoutes[i]; - - result.DownstreamReRoute.Count.ShouldBe(expected.DownstreamReRoute.Count); - - result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamTemplatePattern.OriginalValue.ShouldBe(expected.UpstreamTemplatePattern.OriginalValue); - result.UpstreamTemplatePattern.Template.ShouldBe(expected.UpstreamTemplatePattern.Template); - - result.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); - result.DownstreamReRoute[0].ClaimsToClaims.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToClaims.Count); - result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count); - result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); - result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); - result.DownstreamReRoute[0].LoadBalancerKey.ShouldBe(expected.DownstreamReRoute[0].LoadBalancerKey); - result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); - result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); - result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set"); - } - } - - private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i].DownstreamReRoute[0].AuthenticationOptions; - var expected = expectedReRoutes[i].DownstreamReRoute[0].AuthenticationOptions; - result.AllowedScopes.ShouldBe(expected.AllowedScopes); - } - } - - private void GivenTheClaimsToThingCreatorReturns(List claimsToThing) - { - _claimsToThingCreator - .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest)) - .Returns(claimsToThing); - } - - private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions) - { - _authOptionsCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(authOptions); - } - - private void ThenTheAuthOptionsCreatorIsCalledCorrectly() - { - _authOptionsCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); - } - - private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern, string original) - { - _upstreamTemplatePatternCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(new UpstreamPathTemplate(pattern, 1, false, original)); - } - - private void GivenTheUpstreamTemplatePatternCreatorReturns(params (string pattern, string original)[] list) - { - var builder = _upstreamTemplatePatternCreator - .SetupSequence(x => x.Create(It.IsAny())); - - foreach (var p in list) - { - builder.Returns(new UpstreamPathTemplate(p.pattern, 1, false, p.original)); - } - } - - private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() - { - _requestIdKeyCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once); - } - - private void GivenTheRequestIdCreatorReturns(string requestId) - { - _requestIdKeyCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny())) - .Returns(requestId); - } - - private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions) - { - _qosOptionsCreator - .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].QoSOptions, It.IsAny(), It.IsAny())) - .Returns(qosOptions); - } - - private void ThenTheQosOptionsAre(QoSOptions qosOptions) - { - _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); - _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); - _config.Data.ReRoutes[0].DownstreamReRoute[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); - } - - private void ThenTheServiceProviderCreatorIsCalledCorrectly() - { - _serviceProviderConfigCreator - .Verify(x => x.Create(_fileConfiguration.GlobalConfiguration), Times.Once); - } - - private void ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly() - { - _headerFindAndReplaceCreator - .Verify(x => x.Create(It.IsAny()), Times.Once); - } - - private void GivenTheHeaderFindAndReplaceCreatorReturns() - { - _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List(), new List())); - } - - private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) - { - _serviceProviderConfigCreator - .Setup(x => x.Create(It.IsAny())).Returns(serviceProviderConfiguration); - } - - private void GivenTheFollowingRegionIsReturned(string region) - { - _regionCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(region); - } - - private void ThenTheRegionCreatorIsCalledCorrectly() - { - _regionCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); - } - - private void GivenTheFollowingHttpHandlerOptionsAreReturned(HttpHandlerOptions httpHandlerOptions) - { - _httpHandlerOptionsCreator.Setup(x => x.Create(It.IsAny())) - .Returns(httpHandlerOptions); - } - - private void ThenTheHttpHandlerOptionsCreatorIsCalledCorrectly() - { - _httpHandlerOptionsCreator.Verify(x => x.Create(_fileConfiguration.ReRoutes[0].HttpHandlerOptions), Times.Once()); - } - - private void GivenTheDownstreamAddresses() - { - _downstreamAddressesCreator.Setup(x => x.Create(It.IsAny())).Returns(new List()); - } } } diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index a8f8644a..6dc292f3 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -70,6 +70,28 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] + public void should_create_with_add_headers_to_request() + { + const string key = "X-Forwarded-For"; + const string value = "{RemoteIpAddress}"; + + var reRoute = new FileReRoute + { + UpstreamHeaderTransform = new Dictionary + { + {key, value}, + } + }; + + var expected = new AddHeader(key, value); + + this.Given(x => GivenTheReRoute(reRoute)) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingAddHeaderToUpstreamIsReturned(expected)) + .BDDfy(); + } + [Fact] public void should_use_base_url_placeholder() { diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 9c802961..dfa6362f 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() diff --git a/test/Ocelot.UnitTests/Configuration/LoadBalancerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/LoadBalancerOptionsCreatorTests.cs new file mode 100644 index 00000000..8429bde4 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/LoadBalancerOptionsCreatorTests.cs @@ -0,0 +1,54 @@ +namespace Ocelot.UnitTests.Configuration +{ + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class LoadBalancerOptionsCreatorTests + { + private readonly ILoadBalancerOptionsCreator _creator; + private FileLoadBalancerOptions _fileLoadBalancerOptions; + private LoadBalancerOptions _result; + + public LoadBalancerOptionsCreatorTests() + { + _creator = new LoadBalancerOptionsCreator(); + } + + [Fact] + public void should_create() + { + var fileLoadBalancerOptions = new FileLoadBalancerOptions + { + Type = "test", + Key = "west", + Expiry = 1 + }; + + this.Given(_ => GivenThe(fileLoadBalancerOptions)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheOptionsAreCreated(fileLoadBalancerOptions)) + .BDDfy(); + } + + private void ThenTheOptionsAreCreated(FileLoadBalancerOptions expected) + { + _result.Type.ShouldBe(expected.Type); + _result.Key.ShouldBe(expected.Key); + _result.ExpiryInMs.ShouldBe(expected.Expiry); + } + + private void WhenICreate() + { + _result = _creator.Create(_fileLoadBalancerOptions); + } + + private void GivenThe(FileLoadBalancerOptions fileLoadBalancerOptions) + { + _fileLoadBalancerOptions = fileLoadBalancerOptions; + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ReRouteKeyCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRouteKeyCreatorTests.cs new file mode 100644 index 00000000..f1bb26d9 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ReRouteKeyCreatorTests.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.LoadBalancer.LoadBalancers; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ReRouteKeyCreatorTests + { + private ReRouteKeyCreator _creator; + private FileReRoute _reRoute; + private string _result; + + public ReRouteKeyCreatorTests() + { + _creator = new ReRouteKeyCreator(); + } + + [Fact] + public void should_return_sticky_session_key() + { + var reRoute = new FileReRoute + { + LoadBalancerOptions = new FileLoadBalancerOptions + { + Key = "testy", + Type = nameof(CookieStickySessions) + } + }; + + this.Given(_ => GivenThe(reRoute)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheResultIs($"{nameof(CookieStickySessions)}:{reRoute.LoadBalancerOptions.Key}")) + .BDDfy(); + } + + [Fact] + public void should_return_re_route_key() + { + var reRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/product", + UpstreamHttpMethod = new List {"GET", "POST", "PUT"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 123 + }, + new FileHostAndPort + { + Host = "localhost", + Port = 123 + } + } + }; + + this.Given(_ => GivenThe(reRoute)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheResultIs($"{reRoute.UpstreamPathTemplate}|{string.Join(",", reRoute.UpstreamHttpMethod)}|{string.Join(",", reRoute.DownstreamHostAndPorts.Select(x => $"{x.Host}:{x.Port}"))}")) + .BDDfy(); + } + + private void GivenThe(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void ThenTheResultIs(string expected) + { + _result.ShouldBe(expected); + } + + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs index d23aabc0..8230d6f1 100644 --- a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs @@ -1,77 +1,80 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class ReRouteOptionsCreatorTests - { - private ReRouteOptionsCreator _creator; - private FileReRoute _reRoute; - private ReRouteOptions _result; - - public ReRouteOptionsCreatorTests() - { - _creator = new ReRouteOptionsCreator(); - } - - [Fact] - public void should_create_re_route_options() - { - var reRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - EnableRateLimiting = true - }, - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - }, - RouteClaimsRequirement = new Dictionary() - { - {"",""} - }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 1 - } - }; - - var expected = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .WithIsAuthorised(true) - .WithIsCached(true) - .WithRateLimiting(true) - .Build(); - - this.Given(x => x.GivenTheFollowing(reRoute)) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowing(FileReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenICreate() - { - _result = _creator.Create(_reRoute); - } - - private void ThenTheFollowingIsReturned(ReRouteOptions expected) - { - _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); - _result.IsAuthorised.ShouldBe(expected.IsAuthorised); - _result.IsCached.ShouldBe(expected.IsCached); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - } - } +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections.Generic; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ReRouteOptionsCreatorTests + { + private readonly ReRouteOptionsCreator _creator; + private FileReRoute _reRoute; + private ReRouteOptions _result; + + public ReRouteOptionsCreatorTests() + { + _creator = new ReRouteOptionsCreator(); + } + + [Fact] + public void should_create_re_route_options() + { + var reRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + EnableRateLimiting = true + }, + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + }, + RouteClaimsRequirement = new Dictionary() + { + {"",""} + }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 1 + }, + ServiceName = "west" + }; + + var expected = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .WithIsAuthorised(true) + .WithIsCached(true) + .WithRateLimiting(true) + .WithUseServiceDiscovery(true) + .Build(); + + this.Given(x => x.GivenTheFollowing(reRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void ThenTheFollowingIsReturned(ReRouteOptions expected) + { + _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); + _result.IsAuthorised.ShouldBe(expected.IsAuthorised); + _result.IsCached.ShouldBe(expected.IsCached); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.UseServiceDiscovery.ShouldBe(expected.UseServiceDiscovery); + } + } } diff --git a/test/Ocelot.UnitTests/Configuration/ReRoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRoutesCreatorTests.cs new file mode 100644 index 00000000..dbfcc71a --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ReRoutesCreatorTests.cs @@ -0,0 +1,276 @@ +namespace Ocelot.UnitTests.Configuration +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Moq; + using Ocelot.Cache; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Values; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ReRoutesCreatorTests + { + private ReRoutesCreator _creator; + private Mock _cthCreator; + private Mock _aoCreator; + private Mock _utpCreator; + private Mock _ridkCreator; + private Mock _qosoCreator; + private Mock _rroCreator; + private Mock _rloCreator; + private Mock _rCreator; + private Mock _hhoCreator; + private Mock _hfarCreator; + private Mock _daCreator; + private Mock _lboCreator; + private Mock _rrkCreator; + private Mock _soCreator; + private FileConfiguration _fileConfig; + private ReRouteOptions _rro; + private string _requestId; + private string _rrk; + private UpstreamPathTemplate _upt; + private AuthenticationOptions _ao; + private List _ctt; + private QoSOptions _qoso; + private RateLimitOptions _rlo; + private string _region; + private HttpHandlerOptions _hho; + private HeaderTransformations _ht; + private List _dhp; + private LoadBalancerOptions _lbo; + private List _result; + private SecurityOptions _securityOptions; + + public ReRoutesCreatorTests() + { + _cthCreator = new Mock(); + _aoCreator = new Mock(); + _utpCreator = new Mock(); + _ridkCreator = new Mock(); + _qosoCreator = new Mock(); + _rroCreator = new Mock(); + _rloCreator = new Mock(); + _rCreator = new Mock(); + _hhoCreator = new Mock(); + _hfarCreator = new Mock(); + _daCreator = new Mock(); + _lboCreator = new Mock(); + _rrkCreator = new Mock(); + _soCreator = new Mock(); + + _creator = new ReRoutesCreator( + _cthCreator.Object, + _aoCreator.Object, + _utpCreator.Object, + _ridkCreator.Object, + _qosoCreator.Object, + _rroCreator.Object, + _rloCreator.Object, + _rCreator.Object, + _hhoCreator.Object, + _hfarCreator.Object, + _daCreator.Object, + _lboCreator.Object, + _rrkCreator.Object, + _soCreator.Object + ); + } + + [Fact] + public void should_return_nothing() + { + var fileConfig = new FileConfiguration(); + + this.Given(_ => GivenThe(fileConfig)) + .When(_ => WhenICreate()) + .Then(_ => ThenNoReRoutesAreReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_re_routes() + { + var fileConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + ServiceName = "dave", + DangerousAcceptAnyServerCertificateValidator = true, + AddClaimsToRequest = new Dictionary + { + { "a","b" } + }, + AddHeadersToRequest = new Dictionary + { + { "c","d" } + }, + AddQueriesToRequest = new Dictionary + { + { "e","f" } + }, + UpstreamHttpMethod = new List { "GET", "POST" } + }, + new FileReRoute + { + ServiceName = "wave", + DangerousAcceptAnyServerCertificateValidator = false, + AddClaimsToRequest = new Dictionary + { + { "g","h" } + }, + AddHeadersToRequest = new Dictionary + { + { "i","j" } + }, + AddQueriesToRequest = new Dictionary + { + { "k","l" } + }, + UpstreamHttpMethod = new List { "PUT", "DELETE" } + } + } + }; + + this.Given(_ => GivenThe(fileConfig)) + .And(_ => GivenTheDependenciesAreSetUpCorrectly()) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDependenciesAreCalledCorrectly()) + .And(_ => ThenTheReRoutesAreCreated()) + .BDDfy(); + } + + private void ThenTheDependenciesAreCalledCorrectly() + { + ThenTheDepsAreCalledFor(_fileConfig.ReRoutes[0], _fileConfig.GlobalConfiguration); + ThenTheDepsAreCalledFor(_fileConfig.ReRoutes[1], _fileConfig.GlobalConfiguration); + } + + private void GivenTheDependenciesAreSetUpCorrectly() + { + _rro = new ReRouteOptions(false, false, false, false, false); + _requestId = "testy"; + _rrk = "besty"; + _upt = new UpstreamPathTemplateBuilder().Build(); + _ao = new AuthenticationOptionsBuilder().Build(); + _ctt = new List(); + _qoso = new QoSOptionsBuilder().Build(); + _rlo = new RateLimitOptionsBuilder().Build(); + _region = "vesty"; + _hho = new HttpHandlerOptionsBuilder().Build(); + _ht = new HeaderTransformations(new List(), new List(), new List(), new List()); + _dhp = new List(); + _lbo = new LoadBalancerOptionsBuilder().Build(); + + _rroCreator.Setup(x => x.Create(It.IsAny())).Returns(_rro); + _ridkCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_requestId); + _rrkCreator.Setup(x => x.Create(It.IsAny())).Returns(_rrk); + _utpCreator.Setup(x => x.Create(It.IsAny())).Returns(_upt); + _aoCreator.Setup(x => x.Create(It.IsAny())).Returns(_ao); + _cthCreator.Setup(x => x.Create(It.IsAny>())).Returns(_ctt); + _qosoCreator.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())).Returns(_qoso); + _rloCreator.Setup(x => x.Create(It.IsAny(), It.IsAny())).Returns(_rlo); + _rCreator.Setup(x => x.Create(It.IsAny())).Returns(_region); + _hhoCreator.Setup(x => x.Create(It.IsAny())).Returns(_hho); + _hfarCreator.Setup(x => x.Create(It.IsAny())).Returns(_ht); + _daCreator.Setup(x => x.Create(It.IsAny())).Returns(_dhp); + _lboCreator.Setup(x => x.Create(It.IsAny())).Returns(_lbo); + } + + private void ThenTheReRoutesAreCreated() + { + _result.Count.ShouldBe(2); + + ThenTheReRouteIsSet(_fileConfig.ReRoutes[0], 0); + ThenTheReRouteIsSet(_fileConfig.ReRoutes[1], 1); + } + + private void ThenNoReRoutesAreReturned() + { + _result.ShouldBeEmpty(); + } + + private void GivenThe(FileConfiguration fileConfig) + { + _fileConfig = fileConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileConfig); + } + + private void ThenTheReRouteIsSet(FileReRoute expected, int reRouteIndex) + { + _result[reRouteIndex].DownstreamReRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); + _result[reRouteIndex].DownstreamReRoute[0].IsAuthorised.ShouldBe(_rro.IsAuthorised); + _result[reRouteIndex].DownstreamReRoute[0].IsCached.ShouldBe(_rro.IsCached); + _result[reRouteIndex].DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBe(_rro.EnableRateLimiting); + _result[reRouteIndex].DownstreamReRoute[0].RequestIdKey.ShouldBe(_requestId); + _result[reRouteIndex].DownstreamReRoute[0].LoadBalancerKey.ShouldBe(_rrk); + _result[reRouteIndex].DownstreamReRoute[0].UpstreamPathTemplate.ShouldBe(_upt); + _result[reRouteIndex].DownstreamReRoute[0].AuthenticationOptions.ShouldBe(_ao); + _result[reRouteIndex].DownstreamReRoute[0].ClaimsToHeaders.ShouldBe(_ctt); + _result[reRouteIndex].DownstreamReRoute[0].ClaimsToQueries.ShouldBe(_ctt); + _result[reRouteIndex].DownstreamReRoute[0].ClaimsToClaims.ShouldBe(_ctt); + _result[reRouteIndex].DownstreamReRoute[0].QosOptions.ShouldBe(_qoso); + _result[reRouteIndex].DownstreamReRoute[0].RateLimitOptions.ShouldBe(_rlo); + _result[reRouteIndex].DownstreamReRoute[0].CacheOptions.Region.ShouldBe(_region); + _result[reRouteIndex].DownstreamReRoute[0].CacheOptions.TtlSeconds.ShouldBe(expected.FileCacheOptions.TtlSeconds); + _result[reRouteIndex].DownstreamReRoute[0].HttpHandlerOptions.ShouldBe(_hho); + _result[reRouteIndex].DownstreamReRoute[0].UpstreamHeadersFindAndReplace.ShouldBe(_ht.Upstream); + _result[reRouteIndex].DownstreamReRoute[0].DownstreamHeadersFindAndReplace.ShouldBe(_ht.Downstream); + _result[reRouteIndex].DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(_ht.AddHeadersToUpstream); + _result[reRouteIndex].DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(_ht.AddHeadersToDownstream); + _result[reRouteIndex].DownstreamReRoute[0].DownstreamAddresses.ShouldBe(_dhp); + _result[reRouteIndex].DownstreamReRoute[0].LoadBalancerOptions.ShouldBe(_lbo); + _result[reRouteIndex].DownstreamReRoute[0].UseServiceDiscovery.ShouldBe(_rro.UseServiceDiscovery); + _result[reRouteIndex].DownstreamReRoute[0].DangerousAcceptAnyServerCertificateValidator.ShouldBe(expected.DangerousAcceptAnyServerCertificateValidator); + _result[reRouteIndex].DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DelegatingHandlers); + _result[reRouteIndex].DownstreamReRoute[0].ServiceName.ShouldBe(expected.ServiceName); + _result[reRouteIndex].DownstreamReRoute[0].DownstreamScheme.ShouldBe(expected.DownstreamScheme); + _result[reRouteIndex].DownstreamReRoute[0].RouteClaimsRequirement.ShouldBe(expected.RouteClaimsRequirement); + _result[reRouteIndex].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate); + _result[reRouteIndex].DownstreamReRoute[0].Key.ShouldBe(expected.Key); + _result[reRouteIndex].UpstreamHttpMethod + .Select(x => x.Method) + .ToList() + .ShouldContain(x => x == expected.UpstreamHttpMethod[0]); + _result[reRouteIndex].UpstreamHttpMethod + .Select(x => x.Method) + .ToList() + .ShouldContain(x => x == expected.UpstreamHttpMethod[1]); + _result[reRouteIndex].UpstreamHost.ShouldBe(expected.UpstreamHost); + _result[reRouteIndex].DownstreamReRoute.Count.ShouldBe(1); + _result[reRouteIndex].UpstreamTemplatePattern.ShouldBe(_upt); + } + + private void ThenTheDepsAreCalledFor(FileReRoute fileReRoute, FileGlobalConfiguration globalConfig) + { + _rroCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _ridkCreator.Verify(x => x.Create(fileReRoute, globalConfig), Times.Once); + _rrkCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _utpCreator.Verify(x => x.Create(fileReRoute), Times.Exactly(2)); + _aoCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _cthCreator.Verify(x => x.Create(fileReRoute.AddHeadersToRequest), Times.Once); + _cthCreator.Verify(x => x.Create(fileReRoute.AddClaimsToRequest), Times.Once); + _cthCreator.Verify(x => x.Create(fileReRoute.AddQueriesToRequest), Times.Once); + _qosoCreator.Verify(x => x.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod)); + _rloCreator.Verify(x => x.Create(fileReRoute.RateLimitOptions, globalConfig), Times.Once); + _rCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _hhoCreator.Verify(x => x.Create(fileReRoute.HttpHandlerOptions), Times.Once); + _hfarCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _daCreator.Verify(x => x.Create(fileReRoute), Times.Once); + _lboCreator.Verify(x => x.Create(fileReRoute.LoadBalancerOptions), Times.Once); + _soCreator.Verify(x => x.Create(fileReRoute.SecurityOptions), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs new file mode 100644 index 00000000..220dadcd --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/SecurityOptionsCreatorTests.cs @@ -0,0 +1,72 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class SecurityOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private SecurityOptions _result; + private ISecurityOptionsCreator _creator; + + public SecurityOptionsCreatorTests() + { + _creator = new SecurityOptionsCreator(); + } + + [Fact] + public void should_create_security_config() + { + var ipAllowedList = new List() { "127.0.0.1", "192.168.1.1" }; + var ipBlockedList = new List() { "127.0.0.1", "192.168.1.1" }; + var fileReRoute = new FileReRoute + { + SecurityOptions = new FileSecurityOptions() + { + IPAllowedList = ipAllowedList, + IPBlockedList = ipBlockedList + } + }; + + var expected = new SecurityOptions(ipAllowedList, ipBlockedList); + + this.Given(x => x.GivenThe(fileReRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheResultIs(expected)) + .BDDfy(); + } + + private void GivenThe(FileReRoute reRoute) + { + _fileReRoute = reRoute; + } + + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute.SecurityOptions); + } + + private void ThenTheResultIs(SecurityOptions expected) + { + for (int i = 0; i < expected.IPAllowedList.Count; i++) + { + _result.IPAllowedList[i].ShouldBe(expected.IPAllowedList[i]); + } + + for (int i = 0; i < expected.IPBlockedList.Count; i++) + { + _result.IPBlockedList[i].ShouldBe(expected.IPBlockedList[i]); + } + } + + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 112b7e0b..465c9173 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -9,6 +9,8 @@ using Shouldly; using TestStack.BDDfy; using Xunit; + using Moq; + using Microsoft.AspNetCore.Hosting; public class ConfigurationBuilderExtensionsTests { @@ -19,6 +21,21 @@ private FileConfiguration _reRouteA; private FileConfiguration _reRouteB; private FileConfiguration _aggregate; + private FileConfiguration _envSpecific; + private Mock _hostingEnvironment; + + + public ConfigurationBuilderExtensionsTests() + { + _hostingEnvironment = new Mock(); + // Clean up config files before each test + var subConfigFiles = new DirectoryInfo(".").GetFiles("ocelot.*.json"); + + foreach(var config in subConfigFiles) + { + config.Delete(); + } + } [Fact] public void should_add_base_url_to_config() @@ -32,23 +49,35 @@ [Fact] public void should_merge_files() { - this.Given(_ => GivenMultipleConfigurationFiles("")) + this.Given(_ => GivenMultipleConfigurationFiles("", false)) + .And(_ => GivenTheEnvironmentIs(null)) .When(_ => WhenIAddOcelotConfiguration()) .Then(_ => ThenTheConfigsAreMerged()) .BDDfy(); } + [Fact] + public void should_merge_files_except_env() + { + this.Given(_ => GivenMultipleConfigurationFiles("", true)) + .And(_ => GivenTheEnvironmentIs("Env")) + .When(_ => WhenIAddOcelotConfiguration()) + .Then(_ => ThenTheConfigsAreMerged()) + .And(_ => NotContainsEnvSpecificConfig()) + .BDDfy(); + } + [Fact] public void should_merge_files_in_specific_folder() { string configFolder = "ConfigFiles"; - this.Given(_ => GivenMultipleConfigurationFiles(configFolder)) + this.Given(_ => GivenMultipleConfigurationFiles(configFolder, false)) .When(_ => WhenIAddOcelotConfigurationWithSpecificFolder(configFolder)) .Then(_ => ThenTheConfigsAreMerged()) .BDDfy(); } - private void GivenMultipleConfigurationFiles(string folder) + private void GivenMultipleConfigurationFiles(string folder, bool addEnvSpecificConfig) { if (!string.IsNullOrEmpty(folder)) { @@ -85,7 +114,7 @@ new FileReRoute { DownstreamScheme = "DownstreamScheme", - DownstreamPathTemplate = "DownstreamDownstreamPathTemplate", + DownstreamPathTemplate = "DownstreamPathTemplate", Key = "Key", UpstreamHost = "UpstreamHost", UpstreamHttpMethod = new List @@ -155,7 +184,7 @@ { new FileAggregateReRoute { - ReRouteKeys = new List + ReRouteKeys = new List { "KeyB", "KeyBB" @@ -164,7 +193,7 @@ }, new FileAggregateReRoute { - ReRouteKeys = new List + ReRouteKeys = new List { "KeyB", "KeyBB" @@ -174,6 +203,32 @@ } }; + _envSpecific = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "DownstreamSchemeSpec", + DownstreamPathTemplate = "DownstreamPathTemplateSpec", + Key = "KeySpec", + UpstreamHost = "UpstreamHostSpec", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethodSpec" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "HostSpec", + Port = 80 + } + } + } + } + }; + string globalFilename = Path.Combine(folder, "ocelot.global.json"); string reroutesAFilename = Path.Combine(folder, "ocelot.reRoutesA.json"); string reroutesBFilename = Path.Combine(folder, "ocelot.reRoutesB.json"); @@ -183,19 +238,32 @@ File.WriteAllText(reroutesAFilename, JsonConvert.SerializeObject(_reRouteA)); File.WriteAllText(reroutesBFilename, JsonConvert.SerializeObject(_reRouteB)); File.WriteAllText(aggregatesFilename, JsonConvert.SerializeObject(_aggregate)); + + if (addEnvSpecificConfig) + { + string envSpecificFilename = Path.Combine(folder, "ocelot.Env.json"); + File.WriteAllText(envSpecificFilename, JsonConvert.SerializeObject(_envSpecific)); + } + } + + private void GivenTheEnvironmentIs(string env) + { + _hostingEnvironment.SetupGet(x => x.EnvironmentName).Returns(env); } private void WhenIAddOcelotConfiguration() { IConfigurationBuilder builder = new ConfigurationBuilder(); - builder.AddOcelot(); + + builder.AddOcelot(_hostingEnvironment.Object); + _configRoot = builder.Build(); } private void WhenIAddOcelotConfigurationWithSpecificFolder(string folder) { IConfigurationBuilder builder = new ConfigurationBuilder(); - builder.AddOcelot(folder); + builder.AddOcelot(folder, _hostingEnvironment.Object); _configRoot = builder.Build(); } @@ -235,6 +303,14 @@ fc.Aggregates.Count.ShouldBe(_aggregate.Aggregates.Count); } + private void NotContainsEnvSpecificConfig() + { + var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration)); + fc.ReRoutes.ShouldNotContain(x => x.DownstreamScheme == _envSpecific.ReRoutes[0].DownstreamScheme); + fc.ReRoutes.ShouldNotContain(x => x.DownstreamPathTemplate == _envSpecific.ReRoutes[0].DownstreamPathTemplate); + fc.ReRoutes.ShouldNotContain(x => x.Key == _envSpecific.ReRoutes[0].Key); + } + private void GivenTheBaseUrl(string baseUrl) { #pragma warning disable CS0618 diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index 336bc108..25667638 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _handlerOptions = new HttpHandlerOptionsBuilder().Build(); _loadBalancerOptions = new LoadBalancerOptionsBuilder().WithType(nameof(NoLoadBalancer)).Build(); _qosOptionsCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(_qoSOptions); _creator = new DownstreamRouteCreator(_qosOptionsCreator.Object); } @@ -198,7 +198,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void GivenTheQosCreatorReturns(QoSOptions options) { _qosOptionsCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(options); } @@ -211,7 +211,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheDownstreamRouteIsCreated() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); @@ -229,21 +229,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheDownstreamPathIsForwardSlash() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET"); } private void ThenThePathDoesNotHaveTrailingSlash() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); } private void ThenTheQueryStringIsRemoved() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); } @@ -260,7 +260,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(expected); _result.Data.ReRoute.DownstreamReRoute[0].QosOptions.UseQos.ShouldBeTrue(); _qosOptionsCreator - .Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny()), Times.Once); + .Verify(x => x.Create(expected, _upstreamUrlPath, It.IsAny>()), Times.Once); } private void GivenTheConfiguration(IInternalConfiguration config) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 298492af..0dbdbc37 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -23,7 +23,6 @@ { private readonly Mock _finder; private readonly Mock _factory; - private readonly Mock _repo; private Response _downstreamRoute; private IInternalConfiguration _config; private Mock _loggerFactory; @@ -35,7 +34,6 @@ public DownstreamRouteFinderMiddlewareTests() { - _repo = new Mock(); _finder = new Mock(); _factory = new Mock(); _factory.Setup(x => x.Get(It.IsAny())).Returns(_finder.Object); @@ -45,7 +43,7 @@ _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _multiplexer = new Mock(); - _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _repo.Object, _multiplexer.Object); + _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _multiplexer.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 5f15b136..97690a29 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -459,7 +459,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -545,7 +544,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -556,7 +554,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { }) // empty list of methods .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -587,7 +584,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List()) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List()) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -618,7 +614,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List()) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List()) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -659,7 +654,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) - .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) @@ -759,7 +753,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value); _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index 3b72db1b..cd8cf3c8 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs index 2ad59ca5..05e6ab2c 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs @@ -15,6 +15,9 @@ using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.Headers { + using Ocelot.Infrastructure; + using Ocelot.Logging; + public class AddHeadersToRequestClaimToThingTests { private readonly AddHeadersToRequest _addHeadersToRequest; @@ -24,11 +27,15 @@ namespace Ocelot.UnitTests.Headers private List _configuration; private Response _result; private Response _claimValue; + private Mock _placeholders; + private Mock _factory; public AddHeadersToRequestClaimToThingTests() { _parser = new Mock(); - _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); + _placeholders = new Mock(); + _factory = new Mock(); + _addHeadersToRequest = new AddHeadersToRequest(_parser.Object, _placeholders.Object, _factory.Object); _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); } diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs index 69fd4f69..c2b4189d 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs @@ -1,23 +1,56 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration.Creator; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Headers +namespace Ocelot.UnitTests.Headers { + using Ocelot.Infrastructure; + using Ocelot.Logging; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration.Creator; + using Ocelot.Headers; + using Ocelot.Infrastructure.Claims.Parser; + using Responder; + using Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class AddHeadersToRequestPlainTests { private readonly AddHeadersToRequest _addHeadersToRequest; private HttpContext _context; private AddHeader _addedHeader; + private readonly Mock _placeholders; + private Mock _factory; + private readonly Mock _logger; public AddHeadersToRequestPlainTests() { - _addHeadersToRequest = new AddHeadersToRequest(Mock.Of()); + _placeholders = new Mock(); + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _addHeadersToRequest = new AddHeadersToRequest(Mock.Of(), _placeholders.Object, _factory.Object); + } + + [Fact] + public void should_log_error_if_cannot_find_placeholder() + { + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); + + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) + .Then(_ => ThenAnErrorIsLogged("X-Forwarded-For", "{RemoteIdAddress}")) + .BDDfy(); + } + + [Fact] + public void should_add_placeholder_to_downstream_request() + { + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse("replaced")); + + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders("replaced")) + .BDDfy(); } [Fact] @@ -38,9 +71,23 @@ namespace Ocelot.UnitTests.Headers .BDDfy(); } + private void ThenAnErrorIsLogged(string key, string value) + { + _logger.Verify(x => x.LogWarning($"Unable to add header to response {key}: {value}"), Times.Once); + } + + private void GivenHttpRequestWithoutHeaders() { - _context = new DefaultHttpContext(); + _context = new DefaultHttpContext + { + Request = + { + Headers = + { + } + } + }; } private void GivenHttpRequestWithHeader(string headerKey, string headerValue) @@ -71,5 +118,12 @@ namespace Ocelot.UnitTests.Headers value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null."); value.ToString().ShouldBe(_addedHeader.Value); } + + private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected) + { + var requestHeaders = _context.Request.Headers; + var value = requestHeaders[_addedHeader.Key]; + value.ToString().ShouldBe(expected); + } } } diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs similarity index 88% rename from test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs rename to test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs index 7a448118..6931f15d 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs @@ -1,98 +1,98 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Headers -{ - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Headers; - using Ocelot.Headers.Middleware; - using Ocelot.Logging; - using Ocelot.Request.Middleware; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - - public class HttpRequestHeadersBuilderMiddlewareTests - { - private readonly Mock _addHeaders; - private Response _downstreamRoute; - private Mock _loggerFactory; - private Mock _logger; - private HttpRequestHeadersBuilderMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public HttpRequestHeadersBuilderMiddlewareTests() - { - _addHeaders = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - } - - [Fact] - public void should_call_add_headers_to_request_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToHeaders(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddHeadersToDownstreamRequestReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAddHeadersToRequestIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheAddHeadersToDownstreamRequestReturnsOk() - { - _addHeaders - .Setup(x => x.SetHeadersOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheAddHeadersToRequestIsCalledCorrectly() - { - _addHeaders - .Verify(x => x.SetHeadersOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - _downstreamContext.DownstreamRequest), Times.Once); - } - } -} +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Headers +{ + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Headers; + using Ocelot.Headers.Middleware; + using Ocelot.Logging; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsToHeadersMiddlewareTests + { + private readonly Mock _addHeaders; + private Response _downstreamRoute; + private Mock _loggerFactory; + private Mock _logger; + private ClaimsToHeadersMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ClaimsToHeadersMiddlewareTests() + { + _addHeaders = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new ClaimsToHeadersMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + } + + [Fact] + public void should_call_add_headers_to_request_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToHeaders(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddHeadersToDownstreamRequestReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAddHeadersToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; + } + + private void GivenTheAddHeadersToDownstreamRequestReturnsOk() + { + _addHeaders + .Setup(x => x.SetHeadersOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheAddHeadersToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetHeadersOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + _downstreamContext.DownstreamRequest), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 7b13cc6d..8cba2297 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -109,7 +109,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); } private void GivenTheFollowingRequest() diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index 3e7bc077..4726c7ae 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -1,283 +1,286 @@ -using Xunit; -using Shouldly; -using TestStack.BDDfy; -using System.Net.Http; -using Ocelot.Headers; -using Ocelot.Configuration; -using System.Collections.Generic; -using Ocelot.Responses; -using System.Linq; -using System.Net; -using Moq; -using Ocelot.Infrastructure; -using Ocelot.Middleware; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; - -namespace Ocelot.UnitTests.Headers -{ - public class HttpResponseHeaderReplacerTests - { - private DownstreamResponse _response; - private Placeholders _placeholders; - private readonly HttpResponseHeaderReplacer _replacer; - private List _headerFindAndReplaces; - private Response _result; - private DownstreamRequest _request; - private Mock _finder; - private Mock _repo; - - public HttpResponseHeaderReplacerTests() - { - _repo = new Mock(); - _finder = new Mock(); - _placeholders = new Placeholders(_finder.Object, _repo.Object); - _replacer = new HttpResponseHeaderReplacer(_placeholders); - } - - [Fact] - public void should_replace_headers() - { - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("test", new List {"test"}) - }); - - var fAndRs = new List {new HeaderFindAndReplace("test", "test", "chiken", 0)}; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreReplaced()) - .BDDfy(); - } - - [Fact] - public void should_not_replace_headers() - { - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("test", new List {"test"}) - }); - - var fAndRs = new List(); - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeadersAreNotReplaced()) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url() - { - const string downstreamUrl = "http://downstream.com/"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() - { - const string downstreamUrl = "http://downstream.com/"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() - { - const string downstreamUrl = "http://downstream.com/test/product"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() - { - const string downstreamUrl = "http://downstream.com/test/product"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() - { - const string downstreamUrl = "http://downstream.com:123/test/product"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) - .BDDfy(); - } - - [Fact] - public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() - { - const string downstreamUrl = "http://downstream.com:123/test/product"; - - var request = - new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - - var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, - new List>>() - { - new KeyValuePair>("Location", new List {downstreamUrl}) - }); - - var fAndRs = new List - { - new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0) - }; - - this.Given(x => GivenTheHttpResponse(response)) - .And(x => GivenTheRequestIs(request)) - .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) - .When(x => WhenICallTheReplacer()) - .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:321/test/product")) - .BDDfy(); - } - - private void GivenTheRequestIs(HttpRequestMessage request) - { - _request = new DownstreamRequest(request); - } - - private void ThenTheHeadersAreNotReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _headerFindAndReplaces) - { - var values = _response.Headers.First(x => x.Key == f.Key); - values.Values.ToList()[f.Index].ShouldBe("test"); - } - } - - private void GivenTheFollowingHeaderReplacements(List fAndRs) - { - _headerFindAndReplaces = fAndRs; - } - - private void GivenTheHttpResponse(DownstreamResponse response) - { - _response = response; - } - - private void WhenICallTheReplacer() - { - _result = _replacer.Replace(_response, _headerFindAndReplaces, _request); - } - - private void ThenTheHeaderShouldBe(string key, string value) - { - var test = _response.Headers.First(x => x.Key == key); - test.Values.First().ShouldBe(value); - } - - private void ThenTheHeadersAreReplaced() - { - _result.ShouldBeOfType(); - foreach (var f in _headerFindAndReplaces) - { - var values = _response.Headers.First(x => x.Key == f.Key); - values.Values.ToList()[f.Index].ShouldBe(f.Replace); - } - } - } -} +namespace Ocelot.UnitTests.Headers +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Infrastructure; + using Ocelot.Middleware; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Request.Middleware; + using Xunit; + using Shouldly; + using TestStack.BDDfy; + using System.Net.Http; + using Ocelot.Headers; + using Ocelot.Configuration; + using System.Collections.Generic; + using Ocelot.Responses; + using System.Linq; + using System.Net; + using Moq; + + public class HttpResponseHeaderReplacerTests + { + private DownstreamResponse _response; + private Placeholders _placeholders; + private readonly HttpResponseHeaderReplacer _replacer; + private List _headerFindAndReplaces; + private Response _result; + private DownstreamRequest _request; + private Mock _finder; + private Mock _repo; + private Mock _accessor; + + public HttpResponseHeaderReplacerTests() + { + _accessor = new Mock(); + _repo = new Mock(); + _finder = new Mock(); + _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object); + _replacer = new HttpResponseHeaderReplacer(_placeholders); + } + + [Fact] + public void should_replace_headers() + { + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }, ""); + + var fAndRs = new List {new HeaderFindAndReplace("test", "test", "chiken", 0)}; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreReplaced()) + .BDDfy(); + } + + [Fact] + public void should_not_replace_headers() + { + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }, ""); + + var fAndRs = new List(); + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeadersAreNotReplaced()) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url() + { + const string downstreamUrl = "http://downstream.com/"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() + { + const string downstreamUrl = "http://downstream.com/"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() + { + const string downstreamUrl = "http://downstream.com/test/product"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() + { + const string downstreamUrl = "http://downstream.com/test/product"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:123/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() + { + const string downstreamUrl = "http://downstream.com:123/test/product"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com/test/product")) + .BDDfy(); + } + + [Fact] + public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() + { + const string downstreamUrl = "http://downstream.com:123/test/product"; + + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; + + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }, ""); + + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0) + }; + + this.Given(x => GivenTheHttpResponse(response)) + .And(x => GivenTheRequestIs(request)) + .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) + .When(x => WhenICallTheReplacer()) + .Then(x => ThenTheHeaderShouldBe("Location", "http://ocelot.com:321/test/product")) + .BDDfy(); + } + + private void GivenTheRequestIs(HttpRequestMessage request) + { + _request = new DownstreamRequest(request); + } + + private void ThenTheHeadersAreNotReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _headerFindAndReplaces) + { + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe("test"); + } + } + + private void GivenTheFollowingHeaderReplacements(List fAndRs) + { + _headerFindAndReplaces = fAndRs; + } + + private void GivenTheHttpResponse(DownstreamResponse response) + { + _response = response; + } + + private void WhenICallTheReplacer() + { + var context = new DownstreamContext(new DefaultHttpContext()) {DownstreamResponse = _response, DownstreamRequest = _request}; + _result = _replacer.Replace(context, _headerFindAndReplaces); + } + + private void ThenTheHeaderShouldBe(string key, string value) + { + var test = _response.Headers.First(x => x.Key == key); + test.Values.First().ShouldBe(value); + } + + private void ThenTheHeadersAreReplaced() + { + _result.ShouldBeOfType(); + foreach (var f in _headerFindAndReplaces) + { + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe(f.Replace); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs index 729c4329..191d15fc 100644 --- a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs @@ -1,27 +1,31 @@ -using System; -using System.Net.Http; -using Moq; -using Ocelot.Infrastructure; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using Shouldly; -using Xunit; - namespace Ocelot.UnitTests.Infrastructure { + using Microsoft.AspNetCore.Http; + using System; + using System.Net; + using System.Net.Http; + using Moq; + using Ocelot.Infrastructure; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using Shouldly; + using Xunit; + public class PlaceholdersTests { - private IPlaceholders _placeholders; - private Mock _finder; - private Mock _repo; - + private readonly IPlaceholders _placeholders; + private readonly Mock _finder; + private readonly Mock _repo; + private readonly Mock _accessor; + public PlaceholdersTests() { + _accessor = new Mock(); _repo = new Mock(); _finder = new Mock(); - _placeholders = new Placeholders(_finder.Object, _repo.Object); + _placeholders = new Placeholders(_finder.Object, _repo.Object, _accessor.Object); } [Fact] @@ -33,6 +37,15 @@ namespace Ocelot.UnitTests.Infrastructure result.Data.ShouldBe(baseUrl); } + [Fact] + public void should_return_remote_ip_address() + { + var httpContext = new DefaultHttpContext(){Connection = { RemoteIpAddress = IPAddress.Any}}; + _accessor.Setup(x => x.HttpContext).Returns(httpContext); + var result = _placeholders.Get("{RemoteIpAddress}"); + result.Data.ShouldBe(httpContext.Connection.RemoteIpAddress.ToString()); + } + [Fact] public void should_return_key_does_not_exist() { diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index fb2a1c98..7d2e5edd 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -49,13 +49,13 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>()), + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>(), "some reason"), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>()), + DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>(), "some reason"), DownstreamReRoute = georgeDownstreamReRoute }; @@ -69,6 +69,7 @@ namespace Ocelot.UnitTests.Middleware .When(x => WhenIAggregate()) .Then(x => ThenTheContentIs(expected)) .And(x => ThenTheContentTypeIs("application/json")) + .And(x => ThenTheReasonPhraseIs("cannot return from aggregate..which reason phrase would you use?")) .BDDfy(); } @@ -91,13 +92,13 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>()), + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>(), "some reason"), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>()), + DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>(), "some reason"), DownstreamReRoute = georgeDownstreamReRoute, }; @@ -116,6 +117,11 @@ namespace Ocelot.UnitTests.Middleware .BDDfy(); } + private void ThenTheReasonPhraseIs(string expected) + { + _upstreamContext.DownstreamResponse.ReasonPhrase.ShouldBe(expected); + } + private void ThenTheErrorIsMapped() { _upstreamContext.Errors.ShouldBe(_downstreamContexts[1].Errors); diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs index 2b5bd552..daef2a2c 100644 --- a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs @@ -43,11 +43,11 @@ namespace Ocelot.UnitTests.Middleware { new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason") }, new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason") } }; @@ -72,11 +72,11 @@ namespace Ocelot.UnitTests.Middleware { new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason") }, new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason") } }; @@ -146,7 +146,7 @@ namespace Ocelot.UnitTests.Middleware var laura = await responses[1].Content.ReadAsStringAsync(); var content = $"{tom}, {laura}"; var headers = responses.SelectMany(x => x.Headers).ToList(); - return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers); + return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers, "some reason"); } } } diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs similarity index 88% rename from test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs rename to test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs index e4d95028..2710c866 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs @@ -1,97 +1,97 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.QueryStrings -{ - using System.Collections.Generic; - using System.Net.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.QueryStrings; - using Ocelot.QueryStrings.Middleware; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using System.Security.Claims; - using Microsoft.AspNetCore.Http; - using System.Threading.Tasks; - using Ocelot.Request.Middleware; - - public class QueryStringBuilderMiddlewareTests - { - private readonly Mock _addQueries; - private Mock _loggerFactory; - private Mock _logger; - private QueryStringBuilderMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public QueryStringBuilderMiddlewareTests() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _addQueries = new Mock(); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - _middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object); - } - - [Fact] - public void should_call_add_queries_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToQueries(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheAddHeadersToRequestReturnsOk() - { - _addQueries - .Setup(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheAddQueriesToRequestIsCalledCorrectly() - { - _addQueries - .Verify(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - _downstreamContext.DownstreamRequest), Times.Once); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - } -} +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.QueryStrings +{ + using System.Collections.Generic; + using System.Net.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.QueryStrings; + using Ocelot.QueryStrings.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using System.Security.Claims; + using Microsoft.AspNetCore.Http; + using System.Threading.Tasks; + using Ocelot.Request.Middleware; + + public class ClaimsToQueryStringMiddlewareTests + { + private readonly Mock _addQueries; + private Mock _loggerFactory; + private Mock _logger; + private ClaimsToQueryStringMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ClaimsToQueryStringMiddlewareTests() + { + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _addQueries = new Mock(); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + _middleware = new ClaimsToQueryStringMiddleware(_next, _loggerFactory.Object, _addQueries.Object); + } + + [Fact] + public void should_call_add_queries_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToQueries(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheAddHeadersToRequestReturnsOk() + { + _addQueries + .Setup(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheAddQueriesToRequestIsCalledCorrectly() + { + _addQueries + .Verify(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + _downstreamContext.DownstreamRequest), Times.Once); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index f63c2f34..b1d0ff7b 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Requester; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.UnitTests.Requester { + using System; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Errors; + using Ocelot.Logging; + using Ocelot.Requester; + using Ocelot.Requester.QoS; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; using Responder; public class DelegatingHandlerHandlerProviderFactoryTests @@ -302,6 +301,26 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_return_provider_with_qos_delegate_when_timeout_value_set() + { + var qosOptions = new QoSOptionsBuilder() + .WithTimeoutValue(1) + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) + .And(x => GivenTheServiceProviderReturnsNothing()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(1)) + .And(x => ThenItIsQosHandler(0)) + .BDDfy(); + } + [Fact] public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_error() { diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 9ff6ef10..fc666144 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -294,7 +294,7 @@ namespace Ocelot.UnitTests.Requester private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() { - _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamDownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamDownstreamPathTemplate}"), Times.Once); + _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once); } private void GivenTheClientIsCached() diff --git a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs index 6ff956d2..a5716908 100644 --- a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using System.Net.Http; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Ocelot.Headers; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; @@ -31,7 +32,7 @@ namespace Ocelot.UnitTests.Responder new List>> { new KeyValuePair>("Transfer-Encoding", new List {"woop"}) - }); + }, "some reason"); _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Transfer-Encoding"]; @@ -43,7 +44,7 @@ namespace Ocelot.UnitTests.Responder { var httpContext = new DefaultHttpContext(); var response = new DownstreamResponse(new StringContent("test"), HttpStatusCode.OK, - new List>>()); + new List>>(), "some reason"); _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Content-Length"]; @@ -58,13 +59,28 @@ namespace Ocelot.UnitTests.Responder new List>> { new KeyValuePair>("test", new List {"test"}) - }); + }, "some reason"); _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["test"]; header.First().ShouldBe("test"); } + + [Fact] + public void should_add_reason_phrase() + { + var httpContext = new DefaultHttpContext(); + var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK, + new List>> + { + new KeyValuePair>("test", new List {"test"}) + }, "some reason"); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); + httpContext.Response.HttpContext.Features.Get().ReasonPhrase.ShouldBe(response.ReasonPhrase); + } + [Fact] public void should_call_without_exception() { diff --git a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs new file mode 100644 index 00000000..5e4062ae --- /dev/null +++ b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Responses; +using Ocelot.Security.IPSecurity; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Security +{ + public class IPSecurityPolicyTests + { + private readonly DownstreamContext _downstreamContext; + private readonly DownstreamReRouteBuilder _downstreamReRouteBuilder; + private readonly IPSecurityPolicy _ipSecurityPolicy; + private Response response; + public IPSecurityPolicyTests() + { + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + _downstreamReRouteBuilder = new DownstreamReRouteBuilder(); + _ipSecurityPolicy = new IPSecurityPolicy(); + } + + [Fact] + private void should_No_blocked_Ip_and_allowed_Ip() + { + this.Given(x => x.GivenSetDownstreamReRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenSecurityPassing()) + .BDDfy(); + } + + [Fact] + private void should_blockedIp_clientIp_block() + { + _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + this.Given(x => x.GivenSetBlockedIP()) + .Given(x => x.GivenSetDownstreamReRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenNotSecurityPassing()) + .BDDfy(); + } + + [Fact] + private void should_blockedIp_clientIp_Not_block() + { + _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + this.Given(x => x.GivenSetBlockedIP()) + .Given(x => x.GivenSetDownstreamReRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenSecurityPassing()) + .BDDfy(); + } + + + [Fact] + private void should_allowedIp_clientIp_block() + { + _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + this.Given(x => x.GivenSetAllowedIP()) + .Given(x => x.GivenSetDownstreamReRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenSecurityPassing()) + .BDDfy(); + } + + [Fact] + private void should_allowedIp_clientIp_Not_block() + { + _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + this.Given(x => x.GivenSetAllowedIP()) + .Given(x => x.GivenSetDownstreamReRoute()) + .When(x => x.WhenTheSecurityPolicy()) + .Then(x => x.ThenNotSecurityPassing()) + .BDDfy(); + } + + private void GivenSetAllowedIP() + { + _downstreamReRouteBuilder.WithSecurityOptions(new SecurityOptions(new List { "192.168.1.1" }, new List())); + } + + private void GivenSetBlockedIP() + { + _downstreamReRouteBuilder.WithSecurityOptions(new SecurityOptions(new List(), new List { "192.168.1.1" })); + } + + private void GivenSetDownstreamReRoute() + { + _downstreamContext.DownstreamReRoute = _downstreamReRouteBuilder.Build(); + } + + private void WhenTheSecurityPolicy() + { + response = this._ipSecurityPolicy.Security(_downstreamContext).GetAwaiter().GetResult(); + } + + private void ThenSecurityPassing() + { + Assert.False(response.IsError); + } + + private void ThenNotSecurityPassing() + { + Assert.True(response.IsError); + } + } +} diff --git a/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs new file mode 100644 index 00000000..d7d38140 --- /dev/null +++ b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs @@ -0,0 +1,108 @@ +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Responses; +using Ocelot.Security; +using Ocelot.Security.IPSecurity; +using Ocelot.Security.Middleware; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Security +{ + public class SecurityMiddlewareTests + { + private List> _securityPolicyList; + private Mock _loggerFactory; + private Mock _logger; + private readonly SecurityMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; + + public SecurityMiddlewareTests() + { + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _securityPolicyList = new List>(); + _securityPolicyList.Add(new Mock()); + _securityPolicyList.Add(new Mock()); + _next = context => + { + return Task.CompletedTask; + }; + _middleware = new SecurityMiddleware(_loggerFactory.Object, _securityPolicyList.Select(f => f.Object).ToList(), _next); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + } + [Fact] + public void should_legal_request() + { + this.Given(x => x.GivenPassingSecurityVerification()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheRequestIsPassingSecurity()) + .BDDfy(); + } + + [Fact] + public void should_verification_failed_request() + { + this.Given(x => x.GivenNotPassingSecurityVerification()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheRequestIsNotPassingSecurity()) + .BDDfy(); + } + + private void GivenPassingSecurityVerification() + { + foreach (var item in _securityPolicyList) + { + Response response = new OkResponse(); + item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); + } + } + + private void GivenNotPassingSecurityVerification() + { + for (int i = 0; i < _securityPolicyList.Count; i++) + { + Mock item = _securityPolicyList[i]; + if (i == 0) + { + Error error = new UnauthenticatedError($"Not passing security verification"); + Response response = new ErrorResponse(error); + item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); + } + else + { + Response response = new OkResponse(); + item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); + } + } + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void ThenTheRequestIsPassingSecurity() + { + Assert.False(_downstreamContext.IsError); + } + + private void ThenTheRequestIsNotPassingSecurity() + { + Assert.True(_downstreamContext.IsError); + } + } +} diff --git a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs deleted file mode 100644 index 0ae4096a..00000000 --- a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Ocelot.UnitTests.TestData -{ - using System.Collections.Generic; - using Ocelot.Configuration.File; - - public class AuthenticationConfigTestData - { - public static IEnumerable GetAuthenticationData() - { - yield return new object[] - { - new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List(), - }, - AddHeadersToRequest = - { - { "CustomerId", "Claims[CustomerId] > value" }, - } - } - } - } - }; - - yield return new object[] - { - new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List(), - }, - AddHeadersToRequest = - { - { "CustomerId", "Claims[CustomerId] > value" }, - } - } - } - } - }; - } - } -}