diff --git a/.gitignore b/.gitignore index a2902800..f5dfbc4d 100644 --- a/.gitignore +++ b/.gitignore @@ -183,6 +183,7 @@ ClientBin/ *.dbmdl *.dbproj.schemaview *.pfx +!idsrv3test.pfx *.publishsettings node_modules/ orleans.codegen.cs diff --git a/build.cake b/build.cake index 184b1faa..9960c588 100644 --- a/build.cake +++ b/build.cake @@ -16,7 +16,7 @@ var artifactsDir = Directory("artifacts"); // unit testing var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; -var minCodeCoverage = 75d; +var minCodeCoverage = 76.4d; var coverallsRepoToken = "coveralls-repo-token-ocelot"; var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; @@ -229,11 +229,11 @@ Task("CreatePackages") EnsureDirectoryExists(packagesDir); CopyFiles("./src/**/Ocelot.*.nupkg", packagesDir); - GenerateReleaseNotes(releaseNotesFile); + //GenerateReleaseNotes(releaseNotesFile); System.IO.File.WriteAllLines(artifactsFile, new[]{ "nuget:Ocelot." + buildVersion + ".nupkg", - "releaseNotes:releasenotes.md" + //"releaseNotes:releasenotes.md" }); if (AppVeyor.IsRunningOnAppVeyor) diff --git a/configuration-explanation.txt b/configuration-explanation.txt deleted file mode 100644 index 09d44a2a..00000000 --- a/configuration-explanation.txt +++ /dev/null @@ -1,103 +0,0 @@ -{ - "ReRoutes": [ - { - # The downstream path we are forwarding the request to, ocelot will not add a trailing slash. - # Ocelot replaces any placeholders {etc} with matched values from the incoming request. - "DownstreamPathTemplate": "/identityserverexample/{someid}/something", - # The scheme you want Ocelot to use when making the downstream request - "DownstreamScheme": "https", - # The port you want Ocelot to use when making the downstream request, will default to - # scheme if nothing set - "DownstreamPort": 80, - # The host address of the downstream service, should not have a trailing slash or scheme - # if there is a trailing slash Ocelot will remove it. - "DownstreamHost" "localhost" - # The path template we are listening on for this re route, Ocelot will add a trailing - # slash to this property. Then when a request is made Ocelot makes sure a trailing - # slash is added, so everything matches - "UpstreamPathTemplate": "/identityserverexample", - # The method we are listening for on this re route - "UpstreamHttpMethod": "Get", - # Only support identity server at the moment - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ApiName": "api", - "AllowedScopes": [ - "openid", - "offline_access" - ], - # Required if using reference tokens - "ApiSecret": "secret" - }, - # WARNING - will overwrite any headers already in the request with these values. - # Ocelot will look in the user claims for the key in [] then return the value and save - # it as a header with the given key before the colon (:). The index selection on value - # means that Ocelot will use the delimiter specified after the next > to split the - # claim value and return the index specified. - "AddHeadersToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, - # WARNING - will overwrite any claims already in the request with these values. - # Ocelot will look in the user claims for the key in [] then return the value and save - # it as a claim with the given key before the colon (:). The index selection on value - # means that Ocelot will use the delimiter specified after the next > to split the - # claim value and return the index specified. - "AddClaimsToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, - # WARNING - will overwrite any query string entries already in the request with these values. - # Ocelot will look in the user claims for the key in [] then return the value and save - # it as a query string with the given key before the colon (:). The index selection on value - # means that Ocelot will use the delimiter specified after the next > to split the - # claim value and return the index specified. - "AddQueriesToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, - # This specifies any claims that are required for the user to access this re route. - # In this example the user must have the claim type UserType and - # the value must be registered - "RouteClaimsRequirement": { - "UserType": "registered" - }, - # This tells Ocelot to look for a header and use its value as a request/correlation id. - # If it is set here then the id will be forwarded to the downstream service. If it - # does not then it will not be forwarded - "RequestIdKey": "OcRequestId", - # If this is set the response from the downstream service will be cached using the key that called it. - # This gives the user a chance to influence the key by adding some random query string paramter for - # a user id or something that would get ignored by the downstream service. This is a hack and I - # intend to provide a mechanism the user can specify for the ttl caching. Also want to expand - # the caching a lot. - "FileCacheOptions": { "TtlSeconds": 15 }, - # The value of this is used when matching the upstream template to an upstream url. - "ReRouteIsCaseSensitive": false, - # Tells Ocelot the name of the service it is looking when making requests to service discovery - # for hosts and ports - "ServiceName": "product" - # Tells Ocelot which load balancer to use when making downstream requests. - "LoadBalancer": "RoundRobin" - }, - # This section is meant to be for global configuration settings - "GlobalConfiguration": { - # If this is set it will override any route specific request id keys, behaves the same - # otherwise - "RequestIdKey": "OcRequestId", - # If set Ocelot will try and use service discovery to locate downstream hosts and ports - "ServiceDiscoveryProvider": - { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 - } - } - } \ No newline at end of file diff --git a/configuration.json b/configuration.json deleted file mode 100755 index 65b211fc..00000000 --- a/configuration.json +++ /dev/null @@ -1 +0,0 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration","RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/configuration.yaml b/configuration.yaml deleted file mode 100755 index 2e47e77d..00000000 --- a/configuration.yaml +++ /dev/null @@ -1,3 +0,0 @@ -Routes: -- Downstream: http://localhost:51879/ - Upstream: /heee diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs index 96713fb0..b4c1503a 100644 --- a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs @@ -1,3 +1,4 @@ +using System; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -5,6 +6,8 @@ using Ocelot.Responses; namespace Ocelot.Authentication.Handler.Creator { + using Ocelot.Configuration; + using AuthenticationOptions = Configuration.AuthenticationOptions; /// @@ -16,15 +19,31 @@ namespace Ocelot.Authentication.Handler.Creator { var builder = app.New(); - builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + if (authOptions.Provider.ToLower() == "jwt") { - Authority = authOptions.ProviderRootUrl, - ApiName = authOptions.ApiName, - RequireHttpsMetadata = authOptions.RequireHttps, - AllowedScopes = authOptions.AllowedScopes, - SupportedTokens = SupportedTokens.Both, - ApiSecret = authOptions.ApiSecret - }); + var authenticationConfig = authOptions.Config as JwtConfig; + + builder.UseJwtBearerAuthentication( + new JwtBearerOptions() + { + Authority = authenticationConfig.Authority, + Audience = authenticationConfig.Audience + }); + } + else + { + var authenticationConfig = authOptions.Config as IdentityServerConfig; + + builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = authenticationConfig.ProviderRootUrl, + ApiName = authenticationConfig.ApiName, + RequireHttpsMetadata = authenticationConfig.RequireHttps, + AllowedScopes = authOptions.AllowedScopes, + SupportedTokens = SupportedTokens.Both, + ApiSecret = authenticationConfig.ApiSecret + }); + } var authenticationNext = builder.Build(); diff --git a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs index 2a815ee0..5662fe40 100644 --- a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs +++ b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs @@ -2,6 +2,7 @@ { public enum SupportedAuthenticationProviders { - IdentityServer + IdentityServer, + Jwt } } diff --git a/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs b/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs new file mode 100644 index 00000000..6aca01be --- /dev/null +++ b/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs @@ -0,0 +1,59 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Ocelot.Configuration; + +namespace Ocelot.Authentication.JsonConverters +{ + public class AuthenticationConfigConverter : JsonConverter + { + public override bool CanWrite => false; + + public override bool CanRead => true; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("Use default serialization."); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + var setting = default(IAuthenticationConfig); + + if (jsonObject["Provider"] != null) + { + switch (jsonObject["Provider"].Value()) + { + case "Jwt": + setting = new JwtConfig( + jsonObject["Authority"].Value(), + jsonObject["Audience"].Value()); + break; + + default: + setting = new IdentityServerConfig( + jsonObject["ProviderRootUrl"].Value(), + jsonObject["ApiName"].Value(), + jsonObject["RequireHttps"].Value(), + jsonObject["ApiSecret"].Value()); + break; + } + } + else + { + setting = new IdentityServerConfig(string.Empty, string.Empty, false, string.Empty); + } + + serializer.Populate(jsonObject.CreateReader(), setting); + return setting; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IAuthenticationConfig); + } + } + + +} diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index f29161ce..f6ecd654 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -34,11 +34,9 @@ namespace Ocelot.Authentication.Middleware public async Task Invoke(HttpContext context) { - _logger.TraceMiddlewareEntry(); - if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) { - _logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlwareName} checking if client is authenticated"); + _logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions); @@ -46,7 +44,6 @@ namespace Ocelot.Authentication.Middleware { _logger.LogError($"Error getting authentication handler for {context.Request.Path}. {authenticationHandler.Errors.ToErrorString()}"); SetPipelineError(authenticationHandler.Errors); - _logger.TraceMiddlewareCompleted(); return; } @@ -56,11 +53,7 @@ namespace Ocelot.Authentication.Middleware if (context.User.Identity.IsAuthenticated) { _logger.LogDebug($"Client has been authenticated for {context.Request.Path}"); - - _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); + await _next.Invoke(context); } else { @@ -72,8 +65,6 @@ namespace Ocelot.Authentication.Middleware _logger.LogError($"Client has NOT been authenticated for {context.Request.Path} and pipeline error set. {error.ToErrorString()}"); SetPipelineError(error); - - _logger.TraceMiddlewareCompleted(); return; } } @@ -81,10 +72,7 @@ namespace Ocelot.Authentication.Middleware { _logger.LogTrace($"No authentication needed for {context.Request.Path}"); - _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); + await _next.Invoke(context); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index bfcd5f8d..4a707fdc 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -33,8 +33,6 @@ namespace Ocelot.Authorisation.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started authorisation"); - if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) { _logger.LogDebug("route is authenticated scopes must be checked"); @@ -73,7 +71,7 @@ namespace Ocelot.Authorisation.Middleware if (authorised.IsError) { - _logger.LogDebug("error authorising user"); + _logger.LogDebug($"Error whilst authorising {context.User.Identity.Name} for {context.User.Identity.Name}. Setting pipeline error"); SetPipelineError(authorised.Errors); return; @@ -81,30 +79,23 @@ namespace Ocelot.Authorisation.Middleware if (IsAuthorised(authorised)) { - _logger.LogDebug("user is authorised calling next middleware"); - + _logger.LogDebug($"{context.User.Identity.Name} has succesfully been authorised for {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } else { - _logger.LogDebug("user is not authorised setting pipeline error"); + _logger.LogDebug($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); SetPipelineError(new List { - new UnauthorisedError( - $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") + new UnauthorisedError($"{context.User.Identity.Name} is not authorised to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") }); } } else { - _logger.LogDebug("AuthorisationMiddleware.Invoke route is not authorised calling next middleware"); - + _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } } diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index 9ac26d8f..9abf5761 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,11 +1,13 @@ using System; +using System.Collections.Generic; namespace Ocelot.Cache { public interface IOcelotCache { - void Add(string key, T value, TimeSpan ttl); - void AddAndDelete(string key, T value, TimeSpan ttl); - T Get(string key); + void Add(string key, T value, TimeSpan ttl, string region); + void AddAndDelete(string key, T value, TimeSpan ttl, string region); + T Get(string key, string region); + void ClearRegion(string region); } } diff --git a/src/Ocelot/Cache/IRegionCreator.cs b/src/Ocelot/Cache/IRegionCreator.cs new file mode 100644 index 00000000..8ed186dd --- /dev/null +++ b/src/Ocelot/Cache/IRegionCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Cache +{ + public interface IRegionCreator + { + string Create(FileReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 85a73e70..9c6dcf32 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -13,16 +14,19 @@ namespace Ocelot.Cache.Middleware private readonly RequestDelegate _next; private readonly IOcelotLogger _logger; private readonly IOcelotCache _outputCache; + private readonly IRegionCreator _regionCreator; public OutputCacheMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository, - IOcelotCache outputCache) + IOcelotCache outputCache, + IRegionCreator regionCreator) :base(scopedDataRepository) { _next = next; _outputCache = outputCache; _logger = loggerFactory.CreateLogger(); + _regionCreator = regionCreator; } public async Task Invoke(HttpContext context) @@ -33,11 +37,11 @@ namespace Ocelot.Cache.Middleware return; } - var downstreamUrlKey = DownstreamRequest.RequestUri.OriginalString; + var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); - var cached = _outputCache.Get(downstreamUrlKey); + var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); if (cached != null) { @@ -54,8 +58,6 @@ namespace Ocelot.Cache.Middleware await _next.Invoke(context); - _logger.LogDebug("succesfully called next middleware"); - if (PipelineError) { _logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); @@ -65,7 +67,7 @@ namespace Ocelot.Cache.Middleware var response = HttpResponseMessage; - _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds)); + _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); } diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs index e92d678d..33ee1543 100644 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using CacheManager.Core; namespace Ocelot.Cache @@ -12,12 +14,12 @@ namespace Ocelot.Cache _cacheManager = cacheManager; } - public void Add(string key, T value, TimeSpan ttl) + public void Add(string key, T value, TimeSpan ttl, string region) { - _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); } - public void AddAndDelete(string key, T value, TimeSpan ttl) + public void AddAndDelete(string key, T value, TimeSpan ttl, string region) { var exists = _cacheManager.Get(key); @@ -26,12 +28,17 @@ namespace Ocelot.Cache _cacheManager.Remove(key); } - _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + Add(key, value, ttl, region); } - public T Get(string key) + public T Get(string key, string region) { - return _cacheManager.Get(key); + return _cacheManager.Get(key, region); + } + + public void ClearRegion(string region) + { + _cacheManager.ClearRegion(region); } } } \ No newline at end of file diff --git a/src/Ocelot/Cache/RegionCreator.cs b/src/Ocelot/Cache/RegionCreator.cs new file mode 100644 index 00000000..87de751c --- /dev/null +++ b/src/Ocelot/Cache/RegionCreator.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; + +namespace Ocelot.Cache +{ + + public class RegionCreator : IRegionCreator + { + public string Create(FileReRoute reRoute) + { + if(!string.IsNullOrEmpty(reRoute?.FileCacheOptions?.Region)) + { + return reRoute?.FileCacheOptions?.Region; + } + + var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m)); + + var region = $"{methods}{reRoute.UpstreamPathTemplate.Replace("/", "")}"; + + return region; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Cache/Regions.cs b/src/Ocelot/Cache/Regions.cs new file mode 100644 index 00000000..1dbefbd5 --- /dev/null +++ b/src/Ocelot/Cache/Regions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Ocelot.Cache +{ + public class Regions + { + public Regions(List value) + { + Value = value; + } + public List Value {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index 1b1745e2..9a2e4239 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -26,8 +26,6 @@ namespace Ocelot.Claims.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started claims middleware"); - if (DownstreamRoute.ReRoute.ClaimsToClaims.Any()) { _logger.LogDebug("this route has instructions to convert claims to other claims"); @@ -42,12 +40,7 @@ namespace Ocelot.Claims.Middleware return; } } - - _logger.LogDebug("calling next middleware"); - await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } } } diff --git a/src/Ocelot/Configuration/AuthenticationOptions.cs b/src/Ocelot/Configuration/AuthenticationOptions.cs index 223491b2..1c71d68b 100644 --- a/src/Ocelot/Configuration/AuthenticationOptions.cs +++ b/src/Ocelot/Configuration/AuthenticationOptions.cs @@ -2,24 +2,52 @@ namespace Ocelot.Configuration { + using Newtonsoft.Json; + public class AuthenticationOptions { - public AuthenticationOptions(string provider, string providerRootUrl, string apiName, bool requireHttps, List allowedScopes, string apiSecret) + public AuthenticationOptions(string provider, List allowedScopes, IAuthenticationConfig config) { Provider = provider; - ProviderRootUrl = providerRootUrl; - ApiName = apiName; - RequireHttps = requireHttps; - AllowedScopes = allowedScopes; - ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + Config = config; } public string Provider { get; private set; } + + public List AllowedScopes { get; private set; } + + public IAuthenticationConfig Config { get; private set; } + } + + public class IdentityServerConfig : IAuthenticationConfig + { + public IdentityServerConfig(string providerRootUrl, string apiName, bool requireHttps, string apiSecret) + { + ProviderRootUrl = providerRootUrl; + ApiName = apiName; + RequireHttps = requireHttps; + ApiSecret = apiSecret; + } + public string ProviderRootUrl { get; private set; } public string ApiName { get; private set; } public string ApiSecret { get; private set; } public bool RequireHttps { get; private set; } - public List AllowedScopes { get; private set; } - } + + public class JwtConfig : IAuthenticationConfig + { + public JwtConfig(string authority, string audience) + { + Audience = audience; + Authority = authority; + } + + public string Audience { get; } + + public string Authority { get; } + } + + public interface IAuthenticationConfig {} } diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs index 0c648489..ea43a23e 100644 --- a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs @@ -6,51 +6,93 @@ namespace Ocelot.Configuration.Builder { private string _provider; - private string _providerRootUrl; - private string _apiName; - private string _apiSecret; - private bool _requireHttps; + private List _allowedScopes; + private IAuthenticationConfig _identityServerConfig; + public AuthenticationOptionsBuilder WithProvider(string provider) { _provider = provider; return this; } - public AuthenticationOptionsBuilder WithProviderRootUrl(string providerRootUrl) - { - _providerRootUrl = providerRootUrl; - return this; - } - - public AuthenticationOptionsBuilder WithApiName(string apiName) - { - _apiName = apiName; - return this; - } - - public AuthenticationOptionsBuilder WithApiSecret(string apiSecret) - { - _apiSecret = apiSecret; - return this; - } - - public AuthenticationOptionsBuilder WithRequireHttps(bool requireHttps) - { - _requireHttps = requireHttps; - return this; - } - public AuthenticationOptionsBuilder WithAllowedScopes(List allowedScopes) { _allowedScopes = allowedScopes; return this; } + public AuthenticationOptionsBuilder WithConfig(IAuthenticationConfig config) + { + _identityServerConfig = config; + return this; + } + public AuthenticationOptions Build() { - return new AuthenticationOptions(_provider, _providerRootUrl, _apiName, _requireHttps, _allowedScopes, _apiSecret); + return new AuthenticationOptions(_provider, _allowedScopes, _identityServerConfig); + } + } + + public class IdentityServerConfigBuilder + { + private string _providerRootUrl; + private string _apiName; + private string _apiSecret; + private bool _requireHttps; + + public IdentityServerConfigBuilder WithProviderRootUrl(string providerRootUrl) + { + _providerRootUrl = providerRootUrl; + return this; + } + + public IdentityServerConfigBuilder WithApiName(string apiName) + { + _apiName = apiName; + return this; + } + + public IdentityServerConfigBuilder WithApiSecret(string apiSecret) + { + _apiSecret = apiSecret; + return this; + } + + public IdentityServerConfigBuilder WithRequireHttps(bool requireHttps) + { + _requireHttps = requireHttps; + return this; + } + + public IdentityServerConfig Build() + { + return new IdentityServerConfig(_providerRootUrl, _apiName, _requireHttps, _apiSecret); + } + } + + public class JwtConfigBuilder + { + public string _authority; + + public string _audience; + + public JwtConfigBuilder WithAuthority(string authority) + { + _authority = authority; + return this; + } + + public JwtConfigBuilder WithAudience(string audience) + { + _audience = audience; + return this; + } + + public JwtConfig Build() + { + return new JwtConfig(_authority, _audience); } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index 2fdaf2bb..a8b6a9d3 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -2,11 +2,13 @@ { public class CacheOptions { - public CacheOptions(int ttlSeconds) + public CacheOptions(int ttlSeconds, string region) { TtlSeconds = ttlSeconds; + Region = region; } public int TtlSeconds { get; private set; } + public string Region {get;private set;} } } diff --git a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs index 583748e1..284ec33c 100644 --- a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs @@ -1,20 +1,27 @@ using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; +using Ocelot.Creator.Configuration; namespace Ocelot.Configuration.Creator { public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator { + private readonly IAuthenticationProviderConfigCreator _creator; + + public AuthenticationOptionsCreator(IAuthenticationProviderConfigCreator creator) + { + _creator = creator; + } + public AuthenticationOptions Create(FileReRoute fileReRoute) { + var authenticationConfig = _creator.Create(fileReRoute.AuthenticationOptions); + return new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) - .WithApiName(fileReRoute.AuthenticationOptions?.ApiName) - .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) - .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithApiSecret(fileReRoute.AuthenticationOptions?.ApiSecret) - .Build(); - } + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) + .WithConfig(authenticationConfig) + .Build(); + } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs new file mode 100644 index 00000000..c7a25799 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs @@ -0,0 +1,37 @@ +using Ocelot.Creator.Configuration; + +namespace Ocelot.Configuration.Creator +{ + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.File; + + public class AuthenticationProviderConfigCreator : IAuthenticationProviderConfigCreator + { + public IAuthenticationConfig Create(FileAuthenticationOptions authenticationOptions) + { + if (authenticationOptions.Provider?.ToLower() == "jwt") + { + return CreateJwt(authenticationOptions); + } + + return CreateIdentityServer(authenticationOptions); + } + + private JwtConfig CreateJwt(FileAuthenticationOptions authenticationOptions) + { + return new JwtConfigBuilder() + .WithAudience(authenticationOptions.JwtConfig?.Audience) + .WithAuthority(authenticationOptions.JwtConfig?.Authority) + .Build(); + } + + private IdentityServerConfig CreateIdentityServer(FileAuthenticationOptions authenticationOptions) + { + return new IdentityServerConfigBuilder() + .WithApiName(authenticationOptions.IdentityServerConfig?.ApiName) + .WithApiSecret(authenticationOptions.IdentityServerConfig?.ApiSecret) + .WithProviderRootUrl(authenticationOptions.IdentityServerConfig?.ProviderRootUrl) + .WithRequireHttps(authenticationOptions.IdentityServerConfig.RequireHttps).Build(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c44edd08..c6f5f4fe 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Cache; using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; @@ -30,12 +31,13 @@ namespace Ocelot.Configuration.Creator private readonly IQosProviderHouse _qosProviderHouse; private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IAuthenticationOptionsCreator _authOptionsCreator; - private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private IRequestIdKeyCreator _requestIdKeyCreator; - private IServiceProviderConfigurationCreator _serviceProviderConfigCreator; - private IQoSOptionsCreator _qosOptionsCreator; - private IReRouteOptionsCreator _fileReRouteOptionsCreator; - private IRateLimitOptionsCreator _rateLimitOptionsCreator; + 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; public FileOcelotConfigurationCreator( IOptions options, @@ -52,9 +54,11 @@ namespace Ocelot.Configuration.Creator IServiceProviderConfigurationCreator serviceProviderConfigCreator, IQoSOptionsCreator qosOptionsCreator, IReRouteOptionsCreator fileReRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator ) { + _regionCreator = regionCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator; _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; @@ -137,6 +141,8 @@ namespace Ocelot.Configuration.Creator var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + var region = _regionCreator.Create(fileReRoute); + var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) @@ -151,7 +157,7 @@ namespace Ocelot.Configuration.Creator .WithClaimsToQueries(claimsToQueries) .WithRequestIdKey(requestIdKey) .WithIsCached(fileReRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) .WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithLoadBalancer(fileReRoute.LoadBalancer) .WithDownstreamHost(fileReRoute.DownstreamHost) diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs index 48819608..6ed3b6c0 100644 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -13,6 +13,8 @@ namespace Ocelot.Configuration.Creator var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE"); + var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD"); return new IdentityServerConfiguration( "admin", @@ -28,7 +30,9 @@ namespace Ocelot.Configuration.Creator new List { new User("admin", username, hash, salt) - } + }, + credentialsSigningCertificateLocation, + credentialsSigningCertificatePassword ); } } diff --git a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs index 63e6347c..6333993d 100644 --- a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs +++ b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs @@ -6,14 +6,14 @@ namespace Ocelot.Configuration.File { public FileAuthenticationOptions() { - AllowedScopes = new List(); + AllowedScopes = new List(); + IdentityServerConfig = new FileIdentityServerConfig(); + JwtConfig = new FileJwtConfig(); } public string Provider { get; set; } - public string ProviderRootUrl { get; set; } - public string ApiName { get; set; } - public bool RequireHttps { get; set; } public List AllowedScopes { get; set; } - public string ApiSecret { get; set; } + public FileIdentityServerConfig IdentityServerConfig { get; set; } + public FileJwtConfig JwtConfig { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs index 3f86006b..df9fb631 100644 --- a/src/Ocelot/Configuration/File/FileCacheOptions.cs +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -3,5 +3,6 @@ public class FileCacheOptions { public int TtlSeconds { get; set; } + public string Region {get; set;} } } diff --git a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs new file mode 100644 index 00000000..dfc023b2 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs @@ -0,0 +1,10 @@ +namespace Ocelot.Configuration.File +{ + public class FileIdentityServerConfig + { + public string ProviderRootUrl { get; set; } + public string ApiName { get; set; } + public bool RequireHttps { get; set; } + public string ApiSecret { get; set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileJwtConfig.cs b/src/Ocelot/Configuration/File/FileJwtConfig.cs new file mode 100644 index 00000000..a24522fc --- /dev/null +++ b/src/Ocelot/Configuration/File/FileJwtConfig.cs @@ -0,0 +1,9 @@ +namespace Ocelot.Configuration.File +{ + public class FileJwtConfig + { + public string Authority { get; set; } + + public string Audience { get; set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs index bb66265f..0a388abb 100644 --- a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs @@ -17,5 +17,7 @@ namespace Ocelot.Configuration.Provider AccessTokenType AccessTokenType {get;} bool RequireClientSecret {get;} List Users {get;} + string CredentialsSigningCertificateLocation { get; } + string CredentialsSigningCertificatePassword { get; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs index f0f6897d..881d6f5a 100644 --- a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs @@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Provider IEnumerable grantType, AccessTokenType accessTokenType, bool requireClientSecret, - List users) + List users, string credentialsSigningCertificateLocation, string credentialsSigningCertificatePassword) { ApiName = apiName; RequireHttps = requireHttps; @@ -30,6 +30,8 @@ namespace Ocelot.Configuration.Provider AccessTokenType = accessTokenType; RequireClientSecret = requireClientSecret; Users = users; + CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; + CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; } public string ApiName { get; private set; } @@ -43,5 +45,7 @@ namespace Ocelot.Configuration.Provider public AccessTokenType AccessTokenType {get;private set;} public bool RequireClientSecret {get;private set;} public List Users {get;private set;} + public string CredentialsSigningCertificateLocation { get; private set; } + public string CredentialsSigningCertificatePassword { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index a6673d09..2b8734c4 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -46,7 +46,7 @@ namespace Ocelot.Configuration IsAuthorised = isAuthorised; RequestIdKey = requestIdKey; IsCached = isCached; - FileCacheOptions = fileCacheOptions; + CacheOptions = fileCacheOptions; ClaimsToQueries = claimsToQueries ?? new List(); ClaimsToClaims = claimsToClaims @@ -74,7 +74,7 @@ namespace Ocelot.Configuration public Dictionary RouteClaimsRequirement { get; private set; } public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } - public CacheOptions FileCacheOptions { get; private set; } + public CacheOptions CacheOptions { get; private set; } public string DownstreamScheme {get;private set;} public bool IsQos { get; private set; } public QoSOptions QosOptionsOptions { get; private set; } diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs index 703d039b..6fa6f72a 100644 --- a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Consul; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Ocelot.Authentication.JsonConverters; using Ocelot.Responses; using Ocelot.ServiceDiscovery; @@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Repository public async Task> Get() { - var config = _cache.Get(_ocelotConfiguration); + var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); if (config != null) { @@ -48,7 +49,9 @@ namespace Ocelot.Configuration.Repository var json = Encoding.UTF8.GetString(bytes); - var consulConfig = JsonConvert.DeserializeObject(json); + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new AuthenticationConfigConverter()); + var consulConfig = JsonConvert.DeserializeObject(json, settings); return new OkResponse(consulConfig); } @@ -68,7 +71,7 @@ namespace Ocelot.Configuration.Repository if (result.Response) { - _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3)); + _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); return new OkResponse(); } diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs new file mode 100644 index 00000000..a68201a1 --- /dev/null +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -0,0 +1,29 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ocelot.Cache; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Controllers +{ + [Authorize] + [Route("outputcache")] + public class OutputCacheController : Controller + { + private IOcelotCache _cache; + + public OutputCacheController(IOcelotCache cache) + { + _cache = cache; + } + + [HttpDelete] + [Route("{region}")] + public IActionResult Delete(string region) + { + _cache.ClearRegion(region); + return new NoContentResult(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs b/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs new file mode 100644 index 00000000..f5cd4fda --- /dev/null +++ b/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs @@ -0,0 +1,10 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.File; + +namespace Ocelot.Creator.Configuration +{ + public interface IAuthenticationProviderConfigCreator + { + IAuthenticationConfig Create(FileAuthenticationOptions authenticationOptions); + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index d52341fe..d55a8573 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -41,7 +41,10 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using Microsoft.IdentityModel.Tokens; using Ocelot.Configuration; +using Ocelot.Creator.Configuration; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; namespace Ocelot.DependencyInjection @@ -69,6 +72,7 @@ namespace Ocelot.DependencyInjection services.Configure(configurationRoot); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -87,8 +91,10 @@ namespace Ocelot.DependencyInjection { services.TryAddSingleton(identityServerConfiguration); services.TryAddSingleton(); - services.AddIdentityServer() - .AddTemporarySigningCredential() + var identityServerBuilder = services + .AddIdentityServer(options => { + options.IssuerUri = "Ocelot"; + }) .AddInMemoryApiResources(new List { new ApiResource @@ -120,6 +126,16 @@ namespace Ocelot.DependencyInjection RequireClientSecret = identityServerConfiguration.RequireClientSecret } }).AddResourceOwnerValidator(); + + if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) + { + identityServerBuilder.AddTemporarySigningCredential(); + } + else + { + var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); + identityServerBuilder.AddSigningCredential(cert); + } } var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; @@ -131,6 +147,7 @@ namespace Ocelot.DependencyInjection .AddJsonFormatters(); services.AddLogging(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -169,6 +186,11 @@ namespace Ocelot.DependencyInjection services.TryAddSingleton(); services.TryAddScoped(); services.AddMemoryCache(); + + //Used to log the the start and ending of middleware + services.TryAddSingleton(); + services.AddMiddlewareAnalysis(); + return services; } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index c107a2f5..51cb3a0b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -30,8 +30,6 @@ namespace Ocelot.DownstreamRouteFinder.Middleware public async Task Invoke(HttpContext context) { - _logger.TraceMiddlewareEntry(); - var upstreamUrlPath = context.Request.Path.ToString().SetLastCharacterAs('/'); _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); @@ -40,11 +38,9 @@ namespace Ocelot.DownstreamRouteFinder.Middleware if (downstreamRoute.IsError) { - _logger.LogError($"{MiddlwareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); + _logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); SetPipelineError(downstreamRoute.Errors); - - _logger.TraceMiddlewareCompleted(); return; } @@ -52,12 +48,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware SetDownstreamRouteForThisRequest(downstreamRoute.Data); - _logger.TraceInvokeNext(); - await _next.Invoke(context); - - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index c91e06da..bf08ba27 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -30,8 +30,6 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling downstream url creator middleware"); - var dsPath = _replacer .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); @@ -53,11 +51,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", DownstreamRequest.RequestUri); - _logger.LogDebug("calling next middleware"); - await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } } } \ No newline at end of file diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index cb0fdc98..d53cd76d 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -26,17 +26,15 @@ namespace Ocelot.Headers.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling headers builder middleware"); - if (DownstreamRoute.ReRoute.ClaimsToHeaders.Any()) { - _logger.LogDebug("this route has instructions to convert claims to headers"); + _logger.LogDebug($"{ DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToHeaders, context.User.Claims, DownstreamRequest); if (response.IsError) { - _logger.LogDebug("there was an error setting headers on context, setting pipeline error"); + _logger.LogDebug("Error setting headers on context, setting pipeline error"); SetPipelineError(response.Errors); return; @@ -45,11 +43,7 @@ namespace Ocelot.Headers.Middleware _logger.LogDebug("headers have been set on context"); } - _logger.LogDebug("calling next middleware"); - await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index be5a34d1..909ef755 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -28,18 +28,18 @@ namespace Ocelot.LoadBalancer.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey); if(loadBalancer.IsError) { + _logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); SetPipelineError(loadBalancer.Errors); return; } var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) - { + { + _logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); SetPipelineError(hostAndPort.Errors); return; } @@ -52,23 +52,19 @@ namespace Ocelot.LoadBalancer.Middleware } DownstreamRequest.RequestUri = uriBuilder.Uri; - _logger.LogDebug("calling next middleware"); - try { await _next.Invoke(context); - - loadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - loadBalancer.Data.Release(hostAndPort.Data); - - _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); + _logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); throw; } - - _logger.LogDebug("succesfully called next middleware"); + finally + { + loadBalancer.Data.Release(hostAndPort.Data); + } } } } diff --git a/src/Ocelot/Logging/AspDotNetLogger.cs b/src/Ocelot/Logging/AspDotNetLogger.cs index 9ebaf7fd..82115382 100644 --- a/src/Ocelot/Logging/AspDotNetLogger.cs +++ b/src/Ocelot/Logging/AspDotNetLogger.cs @@ -4,39 +4,39 @@ using Ocelot.Infrastructure.RequestData; namespace Ocelot.Logging { - public class AspDotNetLogger : IOcelotLogger - { - private readonly ILogger _logger; - private readonly IRequestScopedDataRepository _scopedDataRepository; - - public string Name { get; } - + public class AspDotNetLogger : IOcelotLogger + { + private readonly ILogger _logger; + private readonly IRequestScopedDataRepository _scopedDataRepository; + + public string Name { get; } + public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository, string typeName) { - Name = typeName; - _logger = logger; - _scopedDataRepository = scopedDataRepository; - } - - public void LogTrace(string message, params object[] args) - { - _logger.LogTrace(GetMessageWithOcelotRequestId(message), args); - } - - public void LogDebug(string message, params object[] args) - { - _logger.LogDebug(GetMessageWithOcelotRequestId(message), args); - } - public void LogError(string message, Exception exception) - { - _logger.LogError(GetMessageWithOcelotRequestId(message), exception); - } - - public void LogError(string message, params object[] args) - { - _logger.LogError(GetMessageWithOcelotRequestId(message), args); - } - + Name = typeName; + _logger = logger; + _scopedDataRepository = scopedDataRepository; + } + + public void LogTrace(string message, params object[] args) + { + _logger.LogTrace(GetMessageWithOcelotRequestId(message), args); + } + + public void LogDebug(string message, params object[] args) + { + _logger.LogDebug(GetMessageWithOcelotRequestId(message), args); + } + public void LogError(string message, Exception exception) + { + _logger.LogError(GetMessageWithOcelotRequestId(message), exception); + } + + public void LogError(string message, params object[] args) + { + _logger.LogError(GetMessageWithOcelotRequestId(message), args); + } + private string GetMessageWithOcelotRequestId(string message) { var requestId = _scopedDataRepository.Get("RequestId"); diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs new file mode 100644 index 00000000..53d6e14c --- /dev/null +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DiagnosticAdapter; + +namespace Ocelot.Logging +{ + public class OcelotDiagnosticListener + { + private IOcelotLogger _logger; + + public OcelotDiagnosticListener(IOcelotLoggerFactory factory) + { + _logger = factory.CreateLogger(); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] + public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] + public virtual void OnMiddlewareException(Exception exception, string name) + { + _logger.LogTrace($"MiddlewareException: {name}; {exception.Message}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] + public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + } + } +} diff --git a/src/Ocelot/Logging/OcelotLoggerExtensions.cs b/src/Ocelot/Logging/OcelotLoggerExtensions.cs deleted file mode 100644 index 05bd4565..00000000 --- a/src/Ocelot/Logging/OcelotLoggerExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Ocelot.Logging -{ - public static class OcelotLoggerExtensions - { - public static void TraceMiddlewareEntry(this IOcelotLogger logger) - { - logger.LogTrace($"entered {logger.Name}"); - } - - public static void TraceInvokeNext(this IOcelotLogger logger) - { - logger.LogTrace($"invoking next middleware from {logger.Name}"); - } - - public static void TraceInvokeNextCompleted(this IOcelotLogger logger) - { - logger.LogTrace($"returned to {logger.Name} after next middleware completed"); - } - - public static void TraceMiddlewareCompleted(this IOcelotLogger logger) - { - logger.LogTrace($"completed {logger.Name}"); - } - } -} diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index af56891e..0060cbb3 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -13,10 +13,10 @@ namespace Ocelot.Middleware protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository) { _requestScopedDataRepository = requestScopedDataRepository; - MiddlwareName = this.GetType().Name; + MiddlewareName = this.GetType().Name; } - public string MiddlwareName { get; } + public string MiddlewareName { get; } public bool PipelineError => _requestScopedDataRepository.Get("OcelotMiddlewareError").Data; diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 3f98f959..c9b5f536 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Ocelot.Authentication.Middleware; @@ -8,6 +9,7 @@ using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Errors.Middleware; using Ocelot.Headers.Middleware; +using Ocelot.Logging; using Ocelot.QueryStrings.Middleware; using Ocelot.Request.Middleware; using Ocelot.Requester.Middleware; @@ -53,6 +55,8 @@ namespace Ocelot.Middleware { await CreateAdministrationArea(builder); + ConfigureDiagnosticListener(builder); + // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -181,7 +185,6 @@ namespace Ocelot.Middleware builder.Map(configuration.AdministrationPath, app => { var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}"; - app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = identityServerUrl, @@ -206,5 +209,22 @@ namespace Ocelot.Middleware builder.Use(middleware); } } + + /// + /// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends + /// + /// + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { + var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + + //https://github.com/TomPallister/Ocelot/pull/87 not sure why only for dev envs and marc disapeered so just merging and maybe change one day? + if (!env.IsProduction()) + { + var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); + var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); + diagnosticListener.SubscribeWithAdapter(listener); + } + } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 32cca96e..c7626bc7 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -26,10 +26,12 @@ + + diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index f3001e87..b359eadb 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -26,11 +26,9 @@ namespace Ocelot.QueryStrings.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling query string builder middleware"); - if (DownstreamRoute.ReRoute.ClaimsToQueries.Any()) { - _logger.LogDebug("this route has instructions to convert claims to queries"); + _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(DownstreamRoute.ReRoute.ClaimsToQueries, context.User.Claims, DownstreamRequest); @@ -43,11 +41,7 @@ namespace Ocelot.QueryStrings.Middleware } } - _logger.LogDebug("calling next middleware"); - await _next.Invoke(context); - - _logger.LogDebug("succesfully called next middleware"); } } } diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index a9a5564e..e8dbaeef 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -31,18 +31,12 @@ namespace Ocelot.RateLimit.Middleware public async Task Invoke(HttpContext context) { - _logger.TraceMiddlewareEntry(); - var options = DownstreamRoute.ReRoute.RateLimitOptions; // check if rate limiting is enabled if (!DownstreamRoute.ReRoute.EnableEndpointEndpointRateLimiting) { _logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}"); - - _logger.TraceInvokeNext(); await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); return; } // compute identity from request @@ -52,11 +46,7 @@ namespace Ocelot.RateLimit.Middleware if (IsWhitelisted(identity, options)) { _logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting"); - - _logger.TraceInvokeNext(); await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); return; } @@ -78,7 +68,6 @@ namespace Ocelot.RateLimit.Middleware var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); // break execution await ReturnQuotaExceededResponse(context, options, retrystring); - _logger.TraceMiddlewareCompleted(); return; } @@ -91,10 +80,7 @@ namespace Ocelot.RateLimit.Middleware context.Response.OnStarting(SetRateLimitHeaders, state: headers); } - _logger.TraceInvokeNext(); await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); } public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) diff --git a/src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs index 08222d9d..403e550d 100644 --- a/src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/RequestIdMiddleware.cs @@ -20,7 +20,7 @@ namespace Ocelot.RequestId.Middleware public RequestIdMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository requestScopedDataRepository) - :base(requestScopedDataRepository) + : base(requestScopedDataRepository) { _next = next; _logger = loggerFactory.CreateLogger(); @@ -28,15 +28,9 @@ namespace Ocelot.RequestId.Middleware } public async Task Invoke(HttpContext context) - { - _logger.TraceMiddlewareEntry(); - + { SetOcelotRequestId(context); - - _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); - _logger.TraceMiddlewareCompleted(); + await _next.Invoke(context); } private void SetOcelotRequestId(HttpContext context) diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 20313e8f..571113d6 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -62,14 +62,6 @@ namespace Ocelot.Responder } } - private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) - { - if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) - { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); - } - } - public void SetErrorResponseOnContext(HttpContext context, int statusCode) { context.Response.OnStarting(x => @@ -78,5 +70,13 @@ namespace Ocelot.Responder return Task.CompletedTask; }, context); } + + private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) + { + if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) + { + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); + } + } } } \ No newline at end of file diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 7e843486..b6ed2717 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -34,15 +34,12 @@ namespace Ocelot.Responder.Middleware public async Task Invoke(HttpContext context) { - _logger.TraceMiddlewareEntry(); - _logger.TraceInvokeNext(); - await _next.Invoke(context); - _logger.TraceInvokeNextCompleted(); + await _next.Invoke(context); if (PipelineError) { var errors = PipelineErrors; - _logger.LogError($"{errors.Count} pipeline errors found in {MiddlwareName}. Setting error response status code"); + _logger.LogError($"{PipelineErrors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); SetErrorResponse(context, errors); } @@ -51,13 +48,11 @@ namespace Ocelot.Responder.Middleware _logger.LogDebug("no pipeline errors, setting and returning completed response"); await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); } - _logger.TraceMiddlewareCompleted(); } private void SetErrorResponse(HttpContext context, List errors) { var statusCode = _codeMapper.Map(errors); - _responder.SetErrorResponseOnContext(context, statusCode); } } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index c0d143b9..64116034 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -37,237 +37,209 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_401_using_identity_server_access_token() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = _identityServerRootUrl, + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_401_using_identity_server_reference_token() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); } [Fact] public void should_return_response_200_using_identity_server() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = _identityServerRootUrl, + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); } [Fact] public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = _identityServerRootUrl, + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); } [Fact] public void should_return_201_using_identity_server_access_token() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = _identityServerRootUrl, + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); } [Fact] public void should_return_201_using_identity_server_reference_token() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamPort = _downstreamServicePort, - DownstreamHost = _downstreamServiceHost, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = _identityServerRootUrl, + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 011bb679..3eebd000 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -31,191 +31,199 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_authorising_route() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + RouteClaimsRequirement = + { + {"UserType", "registered"} + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); } [Fact] public void should_return_response_403_authorising_route() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } - }; + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserId", "Claims[sub] > value[1] > |"} + }, + RouteClaimsRequirement = + { + {"UserType", "registered"} + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) + .BDDfy(); } [Fact] public void should_return_response_200_using_identity_server_with_allowed_scope() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamHost = "localhost", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamHost = "localhost", + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); } [Fact] public void should_return_response_403_using_identity_server_with_scope_not_allowed() { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 51876, - DownstreamHost = "localhost", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List{ "api", "openid", "offline_access" }, - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } - } - }; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamHost = "localhost", + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List{ "api", "openid", "offline_access" }, + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) - .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) + .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 88a294a0..babb520e 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -33,63 +33,65 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_and_foward_claim_as_header() { - var user = new TestUser() - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } - }; + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1") + } + }; - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 52876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 52876, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:52888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret", - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } - }; + { + "openid", "offline_access", "api" + }, + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:52888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:52888", "api", AccessTokenType.Jwt, user)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200)) - .And(x => _steps.GivenIHaveAToken("http://localhost:52888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:52888", "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200)) + .And(x => _steps.GivenIHaveAToken("http://localhost:52888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) + .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url, int statusCode) diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index a6162c5f..744cfab2 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -33,63 +33,65 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_and_foward_claim_as_query_string() { - var user = new TestUser() - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } - }; + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1") + } + }; - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamPort = 57876, - DownstreamScheme = "http", - DownstreamHost = "localhost", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 57876, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:57888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret", - }, - AddQueriesToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } - }; + { + "openid", "offline_access", "api" + }, + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig{ + ProviderRootUrl = "http://localhost:57888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + }, + AddQueriesToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + } + } + } + }; - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:57888", "api", AccessTokenType.Jwt, user)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) - .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:57888", "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) + .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) + .BDDfy(); } private void GivenThereIsAServiceRunningOn(string url, int statusCode) diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 0ab4cc6a..231f8410 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; +using Ocelot.Authentication.JsonConverters; using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; @@ -29,7 +30,6 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } - [Fact] public void should_return_response_200_with_simple_url() { @@ -105,7 +105,9 @@ namespace Ocelot.AcceptanceTests var json = reader.ReadToEnd(); - _config = JsonConvert.DeserializeObject(json); + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new AuthenticationConfigConverter()); + _config = JsonConvert.DeserializeObject(json, settings); var response = JsonConvert.SerializeObject(true); @@ -167,4 +169,4 @@ namespace Ocelot.AcceptanceTests _steps.Dispose(); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 0faafe84..eea05872 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -15,7 +15,7 @@ - + PreserveNewest @@ -30,6 +30,7 @@ + diff --git a/test/Ocelot.AcceptanceTests/appsettings.json b/test/Ocelot.AcceptanceTests/appsettings.json new file mode 100644 index 00000000..df0788de --- /dev/null +++ b/test/Ocelot.AcceptanceTests/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Error", + "System": "Error", + "Microsoft": "Error" + } + } +} diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 2fa2c05c..39721df6 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; +using Ocelot.Cache; using Ocelot.Configuration.File; using Ocelot.ManualTest; using Shouldly; @@ -19,15 +20,19 @@ namespace Ocelot.IntegrationTests public class AdministrationTests : IDisposable { private readonly HttpClient _httpClient; + private readonly HttpClient _httpClientTwo; private HttpResponseMessage _response; private IWebHost _builder; private IWebHostBuilder _webHostBuilder; private readonly string _ocelotBaseUrl; private BearerToken _token; + private IWebHostBuilder _webHostBuilderTwo; + private IWebHost _builderTwo; public AdministrationTests() { _httpClient = new HttpClient(); + _httpClientTwo = new HttpClient(); _ocelotBaseUrl = "http://localhost:5000"; _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); } @@ -70,6 +75,27 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007")) + .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + [Fact] public void should_return_file_configuration() { @@ -95,7 +121,12 @@ namespace Ocelot.IntegrationTests DownstreamScheme = "https", DownstreamPathTemplate = "/", UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Geoff" + } }, new FileReRoute() { @@ -104,7 +135,12 @@ namespace Ocelot.IntegrationTests DownstreamScheme = "https", DownstreamPathTemplate = "/", UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Dave" + } } } }; @@ -193,6 +229,87 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_clear_region() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10 + } + } + } + }; + + var regionToClear = "gettest"; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) + .BDDfy(); + } + + private void GivenAnotherOcelotIsRunning(string baseUrl) + { + _httpClientTwo.BaseAddress = new Uri(baseUrl); + + _webHostBuilderTwo = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => { + x.AddSingleton(_webHostBuilderTwo); + }) + .UseStartup(); + + _builderTwo = _webHostBuilderTwo.Build(); + + _builderTwo.Start(); + } + + private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() + { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); + } + + private void WhenIGetUrlOnTheSecondOcelot(string url) + { + _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + _response = _httpClientTwo.GetAsync(url).Result; + } + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) { var json = JsonConvert.SerializeObject(updatedConfiguration); @@ -201,6 +318,13 @@ namespace Ocelot.IntegrationTests _response = _httpClient.PostAsync(url, content).Result; } + private void ThenTheResponseShouldBe(List expected) + { + var content = _response.Content.ReadAsStringAsync().Result; + var result = JsonConvert.DeserializeObject(content); + result.Value.ShouldBe(expected); + } + private void ThenTheResponseShouldBe(FileConfiguration expected) { var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); @@ -298,6 +422,11 @@ namespace Ocelot.IntegrationTests _response = _httpClient.GetAsync(url).Result; } + private void WhenIDeleteOnTheApiGateway(string url) + { + _response = _httpClient.DeleteAsync(url).Result; + } + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) { _response.StatusCode.ShouldBe(expectedHttpStatusCode); @@ -305,6 +434,8 @@ namespace Ocelot.IntegrationTests public void Dispose() { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); _builder?.Dispose(); _httpClient?.Dispose(); } diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index db4b23d2..1556111a 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -15,7 +15,7 @@ - + PreserveNewest @@ -30,6 +30,7 @@ + diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 2512a87d..2922d92c 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -15,7 +15,6 @@ using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; -using System.Threading; using System.Collections.Concurrent; namespace Ocelot.IntegrationTests @@ -23,11 +22,9 @@ namespace Ocelot.IntegrationTests public class ThreadSafeHeadersTests : IDisposable { private readonly HttpClient _httpClient; - private HttpResponseMessage _response; private IWebHost _builder; private IWebHostBuilder _webHostBuilder; private readonly string _ocelotBaseUrl; - private BearerToken _token; private IWebHost _downstreamBuilder; private readonly Random _random; private readonly ConcurrentBag _results; @@ -61,7 +58,7 @@ namespace Ocelot.IntegrationTests }; this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) @@ -135,7 +132,7 @@ namespace Ocelot.IntegrationTests text = File.ReadAllText(configurationPath); } - public void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) { var tasks = new Task[times]; diff --git a/test/Ocelot.IntegrationTests/appsettings.json b/test/Ocelot.IntegrationTests/appsettings.json index 503cc778..df0788de 100644 --- a/test/Ocelot.IntegrationTests/appsettings.json +++ b/test/Ocelot.IntegrationTests/appsettings.json @@ -3,8 +3,8 @@ "IncludeScopes": true, "LogLevel": { "Default": "Error", - "System": "Information", - "Microsoft": "Information" + "System": "Error", + "Microsoft": "Error" } } } diff --git a/test/Ocelot.IntegrationTests/idsrv3test.pfx b/test/Ocelot.IntegrationTests/idsrv3test.pfx new file mode 100644 index 00000000..0247dea0 Binary files /dev/null and b/test/Ocelot.IntegrationTests/idsrv3test.pfx differ diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index f541111f..57f23ad0 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -22,6 +22,7 @@ + diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 765326cf..6d2ee544 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -14,13 +14,16 @@ }, "AuthenticationOptions": { "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ApiName": "api", "AllowedScopes": [ "openid", "offline_access" ], - "ApiSecret": "secret" + "IdentityServerConfig": { + "ProviderRootUrl": "http://localhost:52888", + "ApiName": "api", + "ApiSecret": "secret", + "RequireHttps": false + } }, "AddHeadersToRequest": { "CustomerId": "Claims[CustomerId] > value", diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs index 8bf53607..55e1a05c 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs @@ -31,17 +31,19 @@ namespace Ocelot.UnitTests.Authentication _authenticationHandlerFactory = new AuthenticationHandlerFactory(_creator.Object); } - [Fact] - public void should_return_identity_server_access_token_handler() + [Theory] + [InlineData("IdentityServer")] + [InlineData("Jwt")] + public void should_return_access_token_handler(string provider) { var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider("IdentityServer") + .WithProvider(provider) .Build(); this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) .And(x => x.GivenTheCreatorReturns()) .When(x => x.WhenIGetFromTheFactory()) - .Then(x => x.ThenTheHandlerIsReturned("IdentityServer")) + .Then(x => x.ThenTheHandlerIsReturned(provider)) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index be709dff..819c48d7 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,108 +1,76 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Authentication.Handler; -using Ocelot.Authentication.Handler.Factory; -using Ocelot.Authentication.Middleware; -using Ocelot.Cache.Middleware; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Authentication +namespace Ocelot.UnitTests.Authentication { - public class AuthenticationMiddlewareTests : IDisposable + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Authentication.Handler.Factory; + using Ocelot.Authentication.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class AuthenticationMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly Mock _authFactory; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _downstreamRoute; public AuthenticationMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); _authFactory = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_authFactory.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseAuthenticationMiddleware(); - app.Run(async x => - { - await x.Response.WriteAsync("The user is authenticated"); - }); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] public void should_call_next_middleware_if_route_is_not_authenticated() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheUserIsAuthenticated()) .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_authFactory.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseAuthenticationMiddleware(); + + app.Run(async x => + { + await x.Response.WriteAsync("The user is authenticated"); + }); + } + private void ThenTheUserIsAuthenticated() { - var content = _result.Content.ReadAsStringAsync().Result; + var content = ResponseMessage.Content.ReadAsStringAsync().Result; content.ShouldBe("The user is authenticated"); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 9bf15b42..ca346974 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -1,66 +1,32 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Security.Claims; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Authorisation; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Authorization +namespace Ocelot.UnitTests.Authorization { - using Authorisation.Middleware; + using System.Collections.Generic; + using System.Security.Claims; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Authorisation; + using Ocelot.Authorisation.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; - public class AuthorisationMiddlewareTests : IDisposable + public class AuthorisationMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly Mock _authService; private readonly Mock _authScopesService; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _downstreamRoute; public AuthorisationMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); _authService = new Mock(); _authScopesService = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_authService.Object); - x.AddSingleton(_authScopesService.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseAuthorisationMiddleware(); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -77,6 +43,28 @@ namespace Ocelot.UnitTests.Authorization .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_authService.Object); + services.AddSingleton(_authScopesService.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseAuthorisationMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + private void GivenTheAuthServiceReturns(Response expected) { _authService @@ -90,25 +78,5 @@ namespace Ocelot.UnitTests.Authorization .Verify(x => x.Authorise(It.IsAny(), It.IsAny>()), Times.Once); } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs index 87752a52..54107298 100644 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs @@ -16,20 +16,21 @@ namespace Ocelot.UnitTests.Cache private string _value; private string _resultGet; private TimeSpan _ttlSeconds; + private string _region; public CacheManagerCacheTests() { _mockCacheManager = new Mock>(); _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); } + [Fact] public void should_get_from_cache() { - this.Given(x => x.GivenTheFollowingIsCached("someKey", "someValue")) + this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue")) .When(x => x.WhenIGetFromTheCache()) .Then(x => x.ThenTheResultIs("someValue")) .BDDfy(); - } [Fact] @@ -40,13 +41,37 @@ namespace Ocelot.UnitTests.Cache .BDDfy(); } + [Fact] + public void should_delete_key_from_cache() + { + this.Given(_ => GivenTheFollowingRegion("fookey")) + .When(_ => WhenIDeleteTheRegion("fookey")) + .Then(_ => ThenTheRegionIsDeleted("fookey")) + .BDDfy(); + } + + private void WhenIDeleteTheRegion(string region) + { + _ocelotOcelotCacheManager.ClearRegion(region); + } + + private void ThenTheRegionIsDeleted(string region) + { + _mockCacheManager + .Verify(x => x.ClearRegion(region), Times.Once); + } + + private void GivenTheFollowingRegion(string key) + { + _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); + } + private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) { _key = key; _value = value; _ttlSeconds = ttlSeconds; - - _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds); + _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); } private void ThenTheCacheIsCalledCorrectly() @@ -62,15 +87,16 @@ namespace Ocelot.UnitTests.Cache private void WhenIGetFromTheCache() { - _resultGet = _ocelotOcelotCacheManager.Get(_key); + _resultGet = _ocelotOcelotCacheManager.Get(_key, _region); } - private void GivenTheFollowingIsCached(string key, string value) + private void GivenTheFollowingIsCached(string key, string region, string value) { _key = key; _value = value; + _region = region; _mockCacheManager - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(value); } } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index dfc0220f..11c15d8d 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,65 +1,36 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Cache; -using Ocelot.Cache.Middleware; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { - public class OutputCacheMiddlewareTests + using System; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Cache; + using Ocelot.Cache.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 OutputCacheMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock> _cacheManager; - private readonly Mock _scopedRepo; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private HttpResponseMessage _response; public OutputCacheMiddlewareTests() { _cacheManager = new Mock>(); - _scopedRepo = new Mock(); - _url = "http://localhost:51879"; - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_cacheManager.Object); - x.AddSingleton(_scopedRepo.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseOutputCacheMiddleware(); - }); - - _scopedRepo + ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -85,32 +56,60 @@ namespace Ocelot.UnitTests.Cache .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_cacheManager.Object); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseOutputCacheMiddleware(); + } + + private void GivenThereIsACachedResponse(HttpResponseMessage response) + { + _response = response; + _cacheManager + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(_response); + } + + private void GivenResponseIsNotCached() + { + ScopedRepository + .Setup(x => x.Get("HttpResponseMessage")) + .Returns(new OkResponse(new HttpResponseMessage())); + } private void GivenTheDownstreamRouteIs() { var reRoute = new ReRouteBuilder() .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100)) + .WithCacheOptions(new CacheOptions(100, "kanken")) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); var downstreamRoute = new DownstreamRoute(new List(), reRoute); - _scopedRepo + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse(downstreamRoute)); } private void GivenThereAreNoErrors() { - _scopedRepo + ScopedRepository .Setup(x => x.Get("OcelotMiddlewareError")) .Returns(new OkResponse(false)); } private void GivenThereIsADownstreamUrl() { - _scopedRepo + ScopedRepository .Setup(x => x.Get("DownstreamUrl")) .Returns(new OkResponse("anything")); } @@ -118,33 +117,13 @@ namespace Ocelot.UnitTests.Cache private void ThenTheCacheGetIsCalledCorrectly() { _cacheManager - .Verify(x => x.Get(It.IsAny()), Times.Once); + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); } private void ThenTheCacheAddIsCalledCorrectly() { _cacheManager - .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - private void GivenResponseIsNotCached() - { - _scopedRepo - .Setup(x => x.Get("HttpResponseMessage")) - .Returns(new OkResponse(new HttpResponseMessage())); - } - - private void GivenThereIsACachedResponse(HttpResponseMessage response) - { - _response = response; - _cacheManager - .Setup(x => x.Get(It.IsAny())) - .Returns(_response); - } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs new file mode 100644 index 00000000..6a475425 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/RegionCreatorTests.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Ocelot.Cache; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class RegionCreatorTests + { + private string _result; + private FileReRoute _reRoute; + + [Fact] + public void should_create_region() + { + var reRoute = new FileReRoute + { + UpstreamHttpMethod = new List { "Get" }, + UpstreamPathTemplate = "/testdummy" + }; + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("Gettestdummy")) + .BDDfy(); + } + + [Fact] + public void should_use_region() + { + var reRoute = new FileReRoute + { + FileCacheOptions = new FileCacheOptions + { + Region = "region" + } + }; + + this.Given(_ => GivenTheReRoute(reRoute)) + .When(_ => WhenICreateTheRegion()) + .Then(_ => ThenTheRegionIs("region")) + .BDDfy(); + } + + private void GivenTheReRoute(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreateTheRegion() + { + RegionCreator regionCreator = new RegionCreator(); + _result = regionCreator.Create(_reRoute); + } + + private void ThenTheRegionIs(string expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index d17d7978..3eb86d1e 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -1,65 +1,31 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Cache.Middleware; -using Ocelot.Claims; -using Ocelot.Claims.Middleware; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Claims +namespace Ocelot.UnitTests.Claims { - public class ClaimsBuilderMiddlewareTests : IDisposable + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + 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 : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly Mock _addHeaders; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private Response _downstreamRoute; - private HttpResponseMessage _result; public ClaimsBuilderMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); _addHeaders = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - - x.AddLogging(); - x.AddSingleton(_addHeaders.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseClaimsBuilderMiddleware(); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -82,6 +48,27 @@ namespace Ocelot.UnitTests.Claims .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addHeaders.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseClaimsBuilderMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + private void GivenTheAddClaimsToRequestReturns() { _addHeaders @@ -96,24 +83,5 @@ namespace Ocelot.UnitTests.Claims .Verify(x => x.SetClaimsOnContext(It.IsAny>(), It.IsAny()), Times.Once); } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs index ee1292aa..7e2108ee 100644 --- a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs @@ -11,13 +11,13 @@ namespace Ocelot.UnitTests.Configuration { public class AuthenticationOptionsCreatorTests { - private AuthenticationOptionsCreator _authOptionsCreator; + private readonly AuthenticationOptionsCreator _authOptionsCreator; private FileReRoute _fileReRoute; private AuthenticationOptions _result; public AuthenticationOptionsCreatorTests() { - _authOptionsCreator = new AuthenticationOptionsCreator(); + _authOptionsCreator = new AuthenticationOptionsCreator(new AuthenticationProviderConfigCreator()); } [Fact] @@ -28,26 +28,68 @@ namespace Ocelot.UnitTests.Configuration AuthenticationOptions = new FileAuthenticationOptions { Provider = "Geoff", - ProviderRootUrl = "http://www.bbc.co.uk/", - ApiName = "Laura", - RequireHttps = true, - AllowedScopes = new List {"cheese"}, - ApiSecret = "secret" + IdentityServerConfig = new FileIdentityServerConfig() + { + ProviderRootUrl = "http://www.bbc.co.uk/", + ApiName = "Laura", + RequireHttps = true, + ApiSecret = "secret" + }, + AllowedScopes = new List { "cheese" }, + } }; + var authenticationConfig = new IdentityServerConfigBuilder() + .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ProviderRootUrl) + .WithApiName(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiName) + .WithRequireHttps(fileReRoute.AuthenticationOptions.IdentityServerConfig.RequireHttps) + .WithApiSecret(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiSecret) + .Build(); + var expected = new AuthenticationOptionsBuilder() .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) - .WithApiName(fileReRoute.AuthenticationOptions?.ApiName) - .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithApiSecret(fileReRoute.AuthenticationOptions?.ApiSecret) + .WithConfig(authenticationConfig) .Build(); this.Given(x => x.GivenTheFollowing(fileReRoute)) .When(x => x.WhenICreateTheAuthenticationOptions()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) + .Then(x => x.ThenTheFollowingIdentityServerConfigIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_Jwt_auth_options() + { + var fileReRoute = new FileReRoute() + { + AuthenticationOptions = new FileAuthenticationOptions + { + Provider = "Jwt", + JwtConfig = new FileJwtConfig() + { + Audience = "Audience", + Authority = "Authority" + }, + AllowedScopes = new List { "cheese" } + } + }; + + var authenticationConfig = new JwtConfigBuilder() + .WithAudience(fileReRoute.AuthenticationOptions?.JwtConfig?.Audience) + .WithAuthority(fileReRoute.AuthenticationOptions?.JwtConfig?.Authority) + .Build(); + + var expected = new AuthenticationOptionsBuilder() + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) + .WithConfig(authenticationConfig) + .Build(); + + this.Given(x => x.GivenTheFollowing(fileReRoute)) + .When(x => x.WhenICreateTheAuthenticationOptions()) + .Then(x => x.ThenTheFollowingJwtConfigIsReturned(expected)) .BDDfy(); } @@ -61,14 +103,31 @@ namespace Ocelot.UnitTests.Configuration _result = _authOptionsCreator.Create(_fileReRoute); } - private void ThenTheFollowingIsReturned(AuthenticationOptions expected) + private void ThenTheFollowingJwtConfigIsReturned(AuthenticationOptions expected) { _result.AllowedScopes.ShouldBe(expected.AllowedScopes); _result.Provider.ShouldBe(expected.Provider); - _result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl); - _result.RequireHttps.ShouldBe(expected.RequireHttps); - _result.ApiName.ShouldBe(expected.ApiName); - _result.ApiSecret.ShouldBe(expected.ApiSecret); + + var _resultSettings = _result.Config as JwtConfig; + var expectedSettngs = expected.Config as JwtConfig; + + _resultSettings.Audience.ShouldBe(expectedSettngs.Audience); + _resultSettings.Authority.ShouldBe(expectedSettngs.Authority); + + } + + private void ThenTheFollowingIdentityServerConfigIsReturned(AuthenticationOptions expected) + { + _result.AllowedScopes.ShouldBe(expected.AllowedScopes); + _result.Provider.ShouldBe(expected.Provider); + + var _resultSettings = _result.Config as IdentityServerConfig; + var expectedSettngs = expected.Config as IdentityServerConfig; + + _resultSettings.ProviderRootUrl.ShouldBe(expectedSettngs.ProviderRootUrl); + _resultSettings.RequireHttps.ShouldBe(expectedSettngs.RequireHttps); + _resultSettings.ApiName.ShouldBe(expectedSettngs.ApiName); + _resultSettings.ApiSecret.ShouldBe(expectedSettngs.ApiSecret); } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index bd8c46d3..4cb53118 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; +using Ocelot.Cache; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; @@ -17,6 +18,10 @@ using Xunit; namespace Ocelot.UnitTests.Configuration { + using System.Collections; + + using Ocelot.UnitTests.TestData; + public class FileConfigurationCreatorTests { private readonly Mock> _fileConfig; @@ -39,6 +44,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _qosOptionsCreator; private Mock _fileReRouteOptionsCreator; private Mock _rateLimitOptions; + private Mock _regionCreator; public FileConfigurationCreatorTests() { @@ -59,6 +65,7 @@ namespace Ocelot.UnitTests.Configuration _qosOptionsCreator = new Mock(); _fileReRouteOptionsCreator = new Mock(); _rateLimitOptions = new Mock(); + _regionCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, @@ -66,7 +73,51 @@ namespace Ocelot.UnitTests.Configuration _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, - _rateLimitOptions.Object); + _rateLimitOptions.Object, _regionCreator.Object); + } + + [Fact] + public void should_call_region_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "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 => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingRegionIsReturned("region")) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) + .BDDfy(); + } + + private void GivenTheFollowingRegionIsReturned(string region) + { + _regionCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(region); + } + + private void ThenTheRegionCreatorIsCalledCorrectly(string expected) + { + _regionCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); } [Fact] @@ -393,20 +444,18 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_create_with_headers_to_extract() + [Theory] + [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] + public void should_create_with_headers_to_extract(string provider, IAuthenticationConfig config, FileConfiguration fileConfig) { var reRouteOptions = new ReRouteOptionsBuilder() .WithIsAuthenticated(true) .Build(); var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider("IdentityServer") - .WithProviderRootUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithApiSecret("secret") - .WithApiName("api") + .WithProvider(provider) .WithAllowedScopes(new List()) + .WithConfig(config) .Build(); var expected = new List @@ -423,59 +472,32 @@ namespace Ocelot.UnitTests.Configuration .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, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes= new List(), - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName= "api", - ApiSecret = "secret" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - } - } - } - })) + this.Given(x => x.GivenTheConfigIs(fileConfig)) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheClaimsToThingCreatorReturns(new List{new ClaimToThing("CustomerId", "CustomerId", "", 0)})) + .And(x => x.GivenTheClaimsToThingCreatorReturns(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0) })) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(expected)) + .And(x => x.ThenTheAuthenticationOptionsAre(provider, expected)) .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) .BDDfy(); } - [Fact] - public void should_create_with_authentication_properties() + [Theory] + [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] + public void should_create_with_authentication_properties(string provider, IAuthenticationConfig config, FileConfiguration fileConfig) { var reRouteOptions = new ReRouteOptionsBuilder() .WithIsAuthenticated(true) .Build(); - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider("IdentityServer") - .WithProviderRootUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithApiSecret("secret") - .WithApiName("api") - .WithAllowedScopes(new List()) - .Build(); + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider(provider) + .WithAllowedScopes(new List()) + .WithConfig(config) + .Build(); var expected = new List { @@ -487,35 +509,14 @@ namespace Ocelot.UnitTests.Configuration .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, - AuthenticationOptions = new FileAuthenticationOptions - { - AllowedScopes = new List(), - Provider = "IdentityServer", - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName= "api", - ApiSecret = "secret" - } - } - } - })) + this.Given(x => x.GivenTheConfigIs(fileConfig)) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(expected)) + .And(x => x.ThenTheAuthenticationOptionsAre(provider, expected)) .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) .BDDfy(); } @@ -586,7 +587,7 @@ namespace Ocelot.UnitTests.Configuration } } - private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) + private void ThenTheAuthenticationOptionsAre(string provider, List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) { @@ -595,11 +596,25 @@ namespace Ocelot.UnitTests.Configuration result.AllowedScopes.ShouldBe(expected.AllowedScopes); result.Provider.ShouldBe(expected.Provider); - result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl); - result.RequireHttps.ShouldBe(expected.RequireHttps); - result.ApiName.ShouldBe(expected.ApiName); - result.ApiSecret.ShouldBe(expected.ApiSecret); + if (provider.ToLower() == "identityserver") + { + var config = result.Config as IdentityServerConfig; + var expectedConfig = expected.Config as IdentityServerConfig; + + config.ProviderRootUrl.ShouldBe(expectedConfig.ProviderRootUrl); + config.RequireHttps.ShouldBe(expectedConfig.RequireHttps); + config.ApiName.ShouldBe(expectedConfig.ApiName); + config.ApiSecret.ShouldBe(expectedConfig.ApiSecret); + } + else + { + var config = result.Config as JwtConfig; + var expectedConfig = expected.Config as JwtConfig; + + config.Audience.ShouldBe(expectedConfig.Audience); + config.Authority.ShouldBe(expectedConfig.Authority); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs new file mode 100644 index 00000000..8d100e10 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs @@ -0,0 +1,16 @@ +using Ocelot.Configuration.Creator; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class IdentityServerConfigurationCreatorTests + { + [Fact] + public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this() + { + var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); + result.ApiName.ShouldBe("admin"); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs new file mode 100644 index 00000000..100597a0 --- /dev/null +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -0,0 +1,46 @@ +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Ocelot.Controllers; +using System; +using Moq; +using Ocelot.Cache; +using System.Net.Http; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace Ocelot.UnitTests.Controllers +{ + public class OutputCacheControllerTests + { + private OutputCacheController _controller; + private Mock> _cache; + private IActionResult _result; + + public OutputCacheControllerTests() + { + _cache = new Mock>(); + _controller = new OutputCacheController(_cache.Object); + } + + [Fact] + public void should_delete_key() + { + this.When(_ => WhenIDeleteTheKey("a")) + .Then(_ => ThenTheKeyIsDeleted("a")) + .BDDfy(); + } + + private void ThenTheKeyIsDeleted(string key) + { + _result.ShouldBeOfType(); + _cache + .Verify(x => x.ClearRegion(key), Times.Once); + } + + private void WhenIDeleteTheKey(string key) + { + _result = _controller.Delete(key); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 0b3aa149..fb312e21 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -1,61 +1,29 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder +namespace Ocelot.UnitTests.DownstreamRouteFinder { - public class DownstreamRouteFinderMiddlewareTests : IDisposable + using System.Collections.Generic; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class DownstreamRouteFinderMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _downstreamRouteFinder; - private readonly Mock _scopedRepository; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private Response _downstreamRoute; - private HttpResponseMessage _result; public DownstreamRouteFinderMiddlewareTests() { - _url = "http://localhost:51879"; _downstreamRouteFinder = new Mock(); - _scopedRepository = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_downstreamRouteFinder.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseDownstreamRouteFinderMiddleware(); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -73,16 +41,17 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } - - private void ThenTheScopedDataRepositoryIsCalledCorrectly() + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) { - _scopedRepository - .Verify(x => x.Add("DownstreamRoute", _downstreamRoute.Data), Times.Once()); + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_downstreamRouteFinder.Object); + services.AddSingleton(ScopedRepository.Object); } - private void WhenICallTheMiddleware() + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) { - _result = _client.GetAsync(_url).Result; + app.UseDownstreamRouteFinderMiddleware(); } private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) @@ -93,10 +62,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .ReturnsAsync(_downstreamRoute); } - public void Dispose() + private void ThenTheScopedDataRepositoryIsCalledCorrectly() { - _client.Dispose(); - _server.Dispose(); + ScopedRepository + .Verify(x => x.Add("DownstreamRoute", _downstreamRoute.Data), Times.Once()); } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index ae322150..1af70f71 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,73 +1,45 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.DownstreamUrlCreator; -using Ocelot.DownstreamUrlCreator.Middleware; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.Values; -using TestStack.BDDfy; -using Xunit; -using Shouldly; - -namespace Ocelot.UnitTests.DownstreamUrlCreator +namespace Ocelot.UnitTests.DownstreamUrlCreator { - public class DownstreamUrlCreatorMiddlewareTests : IDisposable + using System; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.DownstreamUrlCreator; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.Values; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + + public class DownstreamUrlCreatorMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _downstreamUrlTemplateVariableReplacer; - private readonly Mock _scopedRepository; private readonly Mock _urlBuilder; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private Response _downstreamRoute; private OkResponse _downstreamPath; private HttpRequestMessage _downstreamRequest; - private HttpResponseMessage _result; public DownstreamUrlCreatorMiddlewareTests() { - _url = "http://localhost:51879"; _downstreamUrlTemplateVariableReplacer = new Mock(); - _scopedRepository = new Mock(); _urlBuilder = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); - x.AddSingleton(_scopedRepository.Object); - x.AddSingleton(_urlBuilder.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseDownstreamUrlCreatorMiddleware(); - }); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - _scopedRepository + ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -88,10 +60,24 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(_urlBuilder.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseDownstreamUrlCreatorMiddleware(); + } + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } @@ -109,20 +95,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .Returns(_downstreamPath); } - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - private void ThenTheDownstreamRequestUriIs(string expectedUri) { _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs new file mode 100644 index 00000000..5bf848cd --- /dev/null +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -0,0 +1,87 @@ +namespace Ocelot.UnitTests.Errors +{ + using System; + using System.Net; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Errors.Middleware; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.AspNetCore.Http; + + public class ExceptionHandlerMiddlewareTests : ServerHostedMiddlewareTest + { + bool _shouldThrowAnException = false; + + public ExceptionHandlerMiddlewareTests() + { + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void NoDownstreamException() + { + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsOk()) + .BDDfy(); + } + + [Fact] + public void DownstreamException() + { + this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsError()) + .BDDfy(); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseExceptionHandlerMiddleware(); + app.Run(DownstreamExceptionSimulator); + } + + private async Task DownstreamExceptionSimulator(HttpContext context) + { + await Task.CompletedTask; + + if (_shouldThrowAnException) + { + throw new Exception("BOOM"); + } + + context.Response.StatusCode = (int)HttpStatusCode.OK; + } + + private void GivenAnExceptionWillNotBeThrownDownstream() + { + _shouldThrowAnException = false; + } + + private void GivenAnExceptionWillBeThrownDownstream() + { + _shouldThrowAnException = true; + } + + private void ThenTheResponseIsOk() + { + ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private void ThenTheResponseIsError() + { + ResponseMessage.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 395d7862..76a75d0a 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -1,69 +1,37 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -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.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Headers +namespace Ocelot.UnitTests.Headers { - public class HttpRequestHeadersBuilderMiddlewareTests : IDisposable + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + 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.Responses; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequestHeadersBuilderMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly Mock _addHeaders; private readonly HttpRequestMessage _downstreamRequest; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private Response _downstreamRoute; - private HttpResponseMessage _result; public HttpRequestHeadersBuilderMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); _addHeaders = new Mock(); - - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_addHeaders.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseHttpRequestHeadersBuilderMiddleware(); - }); - _downstreamRequest = new HttpRequestMessage(); - - _scopedRepository + ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -86,6 +54,27 @@ namespace Ocelot.UnitTests.Headers .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addHeaders.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequestHeadersBuilderMiddleware(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + private void GivenTheAddHeadersToDownstreamRequestReturnsOk() { _addHeaders @@ -104,24 +93,5 @@ namespace Ocelot.UnitTests.Headers It.IsAny>(), _downstreamRequest), Times.Once); } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamRoute); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 65074b67..e483f31c 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -1,34 +1,26 @@ -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.LoadBalancer.Middleware; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.Values; -using TestStack.BDDfy; -using Xunit; -using Shouldly; - namespace Ocelot.UnitTests.LoadBalancer { - public class LoadBalancerMiddlewareTests + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Errors; + using Ocelot.LoadBalancer.LoadBalancers; + using Ocelot.LoadBalancer.Middleware; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.Values; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class LoadBalancerMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _loadBalancerHouse; - private readonly Mock _scopedRepository; private readonly Mock _loadBalancer; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private HostAndPort _hostAndPort; private OkResponse _downstreamRoute; private ErrorResponse _getLoadBalancerHouseError; @@ -37,35 +29,17 @@ namespace Ocelot.UnitTests.LoadBalancer public LoadBalancerMiddlewareTests() { - _url = "http://localhost:51879"; _loadBalancerHouse = new Mock(); - _scopedRepository = new Mock(); _loadBalancer = new Mock(); _loadBalancerHouse = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_loadBalancerHouse.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseLoadBalancingMiddleware(); - }); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); - _scopedRepository + + ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); - _server = new TestServer(builder); - _client = _server.CreateClient(); + + GivenTheTestServerIsConfigured(); } [Fact] @@ -118,6 +92,19 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_loadBalancerHouse.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseLoadBalancingMiddleware(); + } + private void GivenTheDownStreamUrlIs(string downstreamUrl) { _downstreamRequest.RequestUri = new System.Uri(downstreamUrl); @@ -142,7 +129,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } @@ -154,7 +141,6 @@ namespace Ocelot.UnitTests.LoadBalancer .Returns(new OkResponse(_loadBalancer.Object)); } - private void GivenTheLoadBalancerHouseReturnsAnError() { _getLoadBalancerHouseError = new ErrorResponse(new List() @@ -167,49 +153,36 @@ namespace Ocelot.UnitTests.LoadBalancer .Returns(_getLoadBalancerHouseError); } - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() { - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); } - private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() + private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() { - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny>()), Times.Once); } - private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() + private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() { - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); - _scopedRepository + ScopedRepository .Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once); } - - private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) { _downstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 403db47f..201ecccf 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -19,6 +19,10 @@ True + + + + diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index 1343df7d..4bbcfd2c 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -1,67 +1,37 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.QueryStrings; -using Ocelot.QueryStrings.Middleware; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; -using System.Security.Claims; - -namespace Ocelot.UnitTests.QueryStrings +namespace Ocelot.UnitTests.QueryStrings { - public class QueryStringBuilderMiddlewareTests : IDisposable + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + 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.Builder; + + public class QueryStringBuilderMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly Mock _addQueries; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private readonly HttpRequestMessage _downstreamRequest; private Response _downstreamRoute; - private HttpResponseMessage _result; public QueryStringBuilderMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); _addQueries = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_addQueries.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseQueryStringBuilderMiddleware(); - }); _downstreamRequest = new HttpRequestMessage(); - - _scopedRepository.Setup(sr => sr.Get("DownstreamRequest")) + ScopedRepository.Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -84,6 +54,19 @@ namespace Ocelot.UnitTests.QueryStrings .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_addQueries.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseQueryStringBuilderMiddleware(); + } + private void GivenTheAddHeadersToRequestReturnsOk() { _addQueries @@ -103,23 +86,12 @@ namespace Ocelot.UnitTests.QueryStrings _downstreamRequest), Times.Once); } - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index a38d5a67..1b73e233 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -1,71 +1,33 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.TestHost; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Infrastructure.RequestData; -using Ocelot.RateLimit; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Logging; -using System.IO; -using Ocelot.RateLimit.Middleware; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Responses; -using Xunit; -using TestStack.BDDfy; -using Ocelot.Configuration.Builder; -using Shouldly; -using Ocelot.Configuration; - -namespace Ocelot.UnitTests.RateLimit +namespace Ocelot.UnitTests.RateLimit { - public class ClientRateLimitMiddlewareTests + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Logging; + using Ocelot.RateLimit; + using Ocelot.RateLimit.Middleware; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ClientRateLimitMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private OkResponse _downstreamRoute; private int responseStatusCode; public ClientRateLimitMiddlewareTests() { - _url = "http://localhost:51879/api/ClientRateLimit"; - _scopedRepository = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddMemoryCache(); - x.AddSingleton(); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseRateLimiting(); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("This is ratelimit test"); - }); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } - [Fact] public void should_call_middleware_and_ratelimiting() { @@ -98,11 +60,29 @@ namespace Ocelot.UnitTests.RateLimit .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddMemoryCache(); + services.AddSingleton(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseRateLimiting(); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("This is ratelimit test"); + }); + } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } @@ -110,28 +90,27 @@ namespace Ocelot.UnitTests.RateLimit private void WhenICallTheMiddlewareMultipleTime(int times) { var clientId = "ocelotclient1"; - // Act + for (int i = 0; i < times; i++) { - var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + var request = new HttpRequestMessage(new HttpMethod("GET"), Url); request.Headers.Add("ClientId", clientId); - var response = _client.SendAsync(request); + var response = Client.SendAsync(request); responseStatusCode = (int)response.Result.StatusCode; } - } private void WhenICallTheMiddlewareWithWhiteClient() { var clientId = "ocelotclient2"; - // Act + for (int i = 0; i < 10; i++) { - var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + var request = new HttpRequestMessage(new HttpMethod("GET"), Url); request.Headers.Add("ClientId", clientId); - var response = _client.SendAsync(request); + var response = Client.SendAsync(request); responseStatusCode = (int)response.Result.StatusCode; } } diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index f8563abf..02f2b208 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -1,63 +1,37 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Request.Builder; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; -using Ocelot.Requester.QoS; - -namespace Ocelot.UnitTests.Request +namespace Ocelot.UnitTests.Request { - public class HttpRequestBuilderMiddlewareTests : IDisposable + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Request.Builder; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using Ocelot.Requester.QoS; + using Microsoft.AspNetCore.Builder; + + public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _requestBuilder; private readonly Mock _scopedRepository; private readonly Mock _qosProviderHouse; private readonly HttpRequestMessage _downstreamRequest; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; public HttpRequestBuilderMiddlewareTests() { - _url = "http://localhost:51879"; _qosProviderHouse = new Mock(); _requestBuilder = new Mock(); _scopedRepository = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_qosProviderHouse.Object); - x.AddSingleton(_requestBuilder.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseHttpRequestBuilderMiddleware(); - }); _downstreamRequest = new HttpRequestMessage(); @@ -65,8 +39,7 @@ namespace Ocelot.UnitTests.Request .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -88,6 +61,28 @@ namespace Ocelot.UnitTests.Request .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_qosProviderHouse.Object); + services.AddSingleton(_requestBuilder.Object); + services.AddSingleton(_scopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequestBuilderMiddleware(); + } + + private void GivenTheDownStreamUrlIs(string downstreamUrl) + { + _downstreamUrl = new OkResponse(downstreamUrl); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamUrl); + } + private void GivenTheQosProviderHouseReturns(Response qosProvider) { _qosProviderHouse @@ -117,24 +112,5 @@ namespace Ocelot.UnitTests.Request _scopedRepository .Verify(x => x.Add("Request", _request.Data), Times.Once()); } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - private void GivenTheDownStreamUrlIs(string downstreamUrl) - { - _downstreamUrl = new OkResponse(downstreamUrl); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_downstreamUrl); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index d56bbce6..a34290f1 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -1,74 +1,40 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.RequestId.Middleware; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.RequestId +namespace Ocelot.UnitTests.RequestId { - public class RequestIdMiddlewareTests + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.RequestId.Middleware; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class RequestIdMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _scopedRepository; private readonly HttpRequestMessage _downstreamRequest; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; private Response _downstreamRoute; - private HttpResponseMessage _result; private string _value; private string _key; public RequestIdMiddlewareTests() { - _url = "http://localhost:51879"; - _scopedRepository = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseRequestIdMiddleware(); - - app.Run(x => - { - x.Response.Headers.Add("LSRequestId", x.TraceIdentifier); - return Task.CompletedTask; - }); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); - _downstreamRequest = new HttpRequestMessage(); - _scopedRepository + ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) .Returns(new OkResponse(_downstreamRequest)); + + GivenTheTestServerIsConfigured(); } [Fact] @@ -106,10 +72,28 @@ namespace Ocelot.UnitTests.RequestId .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseRequestIdMiddleware(); + + app.Run(x => + { + x.Response.Headers.Add("LSRequestId", x.TraceIdentifier); + return Task.CompletedTask; + }); + } + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(_downstreamRoute); } @@ -118,28 +102,17 @@ namespace Ocelot.UnitTests.RequestId { _key = key; _value = value; - _client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value); - } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; + Client.DefaultRequestHeaders.TryAddWithoutValidation(_key, _value); } private void ThenTheTraceIdIsAnything() { - _result.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty(); + ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldNotBeNullOrEmpty(); } private void ThenTheTraceIdIs(string expected) { - _result.Headers.GetValues("LSRequestId").First().ShouldBe(expected); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); + ResponseMessage.Headers.GetValues("LSRequestId").First().ShouldBe(expected); } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index b95367be..f5570f15 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,62 +1,28 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.QueryStrings.Middleware; -using Ocelot.Requester; -using Ocelot.Requester.Middleware; -using Ocelot.Requester.QoS; -using Ocelot.Responder; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester +namespace Ocelot.UnitTests.Requester { - public class HttpRequesterMiddlewareTests : IDisposable + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Logging; + using Ocelot.Requester; + using Ocelot.Requester.Middleware; + using Ocelot.Requester.QoS; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class HttpRequesterMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _requester; - private readonly Mock _scopedRepository; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _response; private OkResponse _request; public HttpRequesterMiddlewareTests() { - _url = "http://localhost:51879"; _requester = new Mock(); - _scopedRepository = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_requester.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseHttpRequesterMiddleware(); - }); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -70,6 +36,27 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_requester.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseHttpRequesterMiddleware(); + } + + private void GivenTheRequestIs(Ocelot.Request.Request request) + { + _request = new OkResponse(request); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_request); + } + private void GivenTheRequesterReturns(HttpResponseMessage response) { _response = new OkResponse(response); @@ -80,34 +67,15 @@ namespace Ocelot.UnitTests.Requester private void GivenTheScopedRepoReturns() { - _scopedRepository + ScopedRepository .Setup(x => x.Add(It.IsAny(), _response.Data)) .Returns(new OkResponse()); } private void ThenTheScopedRepoIsCalledCorrectly() { - _scopedRepository + ScopedRepository .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - private void GivenTheRequestIs(Ocelot.Request.Request request) - { - _request = new OkResponse(request); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_request); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/Responder/AnyError.cs b/test/Ocelot.UnitTests/Responder/AnyError.cs new file mode 100644 index 00000000..a4b35df0 --- /dev/null +++ b/test/Ocelot.UnitTests/Responder/AnyError.cs @@ -0,0 +1,15 @@ +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Responder +{ + class AnyError : Error + { + public AnyError() : base("blahh", OcelotErrorCode.UnknownError) + { + } + + public AnyError(OcelotErrorCode errorCode) : base("blah", errorCode) + { + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 3b79715e..b0455e38 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; +using System.Net; using Ocelot.Errors; -using Ocelot.Middleware; -using Ocelot.Requester; using Ocelot.Responder; using Shouldly; using TestStack.BDDfy; @@ -21,47 +20,127 @@ namespace Ocelot.UnitTests.Responder _codeMapper = new ErrorsToHttpStatusCodeMapper(); } - [Fact] - public void should_return_timeout() + [Theory] + [InlineData(OcelotErrorCode.UnauthenticatedError)] + public void should_return_unauthorized(OcelotErrorCode errorCode) { - this.Given(x => x.GivenThereAreErrors(new List - { - new RequestTimedOutError(new Exception()) - })) - .When(x => x.WhenIGetErrorStatusCode()) - .Then(x => x.ThenTheResponseIsStatusCodeIs(503)) - .BDDfy(); + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Unauthorized); + } + + [Theory] + [InlineData(OcelotErrorCode.CannotFindClaimError)] + [InlineData(OcelotErrorCode.ClaimValueNotAuthorisedError)] + [InlineData(OcelotErrorCode.ScopeNotAuthorisedError)] + [InlineData(OcelotErrorCode.UnauthorizedError)] + [InlineData(OcelotErrorCode.UserDoesNotHaveClaimError)] + public void should_return_forbidden(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Forbidden); + } + + [Theory] + [InlineData(OcelotErrorCode.RequestTimedOutError)] + public void should_return_service_unavailable(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.ServiceUnavailable); + } + + + [Theory] + [InlineData(OcelotErrorCode.CannotAddDataError)] + [InlineData(OcelotErrorCode.CannotFindDataError)] + [InlineData(OcelotErrorCode.DownstreamHostNullOrEmptyError)] + [InlineData(OcelotErrorCode.DownstreamPathNullOrEmptyError)] + [InlineData(OcelotErrorCode.DownstreampathTemplateAlreadyUsedError)] + [InlineData(OcelotErrorCode.DownstreamPathTemplateContainsSchemeError)] + [InlineData(OcelotErrorCode.DownstreamSchemeNullOrEmptyError)] + [InlineData(OcelotErrorCode.InstructionNotForClaimsError)] + [InlineData(OcelotErrorCode.NoInstructionsError)] + [InlineData(OcelotErrorCode.ParsingConfigurationHeaderError)] + [InlineData(OcelotErrorCode.RateLimitOptionsError)] + [InlineData(OcelotErrorCode.ServicesAreEmptyError)] + [InlineData(OcelotErrorCode.ServicesAreNullError)] + [InlineData(OcelotErrorCode.UnableToCompleteRequestError)] + [InlineData(OcelotErrorCode.UnableToCreateAuthenticationHandlerError)] + [InlineData(OcelotErrorCode.UnableToFindDownstreamRouteError)] + [InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)] + [InlineData(OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)] + [InlineData(OcelotErrorCode.UnableToFindQoSProviderError)] + [InlineData(OcelotErrorCode.UnableToSetConfigInConsulError)] + [InlineData(OcelotErrorCode.UnknownError)] + [InlineData(OcelotErrorCode.UnmappableRequestError)] + [InlineData(OcelotErrorCode.UnsupportedAuthenticationProviderError)] + public void should_return_not_found(OcelotErrorCode errorCode) + { + ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.NotFound); } [Fact] - public void should_create_unauthenticated_response_code() + public void AuthenticationErrorsHaveHighestPriority() { - this.Given(x => x.GivenThereAreErrors(new List - { - new UnauthenticatedError("no matter") - })) - .When(x => x.WhenIGetErrorStatusCode()) - .Then(x => x.ThenTheResponseIsStatusCodeIs(401)) - .BDDfy(); - } - - [Fact] - public void should_create_not_found_response_response_code() - { - this.Given(x => x.GivenThereAreErrors(new List - { - new AnyError() - })) - .When(x => x.WhenIGetErrorStatusCode()) - .Then(x => x.ThenTheResponseIsStatusCodeIs(404)) - .BDDfy(); - } - - class AnyError : Error - { - public AnyError() : base("blahh", OcelotErrorCode.UnknownError) + var errors = new List { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.CannotFindClaimError, + OcelotErrorCode.UnauthenticatedError, + OcelotErrorCode.RequestTimedOutError, + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Unauthorized); + } + + [Fact] + public void AuthorisationErrorsHaveSecondHighestPriority() + { + var errors = new List + { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.CannotFindClaimError, + OcelotErrorCode.RequestTimedOutError + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.Forbidden); + } + + [Fact] + public void ServiceUnavailableErrorsHaveThirdHighestPriority() + { + var errors = new List + { + OcelotErrorCode.CannotAddDataError, + OcelotErrorCode.RequestTimedOutError + }; + + ShouldMapErrorsToStatusCode(errors, HttpStatusCode.ServiceUnavailable); + } + + [Fact] + public void check_we_have_considered_all_errors_in_these_tests() + { + // If this test fails then it's because the number of error codes has changed. + // You should make the appropriate changes to the test cases here to ensure + // they cover all the error codes, and then modify this assertion. + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(30, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + } + + private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) + { + ShouldMapErrorsToStatusCode(new List { errorCode }, expectedHttpStatusCode); + } + + private void ShouldMapErrorsToStatusCode(List errorCodes, HttpStatusCode expectedHttpStatusCode) + { + var errors = new List(); + + foreach(var errorCode in errorCodes) + { + errors.Add(new AnyError(errorCode)); } + + this.Given(x => x.GivenThereAreErrors(errors)) + .When(x => x.WhenIGetErrorStatusCode()) + .Then(x => x.ThenTheResponseIsStatusCodeIs(expectedHttpStatusCode)) + .BDDfy(); } private void GivenThereAreErrors(List errors) @@ -77,6 +156,11 @@ namespace Ocelot.UnitTests.Responder private void ThenTheResponseIsStatusCodeIs(int expectedCode) { _result.ShouldBe(expectedCode); - } + } + + private void ThenTheResponseIsStatusCodeIs(HttpStatusCode expectedCode) + { + _result.ShouldBe((int)expectedCode); + } } } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 09a5c22c..3cfa00e5 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,60 +1,28 @@ -using System; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responder; -using Ocelot.Responder.Middleware; -using Ocelot.Responses; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Responder +namespace Ocelot.UnitTests.Responder { - public class ResponderMiddlewareTests : IDisposable + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Logging; + using Ocelot.Responder; + using Ocelot.Responder.Middleware; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class ResponderMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _responder; - private readonly Mock _scopedRepository; private readonly Mock _codeMapper; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _response; public ResponderMiddlewareTests() { - _url = "http://localhost:51879"; _responder = new Mock(); - _scopedRepository = new Mock(); _codeMapper = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(); - x.AddLogging(); - x.AddSingleton(_codeMapper.Object); - x.AddSingleton(_responder.Object); - x.AddSingleton(_scopedRepository.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseResponderMiddleware(); - }); - _server = new TestServer(builder); - _client = _server.CreateClient(); + GivenTheTestServerIsConfigured(); } [Fact] @@ -67,9 +35,31 @@ namespace Ocelot.UnitTests.Responder .BDDfy(); } + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + services.AddSingleton(); + services.AddLogging(); + services.AddSingleton(_codeMapper.Object); + services.AddSingleton(_responder.Object); + services.AddSingleton(ScopedRepository.Object); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseResponderMiddleware(); + } + + private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) + { + _response = new OkResponse(response); + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_response); + } + private void GivenThereAreNoPipelineErrors() { - _scopedRepository + ScopedRepository .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse(false)); } @@ -78,24 +68,5 @@ namespace Ocelot.UnitTests.Responder { //todo a better assert? } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - - private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) - { - _response = new OkResponse(response); - _scopedRepository - .Setup(x => x.Get(It.IsAny())) - .Returns(_response); - } - - public void Dispose() - { - _client.Dispose(); - _server.Dispose(); - } } } diff --git a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs new file mode 100644 index 00000000..29a012b9 --- /dev/null +++ b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs @@ -0,0 +1,62 @@ +namespace Ocelot.UnitTests +{ + using System; + using System.IO; + using System.Net.Http; + using Microsoft.AspNetCore.TestHost; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Moq; + using Ocelot.Infrastructure.RequestData; + + public abstract class ServerHostedMiddlewareTest : IDisposable + { + protected TestServer Server { get; private set; } + protected HttpClient Client { get; private set; } + protected string Url { get; private set; } + protected HttpResponseMessage ResponseMessage { get; private set; } + protected Mock ScopedRepository { get; private set; } + + public ServerHostedMiddlewareTest() + { + Url = "http://localhost:51879"; + ScopedRepository = new Mock(); + } + + protected virtual void GivenTheTestServerIsConfigured() + { + var builder = new WebHostBuilder() + .ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x)) + .UseUrls(Url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => GivenTheTestServerPipelineIsConfigured(app)); + + Server = new TestServer(builder); + Client = Server.CreateClient(); + } + + protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + // override this in your test fixture to set up service dependencies + } + + protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + // override this in your test fixture to set up the test server pipeline + } + + protected void WhenICallTheMiddleware() + { + ResponseMessage = Client.GetAsync(Url).Result; + } + + public void Dispose() + { + Client.Dispose(); + Server.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs index 87425329..3269b420 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -28,6 +28,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] public void should_lookup_service() { this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80)) diff --git a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs new file mode 100644 index 00000000..5392a58d --- /dev/null +++ b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs @@ -0,0 +1,89 @@ +namespace Ocelot.UnitTests.TestData +{ + using System.Collections.Generic; + + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.File; + + public class AuthenticationConfigTestData + { + public static IEnumerable GetAuthenticationData() + { + yield return new object[] + { + "IdentityServer", + new IdentityServerConfigBuilder() + .WithRequireHttps(true) + .WithApiName("test") + .WithApiSecret("test") + .WithProviderRootUrl("test") + .Build(), + new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + IdentityServerConfig = new FileIdentityServerConfig + { + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ApiName = "api", + ApiSecret = "secret" + } + }, + AddHeadersToRequest = + { + { "CustomerId", "Claims[CustomerId] > value" }, + } + } + } + } + }; + + yield return new object[] + { + "Jwt", + new JwtConfigBuilder() + .WithAudience("a") + .WithAuthority("au") + .Build(), + new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true, + AuthenticationOptions = new FileAuthenticationOptions + { + AllowedScopes = new List(), + Provider = "IdentityServer", + JwtConfig = new FileJwtConfig + { + Audience = "a", + Authority = "au" + } + }, + AddHeadersToRequest = + { + { "CustomerId", "Claims[CustomerId] > value" }, + } + } + } + } + }; + } + } +}