From 0b830d989160169d90c83482215b59d4e070dad7 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 07:42:31 +0000 Subject: [PATCH 01/25] mapped admin path works for manual test --- src/Ocelot/Controllers/ReRoutesController.cs | 13 ++++++ .../Middleware/OcelotMiddlewareExtensions.cs | 9 +++++ test/Ocelot.ManualTest/configuration.json | 40 +++++++++---------- test/Ocelot.ManualTest/project.json | 4 +- 4 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 src/Ocelot/Controllers/ReRoutesController.cs diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs new file mode 100644 index 00000000..0b02c796 --- /dev/null +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Ocelot.Controllers +{ + [RouteAttribute("reroutes")] + public class ReRoutesController + { + public IActionResult Get() + { + return new OkObjectResult("hi from re routes controller"); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 352aa501..afc080a0 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -31,7 +31,16 @@ namespace Ocelot.Middleware public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { CreateConfiguration(builder); + + builder.Map("/admin", x => + { + x.UseMvc(); + }); + builder.UseOcelot(new OcelotMiddlewareConfiguration()); + + + return builder; } diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 9b5fcb7e..fc0b6567 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -5,7 +5,7 @@ "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -50,7 +50,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -63,7 +63,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -76,7 +76,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -89,7 +89,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/comments", + "UpstreamPathTemplate": "/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -102,7 +102,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -115,7 +115,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -128,7 +128,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -141,7 +141,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -154,7 +154,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -168,7 +168,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, @@ -177,7 +177,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -190,7 +190,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -204,7 +204,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -218,7 +218,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -232,7 +232,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -246,7 +246,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -260,7 +260,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -274,7 +274,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -288,7 +288,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", + "UpstreamPathTemplate": "/posts/", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index cf67f9bd..04dba415 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -12,7 +12,9 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Ocelot": "0.0.0-dev", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0" + "Microsoft.NETCore.App": "1.1.0", + "Consul": "0.7.2.1", + "Polly": "5.0.3" }, "tools": { From 95fc687e9338f27d8c18c44d153d9d4649fb479b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 12:13:53 +0000 Subject: [PATCH 02/25] started messing around with admin area --- .../Creator/FileOcelotConfigurationCreator.cs | 2 +- .../File/FileGlobalConfiguration.cs | 1 + src/Ocelot/Configuration/IOcelotConfiguration.cs | 1 + src/Ocelot/Configuration/OcelotConfiguration.cs | 4 +++- .../Middleware/OcelotMiddlewareExtensions.cs | 16 ++++++++++------ test/Ocelot.ManualTest/configuration.json | 3 ++- .../FileConfigurationProviderTests.cs | 8 ++++---- .../InMemoryConfigurationRepositoryTests.cs | 8 ++++++++ .../DownstreamRouteFinderTests.cs | 13 +++++++------ 9 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 380e193f..8025f59d 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -85,7 +85,7 @@ namespace Ocelot.Configuration.Creator reRoutes.Add(ocelotReRoute); } - return new OcelotConfiguration(reRoutes); + return new OcelotConfiguration(reRoutes, _options.Value.GlobalConfiguration.AdministrationPath); } private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index f414bc83..fd0f41a9 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -8,5 +8,6 @@ } public string RequestIdKey { get; set; } public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + public string AdministrationPath {get;set;} } } diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IOcelotConfiguration.cs index 8359a2e1..566e2f91 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IOcelotConfiguration.cs @@ -5,5 +5,6 @@ namespace Ocelot.Configuration public interface IOcelotConfiguration { List ReRoutes { get; } + string AdministrationPath {get;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/OcelotConfiguration.cs index 3b0858eb..5655b3c2 100644 --- a/src/Ocelot/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot/Configuration/OcelotConfiguration.cs @@ -4,11 +4,13 @@ namespace Ocelot.Configuration { public class OcelotConfiguration : IOcelotConfiguration { - public OcelotConfiguration(List reRoutes) + public OcelotConfiguration(List reRoutes, string administrationPath) { ReRoutes = reRoutes; + AdministrationPath = administrationPath; } public List ReRoutes { get; } + public string AdministrationPath {get;} } } \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index afc080a0..5ba19ca5 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; using Ocelot.Configuration.Provider; using Ocelot.LoadBalancer.Middleware; @@ -30,9 +31,9 @@ namespace Ocelot.Middleware /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { - CreateConfiguration(builder); + var configuration = CreateConfiguration(builder).Result; - builder.Map("/admin", x => + builder.Map(configuration.AdministrationPath, x => { x.UseMvc(); }); @@ -131,16 +132,19 @@ namespace Ocelot.Middleware return builder; } - private static void CreateConfiguration(IApplicationBuilder builder) + private static async Task CreateConfiguration(IApplicationBuilder builder) { var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - var config = configProvider.Get(); + var config = await configProvider.Get(); - if(config == null) + //todo move this to config validators + if(config == null || config.Data == null || config.IsError) { - throw new Exception("Unable to start Ocelot: configuration was null"); + throw new Exception("Unable to start Ocelot: configuration was invalid"); } + + return config.Data; } private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index fc0b6567..f6f3c787 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -300,6 +300,7 @@ ], "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" + "RequestIdKey": "OcRequestId", + "AdministrationPath": "/admin" } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 98e01293..2573e32f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -29,9 +29,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_get_config() { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List())))) + this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List())))) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .BDDfy(); } @@ -39,9 +39,9 @@ namespace Ocelot.UnitTests.Configuration public void should_create_config_if_it_doesnt_exist() { this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List())))) + .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List())))) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 9f437204..8e0e66d3 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -82,6 +82,14 @@ namespace Ocelot.UnitTests.Configuration _downstreamTemplatePath = downstreamTemplatePath; } + public string AdministrationPath + { + get + { + throw new NotImplementedException(); + } + } + public List ReRoutes => new List { new ReRouteBuilder() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 2fb8f28f..0d272c63 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -48,7 +48,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() - })) + }, string.Empty + )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -80,7 +81,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) @@ -118,7 +119,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") .Build() - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) @@ -145,7 +146,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") .Build(), - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) @@ -193,12 +194,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Returns(_match); } - private void GivenTheConfigurationIs(List reRoutesConfig) + private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath) { _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); + .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) From 159969483b64c5491b1d86b1aa4dac7b4b2a3ba1 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 18:51:47 +0000 Subject: [PATCH 03/25] hacking away --- .../Middleware/OcelotMiddlewareExtensions.cs | 30 +++++++++++-------- .../ReturnsErrorTests.cs | 9 ++++-- .../TestConfiguration.cs | 2 +- test/Ocelot.ManualTest/Startup.cs | 5 ++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 5ba19ca5..26b1638f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -29,31 +29,37 @@ namespace Ocelot.Middleware /// /// /// - public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) + public static async Task UseOcelot(this IApplicationBuilder builder) { - var configuration = CreateConfiguration(builder).Result; - - builder.Map(configuration.AdministrationPath, x => - { - x.UseMvc(); - }); - - builder.UseOcelot(new OcelotMiddlewareConfiguration()); - + await CreateAdministrationArea(builder); + await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } + private static async Task CreateAdministrationArea(IApplicationBuilder builder) + { + var configuration = await CreateConfiguration(builder); + + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, x => + { + x.UseMvc(); + }); + } + } + /// /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration /// /// /// /// - public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { - CreateConfiguration(builder); + await CreateAdministrationArea(builder); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index f9222b86..0d81b6df 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_return_response_200_and_foward_claim_as_header() + public void should_return_server_error() { var configuration = new FileConfiguration { @@ -29,9 +29,12 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamPathTemplate = "http://localhost:53876/", + DownstreamPathTemplate = "/", UpstreamPathTemplate = "/", - UpstreamHttpMethod = "Get" + UpstreamHttpMethod = "Get", + DownstreamPort = 53876, + DownstreamHost = "localhost", + DownstreamScheme = "http", } } }; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 70448fcf..b187f6b4 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -43,11 +44,11 @@ namespace Ocelot.ManualTest services.AddOcelot(); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - app.UseOcelot(); + await app.UseOcelot(); } } } From ef3deec8da78fd282f6b5f2bff8e6d6853496c31 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 19:14:49 +0000 Subject: [PATCH 04/25] more hacking --- test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs | 4 ++-- test/Ocelot.AcceptanceTests/TestConfiguration.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 0d81b6df..868cb39f 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_return_server_error() + public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error() { var configuration = new FileConfiguration { @@ -33,8 +33,8 @@ namespace Ocelot.AcceptanceTests UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", DownstreamPort = 53876, - DownstreamHost = "localhost", DownstreamScheme = "http", + DownstreamHost = "localhost" } } }; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c..ce802efb 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } From 1e88062ce2dc523f635791234110e599736cc8e9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:43:50 +0000 Subject: [PATCH 05/25] moved create admin area call --- .../Middleware/OcelotMiddlewareExtensions.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 26b1638f..269d646d 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -31,26 +31,11 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await CreateAdministrationArea(builder); - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder) - { - var configuration = await CreateConfiguration(builder); - - if(!string.IsNullOrEmpty(configuration.AdministrationPath)) - { - builder.Map(configuration.AdministrationPath, x => - { - x.UseMvc(); - }); - } - } - /// /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration /// @@ -153,6 +138,19 @@ namespace Ocelot.Middleware return config.Data; } + private static async Task CreateAdministrationArea(IApplicationBuilder builder) + { + var configuration = await CreateConfiguration(builder); + + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, x => + { + x.UseMvc(); + }); + } + } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) From 8ae8e2d834ac9d7733f6005ec67dc0e0315cb7f8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:48:49 +0000 Subject: [PATCH 06/25] added acceptance test for calling reroutes controller --- .../AdministrationTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/Ocelot.AcceptanceTests/AdministrationTests.cs diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs new file mode 100644 index 00000000..5a76e458 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class AdministrationTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public AdministrationTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} From 219b45e7d061b49e23afc358be9a4bb9171895a9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:51:19 +0000 Subject: [PATCH 07/25] updated acceptance test assertion --- src/Ocelot/Controllers/ReRoutesController.cs | 1 + test/Ocelot.AcceptanceTests/AdministrationTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs index 0b02c796..4304d4b1 100644 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Ocelot.Controllers diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 5a76e458..44c4447e 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -36,6 +36,7 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } From bc5010837eafdc54f3f28964ea6417327f8b151f Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 13:24:06 +0000 Subject: [PATCH 08/25] removed using --- src/Ocelot/Controllers/ReRoutesController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs index 4304d4b1..0b02c796 100644 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Ocelot.Controllers From 4dac8cb4fb37d3db49dd2ce8cc7a407e25e8640d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 07:27:49 +0000 Subject: [PATCH 09/25] still hacking around --- .../FileConfigurationController.cs | 21 ++++ src/Ocelot/Controllers/ReRoutesController.cs | 13 --- src/Ocelot/Services/GetFileConfiguration.cs | 19 ++++ src/Ocelot/Services/IGetFileConfiguration.cs | 10 ++ .../AdministrationTests.cs | 31 +++++- test/Ocelot.AcceptanceTests/Steps.cs | 20 +++- .../TestConfiguration.cs | 2 +- .../FileConfigurationControllerTests.cs | 53 ++++++++++ .../Services/GetFileConfigurationTests.cs | 96 +++++++++++++++++++ test/Ocelot.UnitTests/configuration.json | 1 + 10 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 src/Ocelot/Controllers/FileConfigurationController.cs delete mode 100644 src/Ocelot/Controllers/ReRoutesController.cs create mode 100644 src/Ocelot/Services/GetFileConfiguration.cs create mode 100644 src/Ocelot/Services/IGetFileConfiguration.cs create mode 100644 test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs create mode 100644 test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs create mode 100755 test/Ocelot.UnitTests/configuration.json diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs new file mode 100644 index 00000000..cf0a792a --- /dev/null +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; +using Ocelot.Services; + +namespace Ocelot.Controllers +{ + [RouteAttribute("configuration")] + public class FileConfigurationController + { + private IGetFileConfiguration _getFileConfig; + + public FileConfigurationController(IGetFileConfiguration getFileConfig) + { + _getFileConfig = getFileConfig; + } + + public IActionResult Get() + { + return new OkObjectResult(_getFileConfig.Invoke().Data); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs deleted file mode 100644 index 0b02c796..00000000 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Ocelot.Controllers -{ - [RouteAttribute("reroutes")] - public class ReRoutesController - { - public IActionResult Get() - { - return new OkObjectResult("hi from re routes controller"); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/GetFileConfiguration.cs new file mode 100644 index 00000000..b97cac92 --- /dev/null +++ b/src/Ocelot/Services/GetFileConfiguration.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Services +{ + public class GetFileConfiguration : IGetFileConfiguration + { + public Response Invoke() + { + var configFilePath = "configuration.json"; + var json = File.ReadAllText(configFilePath); + var fileConfiguration = JsonConvert.DeserializeObject(json); + return new OkResponse(fileConfiguration); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Services/IGetFileConfiguration.cs b/src/Ocelot/Services/IGetFileConfiguration.cs new file mode 100644 index 00000000..08b950a8 --- /dev/null +++ b/src/Ocelot/Services/IGetFileConfiguration.cs @@ -0,0 +1,10 @@ +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Services +{ + public interface IGetFileConfiguration + { + Response Invoke(); + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 44c4447e..29ad10a8 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -34,12 +34,41 @@ namespace Ocelot.AcceptanceTests this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/" + } + } + }; + + this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9b5faa04..73585017 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -75,6 +75,25 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + internal void ThenTheResponseShouldBe(FileConfiguration expected) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for(var i = 0; i < response.ReRoutes.Count; i++) + { + response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + } + } + /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -155,7 +174,6 @@ namespace Ocelot.AcceptanceTests } } - public void WhenIGetUrlOnTheApiGateway(string url) { _response = _ocelotClient.GetAsync(url).Result; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs new file mode 100644 index 00000000..cbd8aeac --- /dev/null +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Controllers; +using Ocelot.Responses; +using Ocelot.Services; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Controllers +{ + public class FileConfigurationControllerTests + { + private FileConfigurationController _controller; + private Mock _getFileConfig; + private IActionResult _result; + + public FileConfigurationControllerTests() + { + _getFileConfig = new Mock(); + _controller = new FileConfigurationController(_getFileConfig.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var expected = new OkResponse(new FileConfiguration()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.ThenTheFileConfigurationIsReturned(expected.Data)) + .BDDfy(); + } + + private void GivenTheGetConfigurationReturns(Response fileConfiguration) + { + _getFileConfig + .Setup(x => x.Invoke()) + .Returns(fileConfiguration); + } + + private void WhenIGetTheFileConfiguration() + { + _result = _controller.Get(); + } + + private void ThenTheFileConfigurationIsReturned(FileConfiguration expected) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs new file mode 100644 index 00000000..89ce22b1 --- /dev/null +++ b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Ocelot.Services; +using Newtonsoft.Json; +using System.IO; + +namespace Ocelot.UnitTests.Services +{ + public class GetFileConfigurationTests + { + private IGetFileConfiguration _getReRoutes; + private FileConfiguration _result; + + public GetFileConfigurationTests() + { + _getReRoutes = new GetFileConfiguration(); + } + + [Fact] + public void should_return_file_configuration() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/test/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "testy", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Port = 198, + Host = "blah" + } + }; + + var config = new FileConfiguration(); + config.GlobalConfiguration = globalConfiguration; + config.ReRoutes.AddRange(reRoutes); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + var configurationPath = "configuration.json"; + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void WhenIGetTheReRoutes() + { + _result = _getReRoutes.Invoke().Data; + } + + private void ThenTheFollowingIsReturned(FileConfiguration expected) + { + _result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + //todo -- add more! + } + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/configuration.json b/test/Ocelot.UnitTests/configuration.json new file mode 100755 index 00000000..61548dab --- /dev/null +++ b/test/Ocelot.UnitTests/configuration.json @@ -0,0 +1 @@ +{"ReRoutes":[{"DownstreamPathTemplate":"/test/test/{test}","UpstreamPathTemplate":null,"UpstreamHttpMethod":null,"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}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":"consul","Host":"blah","Port":198},"AdministrationPath":"testy"}} \ No newline at end of file From 816221c7a6fb2106500db4c02688340e855cebe9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 19 Feb 2017 12:33:09 +0000 Subject: [PATCH 10/25] morehaking about --- .../InMemoryConfigurationRepositoryTests.cs | 17 ++++++----------- .../FileConfigurationControllerTests.cs | 8 ++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8e0e66d3..8d28b112 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void can_add_config() { - this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial"))) + this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath"))) .When(x => x.WhenIAddOrReplaceTheConfig()) .Then(x => x.ThenNoErrorsAreReturned()) .BDDfy(); @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenThereIsASavedConfiguration() { - GivenTheConfigurationIs(new FakeConfig("initial")); + GivenTheConfigurationIs(new FakeConfig("initial", "adminath")); WhenIAddOrReplaceTheConfig(); } @@ -77,17 +77,10 @@ namespace Ocelot.UnitTests.Configuration { private readonly string _downstreamTemplatePath; - public FakeConfig(string downstreamTemplatePath) + public FakeConfig(string downstreamTemplatePath, string administrationPath) { _downstreamTemplatePath = downstreamTemplatePath; - } - - public string AdministrationPath - { - get - { - throw new NotImplementedException(); - } + AdministrationPath = administrationPath; } public List ReRoutes => new List @@ -97,6 +90,8 @@ namespace Ocelot.UnitTests.Configuration .WithUpstreamHttpMethod("Get") .Build() }; + + public string AdministrationPath {get;} } } } diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index cbd8aeac..cd2ab4ce 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Moq; -using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.Controllers; using Ocelot.Responses; @@ -30,7 +28,7 @@ namespace Ocelot.UnitTests.Controllers this.Given(x => x.GivenTheGetConfigurationReturns(expected)) .When(x => x.WhenIGetTheFileConfiguration()) - .Then(x => x.ThenTheFileConfigurationIsReturned(expected.Data)) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) .BDDfy(); } @@ -46,8 +44,10 @@ namespace Ocelot.UnitTests.Controllers _result = _controller.Get(); } - private void ThenTheFileConfigurationIsReturned(FileConfiguration expected) + private void TheTheGetFileConfigurationIsCalledCorrectly() { + _getFileConfig + .Verify(x => x.Invoke(), Times.Once); } } } \ No newline at end of file From fa4766325968cc6cec93173a01073c4d6d1f7670 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 12:58:22 +0000 Subject: [PATCH 11/25] changed file config stuff to just use app base directory --- .../ServiceCollectionExtensions.cs | 2 ++ src/Ocelot/Services/GetFileConfiguration.cs | 2 +- .../AdministrationTests.cs | 35 +++++++------------ test/Ocelot.AcceptanceTests/Steps.cs | 4 ++- .../TestConfiguration.cs | 35 +++---------------- .../Services/GetFileConfigurationTests.cs | 7 ++-- 6 files changed, 26 insertions(+), 59 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index e9be9841..74e6f97e 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using Ocelot.Services; namespace Ocelot.DependencyInjection { @@ -62,6 +63,7 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/GetFileConfiguration.cs index b97cac92..536a0b46 100644 --- a/src/Ocelot/Services/GetFileConfiguration.cs +++ b/src/Ocelot/Services/GetFileConfiguration.cs @@ -10,7 +10,7 @@ namespace Ocelot.Services { public Response Invoke() { - var configFilePath = "configuration.json"; + var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; var json = File.ReadAllText(configFilePath); var fileConfiguration = JsonConvert.DeserializeObject(json); return new OkResponse(fileConfiguration); diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 29ad10a8..525d9be5 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -36,7 +36,6 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } @@ -56,7 +55,18 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 80, DownstreamScheme = "https", - DownstreamPathTemplate = "/" + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" } } }; @@ -69,27 +79,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 73585017..4a946947 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -31,7 +31,7 @@ namespace Ocelot.AcceptanceTests private BearerToken _token; public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; - private Random _random; + private readonly Random _random; public Steps() { @@ -91,6 +91,8 @@ namespace Ocelot.AcceptanceTests response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); } } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c..b4e6b778 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,36 +1,9 @@ -namespace Ocelot.AcceptanceTests -{ - using System.Runtime.InteropServices; +using System; +namespace Ocelot.AcceptanceTests +{ public static class TestConfiguration { - public static double Version => 1.1; - public static string ConfigurationPath => GetConfigurationPath(); - - public static string GetConfigurationPath() - { - var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) - { - return FormatConfigurationPath("osx.10.11", osArchitecture); - } - - if(RuntimeInformation.OSDescription.ToLower().Contains("microsoft windows 10")) - { - return FormatConfigurationPath("win10", osArchitecture); - } - - return FormatConfigurationPath("win7", osArchitecture); - } - - private static string FormatConfigurationPath(string oSDescription, string osArchitecture) - { - var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; - - return configPath; - } + public static string ConfigurationPath => $"{AppContext.BaseDirectory}/configuration.json"; } } diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs index 89ce22b1..fa86ced6 100644 --- a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs +++ b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Moq; using Ocelot.Configuration; @@ -14,7 +15,7 @@ namespace Ocelot.UnitTests.Services { public class GetFileConfigurationTests { - private IGetFileConfiguration _getReRoutes; + private readonly IGetFileConfiguration _getReRoutes; private FileConfiguration _result; public GetFileConfigurationTests() @@ -59,7 +60,8 @@ namespace Ocelot.UnitTests.Services private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - var configurationPath = "configuration.json"; + var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); if (File.Exists(configurationPath)) @@ -89,7 +91,6 @@ namespace Ocelot.UnitTests.Services _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - //todo -- add more! } } } From d236ed3018a278e8ad22d88abe03a302eb503911 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 13:59:17 +0000 Subject: [PATCH 12/25] trying to get identity server authing --- .../FileConfigurationController.cs | 10 ++- .../ServiceCollectionExtensions.cs | 51 ++++++++++++++++ .../Middleware/OcelotMiddlewareExtensions.cs | 22 +++++-- src/Ocelot/project.json | 61 ++++++++++--------- .../AdministrationTests.cs | 6 -- 5 files changed, 107 insertions(+), 43 deletions(-) diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index cf0a792a..3589bdb5 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -1,20 +1,24 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Services; namespace Ocelot.Controllers { - [RouteAttribute("configuration")] - public class FileConfigurationController + [Authorize(Roles = "Admin")] + [Route("configuration")] + public class FileConfigurationController : Controller { - private IGetFileConfiguration _getFileConfig; + private readonly IGetFileConfiguration _getFileConfig; public FileConfigurationController(IGetFileConfiguration getFileConfig) { _getFileConfig = getFileConfig; } + [HttpGet] public IActionResult Get() { + var user = this.HttpContext.User; return new OkObjectResult(_getFileConfig.Invoke().Data); } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 74e6f97e..c06d965b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Net.Http; +using System.Security.Claims; using CacheManager.Core; +using IdentityServer4.Models; +using IdentityServer4.Test; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -61,6 +65,53 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = "admin", + Description = "Ocelot Administration", + Enabled = true, + DisplayName = "admin", + Scopes = new List() + { + new Scope("admin"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List + { + new Secret + { + Value = "secret".Sha256() + } + } + } + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "admin", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List {"admin", "openid", "offline_access"}, + AccessTokenType = AccessTokenType.Jwt, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "admin", + Password = "admin", + SubjectId = "admin", + } + }); services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 269d646d..ab51e8da 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; using Ocelot.Authentication.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; @@ -45,7 +47,7 @@ namespace Ocelot.Middleware public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { await CreateAdministrationArea(builder); - + // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -144,9 +146,21 @@ namespace Ocelot.Middleware if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { - builder.Map(configuration.AdministrationPath, x => + builder.Map(configuration.AdministrationPath, app => { - x.UseMvc(); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = "http://localhost:5000/admin", + ApiName = "admin", + RequireHttpsMetadata = false, + AllowedScopes = new List(), + SupportedTokens = SupportedTokens.Both, + ApiSecret = "secret" + }); + + app.UseIdentityServer(); + + app.UseMvc(); }); } } diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 4e098803..7b169503 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,35 +1,36 @@ { "version": "0.0.0-dev", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Microsoft.AspNetCore.Http": "1.1.0", - "System.Text.RegularExpressions": "4.3.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", - "Microsoft.AspNetCore.Authentication.Google": "1.1.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", - "Microsoft.AspNetCore.Authentication": "1.1.0", - "IdentityServer4.AccessTokenValidation": "1.0.2", - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0", - "CacheManager.Core": "0.9.2", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2", - "Consul": "0.7.2.1", - "Polly": "5.0.3" - }, + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Consul": "0.7.2.1", + "Polly": "5.0.3", + "IdentityServer4": "1.0.1" + }, "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 525d9be5..93c4a8b9 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; @@ -13,7 +9,6 @@ namespace Ocelot.AcceptanceTests { public class AdministrationTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; public AdministrationTests() @@ -81,7 +76,6 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _builder?.Dispose(); _steps.Dispose(); } } From d548a86327d2c4fd8ffbb09a660f5c52f7463788 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 15:29:32 +0000 Subject: [PATCH 13/25] Added integration test project as acceptance style doesnt work when running the new admin area because identityserver needs to use proper networking --- Ocelot.sln | 9 +- build.cake | 23 +- .../FileConfigurationController.cs | 3 +- .../ServiceCollectionExtensions.cs | 4 +- .../Middleware/OcelotMiddlewareExtensions.cs | 4 +- .../AdministrationTests.cs | 82 ------- test/Ocelot.AcceptanceTests/Steps.cs | 22 +- .../AdministrationTests.cs | 209 ++++++++++++++++++ test/Ocelot.IntegrationTests/BearerToken.cs | 16 ++ .../Ocelot.IntegrationTests.xproj | 22 ++ .../Properties/AssemblyInfo.cs | 19 ++ test/Ocelot.IntegrationTests/appsettings.json | 10 + .../configuration.json | 1 + test/Ocelot.IntegrationTests/project.json | 50 +++++ 14 files changed, 384 insertions(+), 90 deletions(-) delete mode 100644 test/Ocelot.AcceptanceTests/AdministrationTests.cs create mode 100644 test/Ocelot.IntegrationTests/AdministrationTests.cs create mode 100644 test/Ocelot.IntegrationTests/BearerToken.cs create mode 100644 test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj create mode 100644 test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs create mode 100644 test/Ocelot.IntegrationTests/appsettings.json create mode 100644 test/Ocelot.IntegrationTests/configuration.json create mode 100644 test/Ocelot.IntegrationTests/project.json diff --git a/Ocelot.sln b/Ocelot.sln index 6c0006b0..d88020d3 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -8,7 +8,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore - appveyor.yml = appveyor.yml build-and-release-unstable.ps1 = build-and-release-unstable.ps1 build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake @@ -19,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md - Ocelot.nuspec = Ocelot.nuspec README.md = README.md release.ps1 = release.ps1 ReleaseNotes.md = ReleaseNotes.md @@ -41,6 +39,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.ManualTest", "test\O EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.xproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.xproj", "{D4575572-99CA-4530-8737-C296EDA326F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +67,10 @@ Global {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -77,5 +81,6 @@ Global {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} + {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} EndGlobalSection EndGlobal diff --git a/build.cake b/build.cake index 579f4e82..d3aee78f 100644 --- a/build.cake +++ b/build.cake @@ -18,7 +18,9 @@ var unitTestAssemblies = @"./test/Ocelot.UnitTests"; // acceptance testing var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); -var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; + +// integration testing +var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); // benchmark testing var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); @@ -129,6 +131,24 @@ Task("RunAcceptanceTests") }); + Task("RunIntegrationTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = "Debug", //int test config is hard-coded for debug + }; + + EnsureDirectoryExists(artifactsForIntegrationTestsDir); + + DoInDirectory("test/Ocelot.IntegrationTests", () => + { + DotNetCoreTest(".", buildSettings); + }); + + }); + Task("RunBenchmarkTests") .IsDependentOn("Restore") .Does(() => @@ -149,6 +169,7 @@ Task("RunBenchmarkTests") Task("RunTests") .IsDependentOn("RunUnitTests") .IsDependentOn("RunAcceptanceTests") + .IsDependentOn("RunIntegrationTests") .Does(() => { }); diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index 3589bdb5..3dc22303 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -4,7 +4,7 @@ using Ocelot.Services; namespace Ocelot.Controllers { - [Authorize(Roles = "Admin")] + [Authorize] [Route("configuration")] public class FileConfigurationController : Controller { @@ -18,7 +18,6 @@ namespace Ocelot.Controllers [HttpGet] public IActionResult Get() { - var user = this.HttpContext.User; return new OkObjectResult(_getFileConfig.Invoke().Data); } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c06d965b..c213aa6b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -112,7 +112,9 @@ namespace Ocelot.DependencyInjection SubjectId = "admin", } }); - services.AddMvcCore().AddJsonFormatters(); + services.AddMvcCore() + .AddAuthorization() + .AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index ab51e8da..fa113771 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -148,9 +148,11 @@ namespace Ocelot.Middleware { builder.Map(configuration.AdministrationPath, app => { + var identityServerUrl = $"http://localhost:5000/{configuration.AdministrationPath.Remove(0,1)}"; + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { - Authority = "http://localhost:5000/admin", + Authority = identityServerUrl, ApiName = "admin", RequireHttpsMetadata = false, AllowedScopes = new List(), diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs deleted file mode 100644 index 93c4a8b9..00000000 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class AdministrationTests : IDisposable - { - private readonly Steps _steps; - - public AdministrationTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - AdministrationPath = "/administration" - } - }; - - this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - AdministrationPath = "/administration" - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = "get", - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = "get", - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - public void Dispose() - { - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 4a946947..1f4a8da1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -133,7 +133,7 @@ namespace Ocelot.AcceptanceTests }) .Configure(a => { - a.UseOcelot(ocelotMiddlewareConfig); + a.UseOcelot(ocelotMiddlewareConfig).Wait(); })); _ocelotClient = _ocelotServer.CreateClient(); @@ -167,6 +167,26 @@ namespace Ocelot.AcceptanceTests } } + public void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _ocelotClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + public void VerifyIdentiryServerStarted(string url) { using (var httpClient = new HttpClient()) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs new file mode 100644 index 00000000..6ea1aa32 --- /dev/null +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.ManualTest; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.IntegrationTests +{ + public class AdministrationTests : IDisposable + { + private readonly HttpClient _httpClient; + private HttpResponseMessage _response; + private IWebHost _builder; + private readonly string _ocelotBaseUrl; + private BearerToken _token; + + public AdministrationTests() + { + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_response_401_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + + private void ThenTheResponseShouldBe(FileConfiguration expected) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + private void GivenOcelotIsRunning() + { + _builder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _httpClient.GetAsync(url).Result; + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs new file mode 100644 index 00000000..efd35c33 --- /dev/null +++ b/test/Ocelot.IntegrationTests/BearerToken.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Ocelot.IntegrationTests +{ + class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } +} diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj new file mode 100644 index 00000000..ece2b3b5 --- /dev/null +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d4575572-99ca-4530-8737-c296eda326f8 + Ocelot.IntegrationTests + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5535cd70 --- /dev/null +++ b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.IntegrationTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")] diff --git a/test/Ocelot.IntegrationTests/appsettings.json b/test/Ocelot.IntegrationTests/appsettings.json new file mode 100644 index 00000000..d73b7dcb --- /dev/null +++ b/test/Ocelot.IntegrationTests/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json new file mode 100644 index 00000000..3f39532c --- /dev/null +++ b/test/Ocelot.IntegrationTests/configuration.json @@ -0,0 +1 @@ +{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/project.json b/test/Ocelot.IntegrationTests/project.json new file mode 100644 index 00000000..7454b3a0 --- /dev/null +++ b/test/Ocelot.IntegrationTests/project.json @@ -0,0 +1,50 @@ +{ + "version": "0.0.0-dev", + + "buildOptions": { + "copyToOutput": { + "include": [ + "configuration.json", + "appsettings.json" + ] + } + }, + + "testRunner": "xunit", + + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "Ocelot": "0.0.0-dev", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Ocelot.ManualTest": "0.0.0-dev", + "Microsoft.AspNetCore.TestHost": "1.1.0", + "IdentityServer4": "1.0.1", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "Shouldly": "2.8.2", + "TestStack.BDDfy": "4.3.2", + "Consul": "0.7.2.1" + }, + "runtimes": { + "win10-x64": {}, + "osx.10.11-x64": {}, + "win7-x64": {} + }, + "frameworks": { + "netcoreapp1.1": { + "imports": [ + ] + } + } +} From 2dfdf0bb865f35cec5c665dd9528185a0d02f8c5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 21 Feb 2017 07:34:47 +0000 Subject: [PATCH 14/25] after much hacking unit tests passing --- Ocelot.nuspec | 48 ---------- appveyor.yml | 8 -- .../Cache/Middleware/OutputCacheMiddleware.cs | 1 - .../Middleware/ClaimsBuilderMiddleware.cs | 1 - .../Configuration/Builder/ReRouteBuilder.cs | 3 +- .../Creator/FileOcelotConfigurationCreator.cs | 13 ++- .../Creator/IOcelotConfigurationCreator.cs | 2 + .../Configuration/File/FileQoSOptions.cs | 7 +- .../Provider/IOcelotConfigurationProvider.cs | 5 +- .../Provider/OcelotConfigurationProvider.cs | 25 +---- src/Ocelot/Configuration/ReRoute.cs | 3 +- .../Setter/FileConfigurationSetter.cs | 32 +++++++ .../Setter/IFileConfigurationSetter.cs | 11 +++ .../FileConfigurationController.cs | 33 ++++++- .../ServiceCollectionExtensions.cs | 7 +- .../Finder/DownstreamRouteFinder.cs | 5 +- .../Finder/IDownstreamRouteFinder.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 3 +- .../DownstreamUrlCreator/IUrlBuilder.cs | 4 +- .../DownstreamUrlCreatorMiddleware.cs | 3 - src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 2 - .../Middleware/ExceptionHandlerMiddleware.cs | 2 - .../HttpRequestHeadersBuilderMiddleware.cs | 1 - src/Ocelot/Headers/RemoveOutputHeaders.cs | 2 - .../LoadBalancers/ILoadBalancer.cs | 1 - .../LoadBalancers/LoadBalancerHouse.cs | 5 +- .../Middleware/OcelotMiddlewareExtensions.cs | 23 ++++- src/Ocelot/Properties/AssemblyInfo.cs | 1 - .../QueryStringBuilderMiddleware.cs | 1 - src/Ocelot/Responses/ErrorResponse.cs | 3 + ...ration.cs => FileConfigurationProvider.cs} | 4 +- ...ation.cs => IFileConfigurationProvider.cs} | 4 +- src/Ocelot/project.json | 1 + test/Ocelot.AcceptanceTests/project.json | 1 + test/Ocelot.Benchmarks/project.json | 1 + test/Ocelot.IntegrationTests/project.json | 1 + test/Ocelot.ManualTest/project.json | 1 + .../FileConfigurationProviderTests.cs | 39 +------- .../FileConfigurationSetterTests.cs | 86 +++++++++++++++++ .../FileConfigurationControllerTests.cs | 96 +++++++++++++++++-- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- ...sts.cs => FileConfigurationGetterTests.cs} | 6 +- test/Ocelot.UnitTests/project.json | 1 + 44 files changed, 313 insertions(+), 194 deletions(-) delete mode 100644 Ocelot.nuspec delete mode 100644 appveyor.yml create mode 100644 src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs create mode 100644 src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs rename src/Ocelot/Services/{GetFileConfiguration.cs => FileConfigurationProvider.cs} (79%) rename src/Ocelot/Services/{IGetFileConfiguration.cs => IFileConfigurationProvider.cs} (52%) create mode 100644 test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs rename test/Ocelot.UnitTests/Services/{GetFileConfigurationTests.cs => FileConfigurationGetterTests.cs} (95%) diff --git a/Ocelot.nuspec b/Ocelot.nuspec deleted file mode 100644 index d236d60d..00000000 --- a/Ocelot.nuspec +++ /dev/null @@ -1,48 +0,0 @@ - - - - Ocelot - 1.0.0 - Tom Pallister - Tom Pallister - https://github.com/TomPallister/Ocelot/blob/develop/LICENSE.md - https://github.com/TomPallister/Ocelot - false - Ocelot Api Gateway - Latest Ocelot - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6e8b16f6..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 1.0.{build} -configuration: -- Release -platform: Any CPU -build_script: -- build.ps1 -cache: -- '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 77024e70..948a7397 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index 1f6486f6..1b1745e2 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 382cf374..8282a11c 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using Ocelot.Values; diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 8025f59d..049c0fff 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -56,14 +56,21 @@ namespace Ocelot.Configuration.Creator public async Task> Create() { - var config = await SetUpConfiguration(); + var config = await SetUpConfiguration(_options.Value); return new OkResponse(config); } - private async Task SetUpConfiguration() + public async Task> Create(FileConfiguration fileConfiguration) + { + var config = await SetUpConfiguration(fileConfiguration); + + return new OkResponse(config); + } + + private async Task SetUpConfiguration(FileConfiguration fileConfiguration) { - var response = _configurationValidator.IsValid(_options.Value); + var response = _configurationValidator.IsValid(fileConfiguration); if (response.Data.IsError) { diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs index 7547d91f..99d1d39d 100644 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Responses; namespace Ocelot.Configuration.Creator @@ -6,5 +7,6 @@ namespace Ocelot.Configuration.Creator public interface IOcelotConfigurationCreator { Task> Create(); + Task> Create(FileConfiguration fileConfiguration); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileQoSOptions.cs b/src/Ocelot/Configuration/File/FileQoSOptions.cs index dfac3ac6..876a3fd5 100644 --- a/src/Ocelot/Configuration/File/FileQoSOptions.cs +++ b/src/Ocelot/Configuration/File/FileQoSOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File +namespace Ocelot.Configuration.File { public class FileQoSOptions { diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 3256e44a..30ded2e9 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.Configuration.Provider { public interface IOcelotConfigurationProvider { - Task> Get(); + Response Get(); } } diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 80fd5697..e416ed8e 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Repository; using Ocelot.Responses; namespace Ocelot.Configuration.Provider @@ -11,16 +9,13 @@ namespace Ocelot.Configuration.Provider public class OcelotConfigurationProvider : IOcelotConfigurationProvider { private readonly IOcelotConfigurationRepository _repo; - private readonly IOcelotConfigurationCreator _creator; - public OcelotConfigurationProvider(IOcelotConfigurationRepository repo, - IOcelotConfigurationCreator creator) + public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) { _repo = repo; - _creator = creator; } - public async Task> Get() + public Response Get() { var repoConfig = _repo.Get(); @@ -29,20 +24,6 @@ namespace Ocelot.Configuration.Provider return new ErrorResponse(repoConfig.Errors); } - if (repoConfig.Data == null) - { - var creatorConfig = await _creator.Create(); - - if (creatorConfig.IsError) - { - return new ErrorResponse(creatorConfig.Errors); - } - - _repo.AddOrReplace(creatorConfig.Data); - - return new OkResponse(creatorConfig.Data); - } - return new OkResponse(repoConfig.Data); } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index ab9405d0..42a0aa79 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using Ocelot.Values; diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs new file mode 100644 index 00000000..28957cc7 --- /dev/null +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public class FileConfigurationSetter : IFileConfigurationSetter + { + private readonly IOcelotConfigurationRepository _configRepo; + private readonly IOcelotConfigurationCreator _configCreator; + + public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, IOcelotConfigurationCreator configCreator) + { + _configRepo = configRepo; + _configCreator = configCreator; + } + + public async Task Set(FileConfiguration fileConfig) + { + var config = await _configCreator.Create(fileConfig); + + if(!config.IsError) + { + _configRepo.AddOrReplace(config.Data); + } + + return new ErrorResponse(config.Errors); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs new file mode 100644 index 00000000..8ab31d1b --- /dev/null +++ b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public interface IFileConfigurationSetter + { + Task Set(FileConfiguration config); + } +} \ No newline at end of file diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index 3dc22303..a378f530 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -1,5 +1,8 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; using Ocelot.Services; namespace Ocelot.Controllers @@ -8,17 +11,39 @@ namespace Ocelot.Controllers [Route("configuration")] public class FileConfigurationController : Controller { - private readonly IGetFileConfiguration _getFileConfig; + private readonly IFileConfigurationProvider _configGetter; + private readonly IFileConfigurationSetter _configSetter; - public FileConfigurationController(IGetFileConfiguration getFileConfig) + public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter) { - _getFileConfig = getFileConfig; + _configGetter = getFileConfig; + _configSetter = configSetter; } [HttpGet] public IActionResult Get() { - return new OkObjectResult(_getFileConfig.Invoke().Data); + var response = _configGetter.Get(); + + if(response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(response.Data); + } + + [HttpPost] + public async Task Post(FileConfiguration fileConfiguration) + { + var response = await _configSetter.Set(fileConfiguration); + + if(response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(fileConfiguration); } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c213aa6b..43ffefe3 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Security.Claims; using CacheManager.Core; using IdentityServer4.Models; using IdentityServer4.Test; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; @@ -46,7 +44,6 @@ namespace Ocelot.DependencyInjection { var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); var ocelotCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - services.AddSingleton>(cacheManagerOutputCache); services.AddSingleton>(ocelotCacheManager); @@ -55,11 +52,9 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) { services.Configure(configurationRoot); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - return services; } @@ -116,7 +111,7 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 8b6447c7..6773feb8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; @@ -22,9 +21,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public async Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) + public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { - var configuration = await _configProvider.Get(); + var configuration = _configProvider.Get(); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index 7ae3ff79..e351ab2f 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index e88bfde8..02e3e4f6 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -34,7 +33,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs index 18683e62..8a07a066 100644 --- a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -1,6 +1,4 @@ -using Ocelot.Configuration; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Responses; +using Ocelot.Responses; using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 80365074..631e278a 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,12 +1,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Ocelot.Configuration; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; -using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.Middleware { diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs index 2124ce3b..43a63715 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 6e6b511e..ea5d2c84 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index cc262048..a89d2ec2 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Headers/RemoveOutputHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs index 7a40534b..f3020036 100644 --- a/src/Ocelot/Headers/RemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -3,8 +3,6 @@ using Ocelot.Responses; namespace Ocelot.Headers { - using System; - public class RemoveOutputHeaders : IRemoveOutputHeaders { /// diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 73d25d48..77dc328d 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index cc6ea73b..68e42fe2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; using Ocelot.Responses; namespace Ocelot.LoadBalancer.LoadBalancers diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index fa113771..9aa7442f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -20,8 +20,11 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Options; using Ocelot.Configuration; + using Ocelot.Configuration.File; using Ocelot.Configuration.Provider; + using Ocelot.Configuration.Setter; using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions @@ -127,17 +130,27 @@ namespace Ocelot.Middleware private static async Task CreateConfiguration(IApplicationBuilder builder) { + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + var configSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - var config = await configProvider.Get(); + var config = await configSetter.Set(fileConfig.Value); - //todo move this to config validators - if(config == null || config.Data == null || config.IsError) + if(config == null || config.IsError) { - throw new Exception("Unable to start Ocelot: configuration was invalid"); + throw new Exception("Unable to start Ocelot: configuration was not set up correctly."); } - return config.Data; + var ocelotConfiguration = configProvider.Get(); + + if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) + { + throw new Exception("Unable to start Ocelot: ocelot configuration was not returned by provider."); + } + + return ocelotConfiguration.Data; } private static async Task CreateAdministrationArea(IApplicationBuilder builder) diff --git a/src/Ocelot/Properties/AssemblyInfo.cs b/src/Ocelot/Properties/AssemblyInfo.cs index ad12027e..dd8b7610 100644 --- a/src/Ocelot/Properties/AssemblyInfo.cs +++ b/src/Ocelot/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index 1424d713..edeee51c 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Responses/ErrorResponse.cs b/src/Ocelot/Responses/ErrorResponse.cs index 9f541507..bf20dbd1 100644 --- a/src/Ocelot/Responses/ErrorResponse.cs +++ b/src/Ocelot/Responses/ErrorResponse.cs @@ -5,6 +5,9 @@ namespace Ocelot.Responses { public class ErrorResponse : Response { + public ErrorResponse(Error error) : base(new List{error}) + { + } public ErrorResponse(List errors) : base(errors) { } diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/FileConfigurationProvider.cs similarity index 79% rename from src/Ocelot/Services/GetFileConfiguration.cs rename to src/Ocelot/Services/FileConfigurationProvider.cs index 536a0b46..abac93f9 100644 --- a/src/Ocelot/Services/GetFileConfiguration.cs +++ b/src/Ocelot/Services/FileConfigurationProvider.cs @@ -6,9 +6,9 @@ using Ocelot.Responses; namespace Ocelot.Services { - public class GetFileConfiguration : IGetFileConfiguration + public class FileConfigurationProvider : IFileConfigurationProvider { - public Response Invoke() + public Response Get() { var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; var json = File.ReadAllText(configFilePath); diff --git a/src/Ocelot/Services/IGetFileConfiguration.cs b/src/Ocelot/Services/IFileConfigurationProvider.cs similarity index 52% rename from src/Ocelot/Services/IGetFileConfiguration.cs rename to src/Ocelot/Services/IFileConfigurationProvider.cs index 08b950a8..725f7463 100644 --- a/src/Ocelot/Services/IGetFileConfiguration.cs +++ b/src/Ocelot/Services/IFileConfigurationProvider.cs @@ -3,8 +3,8 @@ using Ocelot.Responses; namespace Ocelot.Services { - public interface IGetFileConfiguration + public interface IFileConfigurationProvider { - Response Invoke(); + Response Get(); } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 7b169503..1cb4b0c9 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -34,6 +34,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index f1aa378b..b77fe483 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -38,6 +38,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 061a2223..98a414f1 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -11,6 +11,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.IntegrationTests/project.json b/test/Ocelot.IntegrationTests/project.json index 7454b3a0..578f9a7f 100644 --- a/test/Ocelot.IntegrationTests/project.json +++ b/test/Ocelot.IntegrationTests/project.json @@ -39,6 +39,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 04dba415..a31fdca2 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -23,6 +23,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 2573e32f..c7d240f8 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -16,14 +16,12 @@ namespace Ocelot.UnitTests.Configuration { private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; private readonly Mock _configurationRepository; - private readonly Mock _creator; private Response _result; public FileConfigurationProviderTests() { - _creator = new Mock(); _configurationRepository = new Mock(); - _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object, _creator.Object); + _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); } [Fact] @@ -35,16 +33,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_create_config_if_it_doesnt_exist() - { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .BDDfy(); - } - [Fact] public void should_return_error() { @@ -61,29 +49,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_return_error_if_creator_errors() - { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new ErrorResponse(new List - { - new AnyError() - }))) - .BDDfy(); - } - - private void GivenTheCreatorReturns(Response config) - { - _creator - .Setup(x => x.Create()) - .ReturnsAsync(config); - } - private void GivenTheRepoReturns(Response config) { _configurationRepository @@ -93,7 +58,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get().Result; + _result = _ocelotConfigurationProvider.Get(); } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs new file mode 100644 index 00000000..aa9b935a --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class FileConfigurationSetterTests + { + private FileConfiguration _fileConfiguration; + private FileConfigurationSetter _configSetter; + private Mock _configRepo; + private Mock _configCreator; + private Response _configuration; + private object _result; + + public FileConfigurationSetterTests() + { + _configRepo = new Mock(); + _configCreator = new Mock(); + _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object); + } + + [Fact] + public void should_set_configuration() + { + var fileConfig = new FileConfiguration(); + var config = new OcelotConfiguration(new List(), string.Empty); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheCreatorReturns(new OkResponse(config))) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_unable_to_set_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorResponseIsReturned() + { + _result.ShouldBeOfType(); + } + + private void GivenTheCreatorReturns(Response configuration) + { + _configuration = configuration; + _configCreator + .Setup(x => x.Create(_fileConfiguration)) + .ReturnsAsync(_configuration); + } + + private void GivenTheFollowingConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _result = _configSetter.Set(_fileConfiguration).Result; + } + + private void ThenTheConfigurationRepositoryIsCalledCorrectly() + { + _configRepo + .Verify(x => x.AddOrReplace(_configuration.Data), Times.Once); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index cd2ab4ce..2638b4a5 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,28 +1,36 @@ +using System; +using System.Net; using Microsoft.AspNetCore.Mvc; using Moq; using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; using Ocelot.Controllers; +using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Services; using TestStack.BDDfy; using Xunit; +using Shouldly; namespace Ocelot.UnitTests.Controllers { public class FileConfigurationControllerTests { private FileConfigurationController _controller; - private Mock _getFileConfig; + private Mock _configGetter; + private Mock _configSetter; private IActionResult _result; + private FileConfiguration _fileConfiguration; public FileConfigurationControllerTests() { - _getFileConfig = new Mock(); - _controller = new FileConfigurationController(_getFileConfig.Object); + _configGetter = new Mock(); + _configSetter = new Mock(); + _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object); } [Fact] - public void should_return_file_configuration() + public void should_get_file_configuration() { var expected = new OkResponse(new FileConfiguration()); @@ -32,10 +40,75 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } + [Fact] + public void should_return_error_when_cannot_get_config() + { + var expected = new ErrorResponse(It.IsAny()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) + .And(x => x.ThenTheResponseIs()) + .BDDfy(); + } + + [Fact] + public void should_post_file_configuration() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturnsAnError(new OkResponse())) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_set_config() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturnsAnError(new ErrorResponse(new FakeError()))) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .And(x => ThenTheResponseIs()) + .BDDfy(); + } + + private void GivenTheConfigSetterReturnsAnError(Response response) + { + _configSetter + .Setup(x => x.Set(It.IsAny())) + .ReturnsAsync(response); + } + + private void ThenTheConfigrationSetterIsCalledCorrectly() + { + _configSetter + .Verify(x => x.Set(_fileConfiguration), Times.Once); + } + + private void WhenIPostTheFileConfiguration() + { + _result = _controller.Post(_fileConfiguration).Result; + } + + private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void ThenTheResponseIs() + { + _result.ShouldBeOfType(); + } + private void GivenTheGetConfigurationReturns(Response fileConfiguration) { - _getFileConfig - .Setup(x => x.Invoke()) + _configGetter + .Setup(x => x.Get()) .Returns(fileConfiguration); } @@ -46,8 +119,15 @@ namespace Ocelot.UnitTests.Controllers private void TheTheGetFileConfigurationIsCalledCorrectly() { - _getFileConfig - .Verify(x => x.Invoke(), Times.Once); + _configGetter + .Verify(x => x.Get(), Times.Once); + } + + class FakeError : Error + { + public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError) + { + } } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index e7718f37..90a35ad8 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _downstreamRoute = new OkResponse(downstreamRoute); _downstreamRouteFinder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny())) - .ReturnsAsync(_downstreamRoute); + .Returns(_downstreamRoute); } public void Dispose() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 0d272c63..49e9b01b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); + .Returns(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) @@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs similarity index 95% rename from test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs rename to test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs index fa86ced6..32266c44 100644 --- a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs +++ b/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs @@ -15,12 +15,12 @@ namespace Ocelot.UnitTests.Services { public class GetFileConfigurationTests { - private readonly IGetFileConfiguration _getReRoutes; + private readonly IFileConfigurationProvider _getReRoutes; private FileConfiguration _result; public GetFileConfigurationTests() { - _getReRoutes = new GetFileConfiguration(); + _getReRoutes = new FileConfigurationProvider(); } [Fact] @@ -74,7 +74,7 @@ namespace Ocelot.UnitTests.Services private void WhenIGetTheReRoutes() { - _result = _getReRoutes.Invoke().Data; + _result = _getReRoutes.Get().Data; } private void ThenTheFollowingIsReturned(FileConfiguration expected) diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 3151ac57..e61760d6 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -29,6 +29,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { From bf90b12f2c1bbb181db0173260665550d3448c1b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 21 Feb 2017 07:39:09 +0000 Subject: [PATCH 15/25] acceptance tests passing after fixing registrations --- src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs | 2 ++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 43ffefe3..19b13e31 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; @@ -111,6 +112,7 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 58e65bff..c1a6716c 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -30,7 +30,7 @@ namespace Ocelot.AcceptanceTests .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } - + [Fact] public void should_return_response_200_with_simple_url() { From aa0d8fe59a6e41226f6ed4d9b827fa5f9b3dc6fe Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 07:48:49 +0000 Subject: [PATCH 16/25] all tests passing, now to do authentication config provider --- configuration.json | 1 + .../File/FileGlobalConfiguration.cs | 4 +- .../File/FileServiceDiscoveryProvider.cs | 1 + .../Provider/FileConfigurationProvider.cs | 25 +++++ .../Provider}/IFileConfigurationProvider.cs | 2 +- .../Repository/FileConfigurationRepository.cs | 42 ++++++++ .../IFileConfigurationRepository.cs | 11 +++ .../Setter/FileConfigurationSetter.cs | 12 ++- .../FileConfigurationController.cs | 4 +- .../ServiceCollectionExtensions.cs | 4 +- .../Services/FileConfigurationProvider.cs | 19 ---- .../AdministrationTests.cs | 95 ++++++++++++++++++- .../configuration.json | 0 .../FileConfigurationProviderTests.cs | 75 ++++++--------- .../FileConfigurationRepositoryTests.cs} | 78 +++++++++++++-- .../FileConfigurationSetterTests.cs | 29 +++++- .../OcelotConfigurationProviderTests.cs | 77 +++++++++++++++ .../FileConfigurationControllerTests.cs | 2 +- 18 files changed, 399 insertions(+), 82 deletions(-) create mode 100755 configuration.json create mode 100644 src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs rename src/Ocelot/{Services => Configuration/Provider}/IFileConfigurationProvider.cs (80%) create mode 100644 src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs create mode 100644 src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs delete mode 100644 src/Ocelot/Services/FileConfigurationProvider.cs mode change 100644 => 100755 test/Ocelot.IntegrationTests/configuration.json rename test/Ocelot.UnitTests/{Services/FileConfigurationGetterTests.cs => Configuration/FileConfigurationRepositoryTests.cs} (51%) create mode 100644 test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs diff --git a/configuration.json b/configuration.json new file mode 100755 index 00000000..a2784d8c --- /dev/null +++ b/configuration.json @@ -0,0 +1 @@ +{"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},{"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}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index fd0f41a9..95e56f95 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -1,4 +1,5 @@ -namespace Ocelot.Configuration.File + +namespace Ocelot.Configuration.File { public class FileGlobalConfiguration { @@ -6,6 +7,7 @@ { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); } + public string RequestIdKey { get; set; } public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} public string AdministrationPath {get;set;} diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 2f26b6ea..70a3c42f 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -2,6 +2,7 @@ namespace Ocelot.Configuration.File { public class FileServiceDiscoveryProvider { + public string Provider {get;set;} public string Host {get;set;} public int Port { get; set; } diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs new file mode 100644 index 00000000..5f6dbe08 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + public class FileConfigurationProvider : IFileConfigurationProvider + { + private IFileConfigurationRepository _repo; + + public FileConfigurationProvider(IFileConfigurationRepository repo) + { + _repo = repo; + } + + public Response Get() + { + var fileConfig = _repo.Get(); + return new OkResponse(fileConfig.Data); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Services/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs similarity index 80% rename from src/Ocelot/Services/IFileConfigurationProvider.cs rename to src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs index 725f7463..3a4dca14 100644 --- a/src/Ocelot/Services/IFileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs @@ -1,7 +1,7 @@ using Ocelot.Configuration.File; using Ocelot.Responses; -namespace Ocelot.Services +namespace Ocelot.Configuration.Provider { public interface IFileConfigurationProvider { diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs new file mode 100644 index 00000000..f32dcaec --- /dev/null +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -0,0 +1,42 @@ +using System; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationRepository : IFileConfigurationRepository + { + private static readonly object _lock = new object(); + public Response Get() + { + var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; + string json = string.Empty; + lock(_lock) + { + json = System.IO.File.ReadAllText(configFilePath); + } + var fileConfiguration = JsonConvert.DeserializeObject(json); + return new OkResponse(fileConfiguration); + } + + public Response Set(FileConfiguration fileConfiguration) + { + var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + lock(_lock) + { + if (System.IO.File.Exists(configurationPath)) + { + System.IO.File.Delete(configurationPath); + } + + System.IO.File.WriteAllText(configurationPath, jsonConfiguration); + } + + return new OkResponse(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs new file mode 100644 index 00000000..26726b46 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs @@ -0,0 +1,11 @@ +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IFileConfigurationRepository + { + Response Get(); + Response Set(FileConfiguration fileConfiguration); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 28957cc7..93dd4d85 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -10,15 +10,25 @@ namespace Ocelot.Configuration.Setter { private readonly IOcelotConfigurationRepository _configRepo; private readonly IOcelotConfigurationCreator _configCreator; + private readonly IFileConfigurationRepository _repo; - public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, IOcelotConfigurationCreator configCreator) + public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, + IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) { _configRepo = configRepo; _configCreator = configCreator; + _repo = repo; } public async Task Set(FileConfiguration fileConfig) { + var response = _repo.Set(fileConfig); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + var config = await _configCreator.Create(fileConfig); if(!config.IsError) diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index a378f530..5e36c64b 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -2,8 +2,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Configuration.File; +using Ocelot.Configuration.Provider; using Ocelot.Configuration.Setter; -using Ocelot.Services; namespace Ocelot.Controllers { @@ -34,7 +34,7 @@ namespace Ocelot.Controllers } [HttpPost] - public async Task Post(FileConfiguration fileConfiguration) + public async Task Post([FromBody]FileConfiguration fileConfiguration) { var response = await _configSetter.Set(fileConfiguration); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 19b13e31..fdc78bd1 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -34,7 +34,6 @@ using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; -using Ocelot.Services; namespace Ocelot.DependencyInjection { @@ -112,8 +111,9 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Services/FileConfigurationProvider.cs b/src/Ocelot/Services/FileConfigurationProvider.cs deleted file mode 100644 index abac93f9..00000000 --- a/src/Ocelot/Services/FileConfigurationProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.IO; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Services -{ - public class FileConfigurationProvider : IFileConfigurationProvider - { - public Response Get() - { - var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; - var json = File.ReadAllText(configFilePath); - var fileConfiguration = JsonConvert.DeserializeObject(json); - return new OkResponse(fileConfiguration); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 6ea1aa32..6661a2cc 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -74,7 +74,14 @@ namespace Ocelot.IntegrationTests { GlobalConfiguration = new FileGlobalConfiguration { - AdministrationPath = "/administration" + AdministrationPath = "/administration", + RequestIdKey = "RequestId", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "127.0.0.1", + Provider = "test" + } + }, ReRoutes = new List() { @@ -109,6 +116,88 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_get_file_configuration_edit_and_post_updated_version() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" + } + } + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "127.0.0.1", + DownstreamPort = 80, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "123.123.123", + DownstreamPort = 443, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = "post", + UpstreamPathTemplate = "/test" + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .BDDfy(); + } + + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + private void ThenTheResponseShouldBe(FileConfiguration expected) { var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); @@ -180,6 +269,8 @@ namespace Ocelot.IntegrationTests File.WriteAllText(configurationPath, jsonConfiguration); + var text = File.ReadAllText(configurationPath); + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; if (File.Exists(configurationPath)) @@ -188,6 +279,8 @@ namespace Ocelot.IntegrationTests } File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); } private void WhenIGetUrlOnTheApiGateway(string url) diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json old mode 100644 new mode 100755 diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index c7d240f8..a1ea4af7 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -1,77 +1,62 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Moq; using Ocelot.Configuration; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; -using Ocelot.Errors; +using Ocelot.Configuration.File; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; +using Newtonsoft.Json; +using System.IO; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; namespace Ocelot.UnitTests.Configuration { public class FileConfigurationProviderTests { - private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; - private readonly Mock _configurationRepository; - private Response _result; + private readonly IFileConfigurationProvider _provider; + private Mock _repo; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; public FileConfigurationProviderTests() { - _configurationRepository = new Mock(); - _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); + _repo = new Mock(); + _provider = new FileConfigurationProvider(_repo.Object); } [Fact] - public void should_get_config() + public void should_return_file_configuration() { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + var config = new FileConfiguration(); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheRepoIsCalledCorrectly()) .BDDfy(); } - [Fact] - public void should_return_error() - { - this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned( - new ErrorResponse(new List - { - new AnyError() - }))) - .BDDfy(); - } + - private void GivenTheRepoReturns(Response config) + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - _configurationRepository + _fileConfiguration = fileConfiguration; + _repo .Setup(x => x.Get()) - .Returns(config); + .Returns(new OkResponse(fileConfiguration)); } - private void WhenIGetTheConfig() + private void WhenIGetTheReRoutes() { - _result = _ocelotConfigurationProvider.Get(); + _result = _provider.Get().Data; } - private void TheFollowingIsReturned(Response expected) + private void ThenTheRepoIsCalledCorrectly() { - _result.IsError.ShouldBe(expected.IsError); - } - - class AnyError : Error - { - public AnyError() - : base("blamo", OcelotErrorCode.UnknownError) - { - } + _repo + .Verify(x => x.Get(), Times.Once); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs similarity index 51% rename from test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index 32266c44..dfa8f67a 100644 --- a/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -7,20 +7,21 @@ using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; -using Ocelot.Services; using Newtonsoft.Json; using System.IO; +using Ocelot.Configuration.Repository; -namespace Ocelot.UnitTests.Services +namespace Ocelot.UnitTests.Configuration { - public class GetFileConfigurationTests + public class FileConfigurationRepositoryTests { - private readonly IFileConfigurationProvider _getReRoutes; + private readonly IFileConfigurationRepository _repo; private FileConfiguration _result; + private FileConfiguration _fileConfiguration; - public GetFileConfigurationTests() + public FileConfigurationRepositoryTests() { - _getReRoutes = new FileConfigurationProvider(); + _repo = new FileConfigurationRepository(); } [Fact] @@ -58,6 +59,69 @@ namespace Ocelot.UnitTests.Services .BDDfy(); } + [Fact] + public void should_set_file_configuration() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHost = "123.12.12.12", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "asdas", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Port = 198, + Host = "blah" + } + }; + + var config = new FileConfiguration(); + config.GlobalConfiguration = globalConfiguration; + config.ReRoutes.AddRange(reRoutes); + + this.Given(x => GivenIHaveAConfiguration(config)) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationIsStoredAs(config)) + .BDDfy(); + } + + private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _repo.Set(_fileConfiguration); + _result = _repo.Get().Data; + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration expected) + { + _result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + } + } + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; @@ -74,7 +138,7 @@ namespace Ocelot.UnitTests.Services private void WhenIGetTheReRoutes() { - _result = _getReRoutes.Get().Data; + _result = _repo.Get().Data; } private void ThenTheFollowingIsReturned(FileConfiguration expected) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index aa9b935a..bf4804f6 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Moq; using Ocelot.Configuration; @@ -21,12 +22,14 @@ namespace Ocelot.UnitTests.Configuration private Mock _configCreator; private Response _configuration; private object _result; + private Mock _repo; public FileConfigurationSetterTests() { + _repo = new Mock(); _configRepo = new Mock(); _configCreator = new Mock(); - _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object); + _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); } [Fact] @@ -36,24 +39,46 @@ namespace Ocelot.UnitTests.Configuration var config = new OcelotConfiguration(new List(), string.Empty); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) .And(x => GivenTheCreatorReturns(new OkResponse(config))) .When(x => WhenISetTheConfiguration()) .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) .BDDfy(); } + [Fact] - public void should_return_error_if_unable_to_set_configuration() + public void should_return_error_if_unable_to_set_file_configuration() { var fileConfig = new FileConfiguration(); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_unable_to_set_ocelot_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) .When(x => WhenISetTheConfiguration()) .And(x => ThenAnErrorResponseIsReturned()) .BDDfy(); } + private void GivenTheRepoReturns(Response response) + { + _repo + .Setup(x => x.Set(It.IsAny())) + .Returns(response); + } + private void ThenAnErrorResponseIsReturned() { _result.ShouldBeOfType(); diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs new file mode 100644 index 00000000..9be55087 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class OcelotConfigurationProviderTests + { + private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; + private readonly Mock _configurationRepository; + private Response _result; + + public OcelotConfigurationProviderTests() + { + _configurationRepository = new Mock(); + _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); + } + + [Fact] + public void should_get_config() + { + this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned( + new ErrorResponse(new List + { + new AnyError() + }))) + .BDDfy(); + } + + private void GivenTheRepoReturns(Response config) + { + _configurationRepository + .Setup(x => x.Get()) + .Returns(config); + } + + private void WhenIGetTheConfig() + { + _result = _ocelotConfigurationProvider.Get(); + } + + private void TheFollowingIsReturned(Response expected) + { + _result.IsError.ShouldBe(expected.IsError); + } + + class AnyError : Error + { + public AnyError() + : base("blamo", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 2638b4a5..7f4d7b87 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -7,10 +7,10 @@ using Ocelot.Configuration.Setter; using Ocelot.Controllers; using Ocelot.Errors; using Ocelot.Responses; -using Ocelot.Services; using TestStack.BDDfy; using Xunit; using Shouldly; +using Ocelot.Configuration.Provider; namespace Ocelot.UnitTests.Controllers { From f8804f5d9deba1a373ba86ba1128a10590ce1833 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 20:04:51 +0000 Subject: [PATCH 17/25] started adding some kind of auth config --- ...odedIdentityServerConfigurationProvider.cs | 86 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 44 ++++------ .../Middleware/OcelotMiddlewareExtensions.cs | 11 ++- 3 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs diff --git a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs new file mode 100644 index 00000000..09780bc6 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using IdentityServer4.Test; + +namespace Ocelot.Configuration.Provider +{ + public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider + { + public IdentityServerConfiguration Get() + { + var url = ""; + return new IdentityServerConfiguration( + url, + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List { + new TestUser + { + Username = "admin", + Password = "admin", + SubjectId = "admin", + } + } + ); + } + } + + public interface IIdentityServerConfigurationProvider + { + IdentityServerConfiguration Get(); + } + + public class IdentityServerConfiguration + { + public IdentityServerConfiguration( + string identityServerUrl, + string apiName, + bool requireHttps, + SupportedTokens supportedTokens, + string apiSecret, + List allowedScopes, + string description, + bool enabled, + IEnumerable grantType, + AccessTokenType accessTokenType, + bool requireClientSecret, + List users) + { + IdentityServerUrl = identityServerUrl; + ApiName = apiName; + RequireHttps = requireHttps; + SupportedTokens = supportedTokens; + ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + Description = description; + Enabled = enabled; + AllowedGrantTypes = grantType; + AccessTokenType = accessTokenType; + RequireClientSecret = requireClientSecret; + Users = users; + } + + public string IdentityServerUrl { get; private set; } + public string ApiName { get; private set; } + public bool RequireHttps { get; private set; } + public List AllowedScopes { get; private set; } + public SupportedTokens SupportedTokens { get; private set; } + public string ApiSecret { get; private set; } + public string Description {get;private set;} + public bool Enabled {get;private set;} + public IEnumerable AllowedGrantTypes {get;private set;} + public AccessTokenType AccessTokenType {get;private set;} + public bool RequireClientSecret = false; + public List Users {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index fdc78bd1..0529c938 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using CacheManager.Core; using IdentityServer4.Models; @@ -60,27 +61,25 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { + var authProvider = new HardCodedIdentityServerConfigurationProvider(); + var identityServerConfig = authProvider.Get(); + services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(new List { new ApiResource { - Name = "admin", - Description = "Ocelot Administration", - Enabled = true, - DisplayName = "admin", - Scopes = new List() - { - new Scope("admin"), - new Scope("openid"), - new Scope("offline_access") - }, + Name = identityServerConfig.ApiName, + Description = identityServerConfig.Description, + Enabled = identityServerConfig.Enabled, + DisplayName = identityServerConfig.ApiName, + Scopes = identityServerConfig.AllowedScopes.Select(x => new Scope(x)).ToList(), ApiSecrets = new List { new Secret { - Value = "secret".Sha256() + Value = identityServerConfig.ApiSecret.Sha256() } } } @@ -89,24 +88,17 @@ namespace Ocelot.DependencyInjection { new Client { - ClientId = "admin", + ClientId = identityServerConfig.ApiName, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List {"admin", "openid", "offline_access"}, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true, - RequireClientSecret = false + ClientSecrets = new List {new Secret(identityServerConfig.ApiSecret.Sha256())}, + AllowedScopes = identityServerConfig.AllowedScopes, + AccessTokenType = identityServerConfig.AccessTokenType, + Enabled = identityServerConfig.Enabled, + RequireClientSecret = identityServerConfig.RequireClientSecret } }) - .AddTestUsers(new List - { - new TestUser - { - Username = "admin", - Password = "admin", - SubjectId = "admin", - } - }); + .AddTestUsers(identityServerConfig.Users); + services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9aa7442f..57ed9bf3 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -157,6 +157,9 @@ namespace Ocelot.Middleware { var configuration = await CreateConfiguration(builder); + var authProvider = new HardCodedIdentityServerConfigurationProvider(); + var identityServerConfig = authProvider.Get(); + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { builder.Map(configuration.AdministrationPath, app => @@ -166,11 +169,11 @@ namespace Ocelot.Middleware app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = identityServerUrl, - ApiName = "admin", - RequireHttpsMetadata = false, - AllowedScopes = new List(), + ApiName = identityServerConfig.ApiName, + RequireHttpsMetadata = identityServerConfig.RequireHttps, + AllowedScopes = identityServerConfig.AllowedScopes, SupportedTokens = SupportedTokens.Both, - ApiSecret = "secret" + ApiSecret = identityServerConfig.ApiSecret }); app.UseIdentityServer(); From bd07af692657e140b67f9638d7ae856e136dfb96 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 22:13:35 +0000 Subject: [PATCH 18/25] more work towards getting identity server and admin area set up --- README.md | 18 +++++ configuration.json | 2 +- .../ServiceCollectionExtensions.cs | 68 ++++++++++--------- .../Middleware/OcelotMiddlewareExtensions.cs | 43 ++++++++---- test/Ocelot.AcceptanceTests/Steps.cs | 22 +++++- .../AdministrationTests.cs | 12 +++- test/Ocelot.ManualTest/Program.cs | 15 ++-- test/Ocelot.ManualTest/Startup.cs | 10 ++- 8 files changed, 132 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index dfbdfd1e..d0db1e39 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,24 @@ Currently this is the only way to get configuration into Ocelot. } } +Then in your Program.cs you will want to have the following.. + + IWebHostBuilder builder = new WebHostBuilder(); + + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + + var host = builder.Build(); + + host.Run(); + +Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot +find a better way of doing this at the moment without setting this in a static or some kind of config. This is pretty much all you need to get going.......more to come! diff --git a/configuration.json b/configuration.json index a2784d8c..2faeadfa 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"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},{"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}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"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},{"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}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0529c938..52c47a25 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using CacheManager.Core; using IdentityServer4.Models; using IdentityServer4.Test; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -61,44 +62,49 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { - var authProvider = new HardCodedIdentityServerConfigurationProvider(); - var identityServerConfig = authProvider.Get(); + return AddOcelot(services, null); + } - services.AddIdentityServer() - .AddTemporarySigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource + public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration) + { + if(identityServerConfiguration != null) + { + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - Name = identityServerConfig.ApiName, - Description = identityServerConfig.Description, - Enabled = identityServerConfig.Enabled, - DisplayName = identityServerConfig.ApiName, - Scopes = identityServerConfig.AllowedScopes.Select(x => new Scope(x)).ToList(), - ApiSecrets = new List + new ApiResource { - new Secret + Name = identityServerConfiguration.ApiName, + Description = identityServerConfiguration.Description, + Enabled = identityServerConfiguration.Enabled, + DisplayName = identityServerConfiguration.ApiName, + Scopes = identityServerConfiguration.AllowedScopes.Select(x => new Scope(x)).ToList(), + ApiSecrets = new List { - Value = identityServerConfig.ApiSecret.Sha256() + new Secret + { + Value = identityServerConfiguration.ApiSecret.Sha256() + } } } - } - }) - .AddInMemoryClients(new List - { - new Client + }) + .AddInMemoryClients(new List { - ClientId = identityServerConfig.ApiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret(identityServerConfig.ApiSecret.Sha256())}, - AllowedScopes = identityServerConfig.AllowedScopes, - AccessTokenType = identityServerConfig.AccessTokenType, - Enabled = identityServerConfig.Enabled, - RequireClientSecret = identityServerConfig.RequireClientSecret - } - }) - .AddTestUsers(identityServerConfig.Users); - + new Client + { + ClientId = identityServerConfiguration.ApiName, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, + AllowedScopes = identityServerConfiguration.AllowedScopes, + AccessTokenType = identityServerConfiguration.AccessTokenType, + Enabled = identityServerConfiguration.Enabled, + RequireClientSecret = identityServerConfiguration.RequireClientSecret + } + }) + .AddTestUsers(identityServerConfiguration.Users); + } + services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 57ed9bf3..99509650 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -19,6 +19,7 @@ namespace Ocelot.Middleware using System; using System.Threading.Tasks; using Authorisation.Middleware; + using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Ocelot.Configuration; @@ -36,7 +37,21 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); + await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration) + { + await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration) + { + await builder.UseOcelot(middlewareConfiguration, null); return builder; } @@ -47,9 +62,9 @@ namespace Ocelot.Middleware /// /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration) { - await CreateAdministrationArea(builder); + await CreateAdministrationArea(builder, identityServerConfiguration); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -153,27 +168,28 @@ namespace Ocelot.Middleware return ocelotConfiguration.Data; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder) + private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration) { var configuration = await CreateConfiguration(builder); - var authProvider = new HardCodedIdentityServerConfigurationProvider(); - var identityServerConfig = authProvider.Get(); - - if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { + var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); + + var baseSchemeUrlAndPort = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + builder.Map(configuration.AdministrationPath, app => { - var identityServerUrl = $"http://localhost:5000/{configuration.AdministrationPath.Remove(0,1)}"; + var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}"; app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = identityServerUrl, - ApiName = identityServerConfig.ApiName, - RequireHttpsMetadata = identityServerConfig.RequireHttps, - AllowedScopes = identityServerConfig.AllowedScopes, + ApiName = identityServerConfiguration.ApiName, + RequireHttpsMetadata = identityServerConfiguration.RequireHttps, + AllowedScopes = identityServerConfiguration.AllowedScopes, SupportedTokens = SupportedTokens.Both, - ApiSecret = identityServerConfig.ApiSecret + ApiSecret = identityServerConfiguration.ApiSecret }); app.UseIdentityServer(); @@ -182,7 +198,6 @@ namespace Ocelot.Middleware }); } } - private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 1f4a8da1..d923148a 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -11,6 +11,7 @@ using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Configuration.File; @@ -32,6 +33,7 @@ namespace Ocelot.AcceptanceTests public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; private readonly Random _random; + private IWebHostBuilder _webHostBuilder; public Steps() { @@ -69,7 +71,14 @@ namespace Ocelot.AcceptanceTests /// public void GivenOcelotIsRunning() { - _ocelotServer = new TestServer(new WebHostBuilder() + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder .UseStartup()); _ocelotClient = _ocelotServer.CreateClient(); @@ -109,7 +118,14 @@ namespace Ocelot.AcceptanceTests var configuration = builder.Build(); - _ocelotServer = new TestServer(new WebHostBuilder() + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder .UseConfiguration(configuration) .ConfigureServices(s => { @@ -121,7 +137,7 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - + s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 6661a2cc..06e7bd5d 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.ManualTest; @@ -19,6 +20,7 @@ namespace Ocelot.IntegrationTests private readonly HttpClient _httpClient; private HttpResponseMessage _response; private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; private readonly string _ocelotBaseUrl; private BearerToken _token; @@ -246,12 +248,16 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunning() { - _builder = new WebHostBuilder() + _webHostBuilder = new WebHostBuilder() .UseUrls(_ocelotBaseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .Build(); + .ConfigureServices(x => { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); _builder.Start(); } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index a049d3ea..a545819a 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -1,5 +1,6 @@ using System.IO; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.ManualTest { @@ -7,11 +8,17 @@ namespace Ocelot.ManualTest { public static void Main(string[] args) { - var host = new WebHostBuilder() - .UseKestrel() + IWebHostBuilder builder = new WebHostBuilder(); + + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + + builder.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .Build(); + .UseStartup(); + + var host = builder.Build(); host.Run(); } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index b187f6b4..aa34c9a2 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Ocelot.Configuration.Provider; using Ocelot.DependencyInjection; using Ocelot.Middleware; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; @@ -14,6 +15,8 @@ namespace Ocelot.ManualTest { public class Startup { + private IdentityServerConfiguration _identityServerConfig; + public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() @@ -24,6 +27,9 @@ namespace Ocelot.ManualTest .AddEnvironmentVariables(); Configuration = builder.Build(); + + var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider(); + _identityServerConfig = identityServerConfigProvider.Get(); } public IConfigurationRoot Configuration { get; } @@ -41,14 +47,14 @@ namespace Ocelot.ManualTest services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); + services.AddOcelot(_identityServerConfig); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - await app.UseOcelot(); + await app.UseOcelot(_identityServerConfig); } } } From 112a9c303e53f34a95eafcc488f4f0d545be816d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 24 Feb 2017 19:52:48 +0000 Subject: [PATCH 19/25] hacky auth working --- README.md | 48 ++++++- configuration.json | 2 +- .../Authentication/HashMatcher.cs | 22 ++++ .../Authentication/IHashMatcher.cs | 7 ++ .../OcelotResourceOwnerPasswordValidator.cs | 53 ++++++++ .../Provider/IIdentityServerConfiguration.cs | 21 ++++ ...ider.cs => IdentityServerConfiguration.cs} | 47 +------ src/Ocelot/Configuration/Provider/User.cs | 17 +++ .../ServiceCollectionExtensions.cs | 26 ++-- .../Middleware/OcelotMiddlewareExtensions.cs | 24 +--- src/Ocelot/project.json | 3 +- test/Ocelot.AcceptanceTests/Steps.cs | 3 +- .../AdministrationTests.cs | 2 +- test/Ocelot.ManualTest/Startup.cs | 35 ++++-- test/Ocelot.ManualTest/project.json | 3 +- .../Configuration/HashCreationTests.cs | 33 +++++ .../Configuration/HashMatcherTests.cs | 76 ++++++++++++ ...elotResourceOwnerPasswordValidatorTests.cs | 117 ++++++++++++++++++ test/Ocelot.UnitTests/project.json | 3 +- 19 files changed, 448 insertions(+), 94 deletions(-) create mode 100644 src/Ocelot/Configuration/Authentication/HashMatcher.cs create mode 100644 src/Ocelot/Configuration/Authentication/IHashMatcher.cs create mode 100644 src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs create mode 100644 src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs rename src/Ocelot/Configuration/Provider/{HardCodedIdentityServerConfigurationProvider.cs => IdentityServerConfiguration.cs} (52%) create mode 100644 src/Ocelot/Configuration/Provider/User.cs create mode 100644 test/Ocelot.UnitTests/Configuration/HashCreationTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs diff --git a/README.md b/README.md index d0db1e39..d2316c9a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) + Attempt at a .NET Api Gateway This project is aimed at people using .NET running @@ -73,7 +75,7 @@ More information on how to use these options is below.. An example startup using a json file for configuration can be seen below. Currently this is the only way to get configuration into Ocelot. - public class Startup + public class Startup { public Startup(IHostingEnvironment env) { @@ -101,15 +103,14 @@ Currently this is the only way to get configuration into Ocelot. }; services.AddOcelotOutputCaching(settings); - services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); + services.AddOcelot(Configuration); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - app.UseOcelot(); + await app.UseOcelot(); } } @@ -386,6 +387,43 @@ In orde to use caching on a route in your ReRoute configuration add this setting In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. +## Administration + +Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated +using bearer tokens that you request from iteself. This support is provided by the amazing IdentityServer +project that I have been using for a few years now. Check them out. + +In order to enable the administration section you need to do a few things. First of all add this to your +initial configuration.json. The value can be anything you want and it is obviously reccomended don't use +a url you would like to route through with Ocelot as this will not work. The administration uses the +MapWhen functionality of asp.net core and all requests to root/administration will be sent there not +to the Ocelot middleware. + + "GlobalConfiguration": { + "AdministrationPath": "/administration" + } + +This will get the admin area set up but not the authentication. You need to set 3 environmental variables. + + OCELOT_USERNAME + OCELOT_HASH + OCELOT_SALT + +These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to +use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to +supply username and password. + +In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() this technique is based on MS doc I found online TODO find and link... + +OK next thing is to get this config into Ocelot... + + +At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At +least your stuff needs to be hashed! + + + + ## Ocelot Middleware injection and overrides Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware diff --git a/configuration.json b/configuration.json index 2faeadfa..3f39532c 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"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},{"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}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/HashMatcher.cs b/src/Ocelot/Configuration/Authentication/HashMatcher.cs new file mode 100644 index 00000000..08773332 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/HashMatcher.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace Ocelot.Configuration.Authentication +{ + public class HashMatcher : IHashMatcher + { + public bool Match(string password, string salt, string hash) + { + byte[] s = Convert.FromBase64String(salt); + + string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: s, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + + return hashed == hash; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs new file mode 100644 index 00000000..ad0d8e03 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Configuration.Authentication +{ + public interface IHashMatcher + { + bool Match(string password, string salt, string hash); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs b/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs new file mode 100644 index 00000000..416c8ec2 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Validation; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Configuration.Authentication +{ + public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator + { + private readonly IHashMatcher _matcher; + private readonly IIdentityServerConfiguration _identityServerConfiguration; + + public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration) + { + _identityServerConfiguration = identityServerConfiguration; + _matcher = matcher; + } + + public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) + { + try + { + var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName); + + if(user == null) + { + context.Result = new GrantValidationResult( + TokenRequestErrors.InvalidGrant, + "invalid custom credential"); + } + else if(_matcher.Match(context.Password, user.Salt, user.Hash)) + { + context.Result = new GrantValidationResult( + subject: "admin", + authenticationMethod: "custom"); + } + else + { + context.Result = new GrantValidationResult( + TokenRequestErrors.InvalidGrant, + "invalid custom credential"); + } + } + catch(Exception ex) + { + Console.WriteLine(ex); + } + + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs new file mode 100644 index 00000000..bb66265f --- /dev/null +++ b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; + +namespace Ocelot.Configuration.Provider +{ + public interface IIdentityServerConfiguration + { + string ApiName { get; } + bool RequireHttps { get; } + List AllowedScopes { get; } + SupportedTokens SupportedTokens { get; } + string ApiSecret { get; } + string Description {get;} + bool Enabled {get;} + IEnumerable AllowedGrantTypes {get;} + AccessTokenType AccessTokenType {get;} + bool RequireClientSecret {get;} + List Users {get;} + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs similarity index 52% rename from src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs rename to src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs index 09780bc6..f0f6897d 100644 --- a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs @@ -1,49 +1,12 @@ -using System; using System.Collections.Generic; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; -using IdentityServer4.Test; namespace Ocelot.Configuration.Provider { - public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider - { - public IdentityServerConfiguration Get() - { - var url = ""; - return new IdentityServerConfiguration( - url, - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List { - new TestUser - { - Username = "admin", - Password = "admin", - SubjectId = "admin", - } - } - ); - } - } - - public interface IIdentityServerConfigurationProvider - { - IdentityServerConfiguration Get(); - } - - public class IdentityServerConfiguration + public class IdentityServerConfiguration : IIdentityServerConfiguration { public IdentityServerConfiguration( - string identityServerUrl, string apiName, bool requireHttps, SupportedTokens supportedTokens, @@ -54,9 +17,8 @@ namespace Ocelot.Configuration.Provider IEnumerable grantType, AccessTokenType accessTokenType, bool requireClientSecret, - List users) + List users) { - IdentityServerUrl = identityServerUrl; ApiName = apiName; RequireHttps = requireHttps; SupportedTokens = supportedTokens; @@ -70,7 +32,6 @@ namespace Ocelot.Configuration.Provider Users = users; } - public string IdentityServerUrl { get; private set; } public string ApiName { get; private set; } public bool RequireHttps { get; private set; } public List AllowedScopes { get; private set; } @@ -80,7 +41,7 @@ namespace Ocelot.Configuration.Provider public bool Enabled {get;private set;} public IEnumerable AllowedGrantTypes {get;private set;} public AccessTokenType AccessTokenType {get;private set;} - public bool RequireClientSecret = false; - public List Users {get;private set;} + public bool RequireClientSecret {get;private set;} + public List Users {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/User.cs b/src/Ocelot/Configuration/Provider/User.cs new file mode 100644 index 00000000..f61ff4e5 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/User.cs @@ -0,0 +1,17 @@ +namespace Ocelot.Configuration.Provider +{ + public class User + { + public User(string subject, string userName, string hash, string salt) + { + Subject = subject; + UserName = userName; + Hash = hash; + Salt = salt; + } + public string Subject { get; private set; } + public string UserName { get; private set; } + public string Hash { get; private set; } + public string Salt { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 52c47a25..ccd63e64 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; using Ocelot.Cache; using Ocelot.Claims; +using Ocelot.Configuration.Authentication; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; @@ -41,7 +42,6 @@ namespace Ocelot.DependencyInjection { public static class ServiceCollectionExtensions { - public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action settings) { var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); @@ -51,24 +51,23 @@ namespace Ocelot.DependencyInjection return services; } - public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) + + public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) + { + return AddOcelot(services, configurationRoot, null); + } + + public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration) { services.Configure(configurationRoot); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - return services; - } - - public static IServiceCollection AddOcelot(this IServiceCollection services) - { - return AddOcelot(services, null); - } - - public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration) - { + if(identityServerConfiguration != null) { + services.AddSingleton(identityServerConfiguration); + services.AddSingleton(); services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(new List @@ -101,8 +100,7 @@ namespace Ocelot.DependencyInjection Enabled = identityServerConfiguration.Enabled, RequireClientSecret = identityServerConfiguration.RequireClientSecret } - }) - .AddTestUsers(identityServerConfiguration.Users); + }).AddResourceOwnerValidator(); } services.AddMvcCore() diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 99509650..553b05b8 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -37,21 +37,7 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration) - { - await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration) - { - await builder.UseOcelot(middlewareConfiguration, null); + await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } @@ -62,9 +48,9 @@ namespace Ocelot.Middleware /// /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { - await CreateAdministrationArea(builder, identityServerConfiguration); + await CreateAdministrationArea(builder); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -168,10 +154,12 @@ namespace Ocelot.Middleware return ocelotConfiguration.Data; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration) + private static async Task CreateAdministrationArea(IApplicationBuilder builder) { var configuration = await CreateConfiguration(builder); + var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 1cb4b0c9..1ca9d403 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -29,7 +29,8 @@ "CacheManager.Microsoft.Extensions.Logging": "0.9.2", "Consul": "0.7.2.1", "Polly": "5.0.3", - "IdentityServer4": "1.0.1" + "IdentityServer4": "1.0.1", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index d923148a..5a256c81 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -139,8 +139,7 @@ namespace Ocelot.AcceptanceTests }; s.AddOcelotOutputCaching(settings); - s.AddOcelotFileConfiguration(configuration); - s.AddOcelot(); + s.AddOcelot(configuration); }) .ConfigureLogging(l => { diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 06e7bd5d..210c7ac5 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -235,7 +235,7 @@ namespace Ocelot.IntegrationTests new KeyValuePair("client_secret", "secret"), new KeyValuePair("scope", "admin"), new KeyValuePair("username", "admin"), - new KeyValuePair("password", "admin"), + new KeyValuePair("password", "secret"), new KeyValuePair("grant_type", "password") }; var content = new FormUrlEncodedContent(formData); diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index aa34c9a2..c1277af7 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -15,7 +18,7 @@ namespace Ocelot.ManualTest { public class Startup { - private IdentityServerConfiguration _identityServerConfig; + private IIdentityServerConfiguration _identityServerConfig; public Startup(IHostingEnvironment env) { @@ -27,9 +30,6 @@ namespace Ocelot.ManualTest .AddEnvironmentVariables(); Configuration = builder.Build(); - - var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider(); - _identityServerConfig = identityServerConfigProvider.Get(); } public IConfigurationRoot Configuration { get; } @@ -46,15 +46,36 @@ namespace Ocelot.ManualTest }; services.AddOcelotOutputCaching(settings); - services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(_identityServerConfig); + + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + _identityServerConfig = new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + + services.AddOcelot(Configuration, _identityServerConfig); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - await app.UseOcelot(_identityServerConfig); + await app.UseOcelot(); } } } diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index a31fdca2..52a8962f 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -14,7 +14,8 @@ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Consul": "0.7.2.1", - "Polly": "5.0.3" + "Polly": "5.0.3", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "tools": { diff --git a/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs new file mode 100644 index 00000000..d99efd58 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashCreationTests + { + [Fact] + public void should_create_hash_and_salt() + { + var password = "secret"; + + var salt = new byte[128 / 8]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + + var storedSalt = Convert.ToBase64String(salt); + + var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs new file mode 100644 index 00000000..34e9477e --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs @@ -0,0 +1,76 @@ +using Ocelot.Configuration.Authentication; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashMatcherTests + { + private string _password; + private string _hash; + private string _salt; + private bool _result; + private HashMatcher _hashMatcher; + + public HashMatcherTests() + { + _hashMatcher = new HashMatcher(); + } + + [Fact] + public void should_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(true)) + .BDDfy(); + } + + [Fact] + public void should_not_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret1"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(false)) + .BDDfy(); + } + + private void GivenThePassword(string password) + { + _password = password; + } + + private void GivenTheHash(string hash) + { + _hash = hash; + } + + private void GivenTheSalt(string salt) + { + _salt = salt; + } + + private void WhenIMatch() + { + _result = _hashMatcher.Match(_password, _salt, _hash); + } + + private void ThenTheResultIs(bool expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs new file mode 100644 index 00000000..a8d11713 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs @@ -0,0 +1,117 @@ +using Ocelot.Configuration.Authentication; +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Moq; +using IdentityServer4.Validation; +using Ocelot.Configuration.Provider; +using System.Collections.Generic; + +namespace Ocelot.UnitTests.Configuration +{ + public class OcelotResourceOwnerPasswordValidatorTests + { + private OcelotResourceOwnerPasswordValidator _validator; + private Mock _matcher; + private string _userName; + private string _password; + private ResourceOwnerPasswordValidationContext _context; + private Mock _config; + private User _user; + + public OcelotResourceOwnerPasswordValidatorTests() + { + _matcher = new Mock(); + _config = new Mock(); + _validator = new OcelotResourceOwnerPasswordValidator(_matcher.Object, _config.Object); + } + + [Fact] + public void should_return_success() + { + this.Given(x => GivenTheUserName("tom")) + .And(x => GivenThePassword("password")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(true)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsValidated()) + .And(x => ThenTheMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_fail_when_no_user() + { + this.Given(x => GivenTheUserName("bob")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(true)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsNotValidated()) + .BDDfy(); + } + + [Fact] + public void should_return_fail_when_password_doesnt_match() + { + this.Given(x => GivenTheUserName("tom")) + .And(x => GivenThePassword("password")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(false)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsNotValidated()) + .And(x => ThenTheMatcherIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheMatcherIsCalledCorrectly() + { + _matcher + .Verify(x => x.Match(_password, _user.Salt, _user.Hash), Times.Once); + } + + private void GivenThePassword(string password) + { + _password = password; + } + + private void GivenTheUserIs(User user) + { + _user = user; + _config + .Setup(x => x.Users) + .Returns(new List{_user}); + } + + private void GivenTheMatcherReturns(bool expected) + { + _matcher + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(expected); + } + + private void GivenTheUserName(string userName) + { + _userName = userName; + } + + private void WhenIValidate() + { + _context = new ResourceOwnerPasswordValidationContext + { + UserName = _userName, + Password = _password + }; + _validator.ValidateAsync(_context).Wait(); + } + + private void ThenTheUserIsValidated() + { + _context.Result.IsError.ShouldBe(false); + } + + private void ThenTheUserIsNotValidated() + { + _context.Result.IsError.ShouldBe(true); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index e61760d6..bbc706b6 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -24,7 +24,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.DotNet.InternalAbstractions": "1.0.0" + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "runtimes": { "win10-x64": {}, From 07c334cc985455e969e88ada89478d2b58e8f955 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 16:09:00 +0000 Subject: [PATCH 20/25] moved stuff around so a bit less crap --- .../ServiceCollectionExtensions.cs | 32 ++++++++++++++++--- test/Ocelot.ManualTest/Startup.cs | 31 +----------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index ccd63e64..825edea4 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using IdentityServer4.Test; using Microsoft.AspNetCore.Hosting; @@ -53,16 +54,13 @@ namespace Ocelot.DependencyInjection } public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) - { - return AddOcelot(services, configurationRoot, null); - } - - public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration) { services.Configure(configurationRoot); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + var identityServerConfiguration = GetIdentityServerConfiguration(); if(identityServerConfiguration != null) { @@ -143,5 +141,29 @@ namespace Ocelot.DependencyInjection return services; } + + private static IdentityServerConfiguration GetIdentityServerConfiguration() + { + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + return new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + } } } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index c1277af7..3c6d2949 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Ocelot.Configuration.Provider; using Ocelot.DependencyInjection; using Ocelot.Middleware; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; @@ -18,8 +13,6 @@ namespace Ocelot.ManualTest { public class Startup { - private IIdentityServerConfiguration _identityServerConfig; - public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() @@ -46,29 +39,7 @@ namespace Ocelot.ManualTest }; services.AddOcelotOutputCaching(settings); - - var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); - var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); - var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); - - _identityServerConfig = new IdentityServerConfiguration( - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List - { - new User("admin", username, hash, salt) - } - ); - - services.AddOcelot(Configuration, _identityServerConfig); + services.AddOcelot(Configuration); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) From be24f9a9ca699a8ecbacd161186bb35c8fdd4eb7 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 17:02:36 +0000 Subject: [PATCH 21/25] Added base url finder for when nothing set in Program.cs --- .../ServiceCollectionExtensions.cs | 7 ++- src/Ocelot/Middleware/BaseUrlFinder.cs | 21 +++++++ src/Ocelot/Middleware/IBaseUrlFinder.cs | 7 +++ .../Middleware/OcelotMiddlewareExtensions.cs | 7 ++- .../Middleware/BaseUrlFinderTests.cs | 61 +++++++++++++++++++ 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/Ocelot/Middleware/BaseUrlFinder.cs create mode 100644 src/Ocelot/Middleware/IBaseUrlFinder.cs create mode 100644 test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 825edea4..5f83bec8 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,8 +5,6 @@ using System.Net.Http; using CacheManager.Core; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -32,12 +30,14 @@ using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; +using Ocelot.Middleware; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; namespace Ocelot.DependencyInjection { @@ -59,6 +59,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = GetIdentityServerConfiguration(); @@ -107,7 +108,7 @@ namespace Ocelot.DependencyInjection services.AddLogging(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/BaseUrlFinder.cs b/src/Ocelot/Middleware/BaseUrlFinder.cs new file mode 100644 index 00000000..da1fda4e --- /dev/null +++ b/src/Ocelot/Middleware/BaseUrlFinder.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Hosting; + +namespace Ocelot.Middleware +{ + public class BaseUrlFinder : IBaseUrlFinder + { + private readonly IWebHostBuilder _webHostBuilder; + + public BaseUrlFinder(IWebHostBuilder webHostBuilder) + { + _webHostBuilder = webHostBuilder; + } + + public string Find() + { + var baseSchemeUrlAndPort = _webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + + return string.IsNullOrEmpty(baseSchemeUrlAndPort) ? "http://localhost:5000" : baseSchemeUrlAndPort; + } + } +} diff --git a/src/Ocelot/Middleware/IBaseUrlFinder.cs b/src/Ocelot/Middleware/IBaseUrlFinder.cs new file mode 100644 index 00000000..df54c0fe --- /dev/null +++ b/src/Ocelot/Middleware/IBaseUrlFinder.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Middleware +{ + public interface IBaseUrlFinder + { + string Find(); + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 553b05b8..bf384dd2 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -162,9 +162,9 @@ namespace Ocelot.Middleware if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { - var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); - - var baseSchemeUrlAndPort = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + var urlFinder = (IBaseUrlFinder)builder.ApplicationServices.GetService(typeof(IBaseUrlFinder)); + + var baseSchemeUrlAndPort = urlFinder.Find(); builder.Map(configuration.AdministrationPath, app => { @@ -186,6 +186,7 @@ namespace Ocelot.Middleware }); } } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs new file mode 100644 index 00000000..c0777a92 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Moq; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class BaseUrlFinderTests + { + private readonly BaseUrlFinder _baseUrlFinder; + private readonly Mock _webHostBuilder; + private string _result; + + public BaseUrlFinderTests() + { + _webHostBuilder = new Mock(); + _baseUrlFinder = new BaseUrlFinder(_webHostBuilder.Object); + } + + [Fact] + public void should_find_base_url_based_on_webhostbuilder() + { + this.Given(x => GivenTheWebHostBuilderReturns("http://localhost:7000")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:7000")) + .BDDfy(); + } + + [Fact] + public void should_use_default_base_url() + { + this.Given(x => GivenTheWebHostBuilderReturns("")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:5000")) + .BDDfy(); + } + + private void GivenTheWebHostBuilderReturns(string url) + { + _webHostBuilder + .Setup(x => x.GetSetting(WebHostDefaults.ServerUrlsKey)) + .Returns(url); + } + + private void WhenIFindTheUrl() + { + _result = _baseUrlFinder.Find(); + } + + private void ThenTheUrlIs(string expected) + { + _result.ShouldBe(expected); + } + } +} From a983af35a1b7c85398018a372bece15133c13b77 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 17:47:24 +0000 Subject: [PATCH 22/25] change to catch not modified response and get config working correctly --- README.md | 15 ++++---- .../Creator/FileOcelotConfigurationCreator.cs | 6 ++-- .../IdentityServerConfigurationCreator.cs | 35 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 27 +------------- src/Ocelot/Responder/HttpContextResponder.cs | 6 +++- 5 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs diff --git a/README.md b/README.md index d2316c9a..7f9a410d 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,8 @@ Currently this is the only way to get configuration into Ocelot. } } -Then in your Program.cs you will want to have the following.. +Then in your Program.cs you will want to have the following. This can be changed if you +don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. IWebHostBuilder builder = new WebHostBuilder(); @@ -390,7 +391,7 @@ In this example ttl seconds is set to 15 which means the cache will expire after ## Administration Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated -using bearer tokens that you request from iteself. This support is provided by the amazing IdentityServer +using bearer tokens that you request from iteself. This is provided by the amazing [IdentityServer](https://github.com/IdentityServer/IdentityServer4) project that I have been using for a few years now. Check them out. In order to enable the administration section you need to do a few things. First of all add this to your @@ -403,7 +404,7 @@ to the Ocelot middleware. "AdministrationPath": "/administration" } -This will get the admin area set up but not the authentication. You need to set 3 environmental variables. +This will get the admin area set up but not the authentication. You need to set 3 environmental variables. OCELOT_USERNAME OCELOT_HASH @@ -413,13 +414,13 @@ These need to be the admin username you want to use with Ocelot and the hash and use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to supply username and password. -In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() this technique is based on MS doc I found online TODO find and link... +In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() +this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing) +using SHA256 rather than SHA1. -OK next thing is to get this config into Ocelot... +Now if you went with the configuration options above and want to access the API you can make the following requests. -At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At -least your stuff needs to be hashed! diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 049c0fff..2182070d 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -86,13 +86,13 @@ namespace Ocelot.Configuration.Creator var reRoutes = new List(); - foreach (var reRoute in _options.Value.ReRoutes) + foreach (var reRoute in fileConfiguration.ReRoutes) { - var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); + var ocelotReRoute = await SetUpReRoute(reRoute, fileConfiguration.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } - return new OcelotConfiguration(reRoutes, _options.Value.GlobalConfiguration.AdministrationPath); + return new OcelotConfiguration(reRoutes, fileConfiguration.GlobalConfiguration.AdministrationPath); } private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs new file mode 100644 index 00000000..48819608 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Configuration.Creator +{ + public static class IdentityServerConfigurationCreator + { + public static IdentityServerConfiguration GetIdentityServerConfiguration() + { + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + return new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List { "admin", "openid", "offline_access" }, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + } + } +} diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 5f83bec8..ad2a4a8d 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; @@ -61,7 +60,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); - var identityServerConfiguration = GetIdentityServerConfiguration(); + var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); if(identityServerConfiguration != null) { @@ -142,29 +141,5 @@ namespace Ocelot.DependencyInjection return services; } - - private static IdentityServerConfiguration GetIdentityServerConfiguration() - { - var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); - var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); - var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); - - return new IdentityServerConfiguration( - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List - { - new User("admin", username, hash, salt) - } - ); - } } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 40b60c30..20313e8f 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -54,7 +55,10 @@ namespace Ocelot.Responder using (Stream stream = new MemoryStream(content)) { - await stream.CopyToAsync(context.Response.Body); + if (response.StatusCode != HttpStatusCode.NotModified) + { + await stream.CopyToAsync(context.Response.Body); + } } } From c2f98f0d69def5f938a812c59e8e2e490a357995 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 18:06:38 +0000 Subject: [PATCH 23/25] final changes to readme and added postman scripts --- Ocelot.sln | 1 + README.md | 76 ++++++++++----------- ocelot.postman_collection.json | 117 +++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 37 deletions(-) create mode 100644 ocelot.postman_collection.json diff --git a/Ocelot.sln b/Ocelot.sln index d88020d3..9a34aed2 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md + ocelot.postman_collection.json = ocelot.postman_collection.json README.md = README.md release.ps1 = release.ps1 ReleaseNotes.md = ReleaseNotes.md diff --git a/README.md b/README.md index 7f9a410d..d72f6b01 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,45 @@ This means that when Ocelot tries to match the incoming upstream url with an ups evaluation will be case sensitive. This setting defaults to false so only set it if you want the ReRoute to be case sensitive is my advice! +## Administration + +Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated +using bearer tokens that you request from iteself. This is provided by the amazing [IdentityServer](https://github.com/IdentityServer/IdentityServer4) +project that I have been using for a few years now. Check them out. + +In order to enable the administration section you need to do a few things. First of all add this to your +initial configuration.json. The value can be anything you want and it is obviously reccomended don't use +a url you would like to route through with Ocelot as this will not work. The administration uses the +MapWhen functionality of asp.net core and all requests to root/administration will be sent there not +to the Ocelot middleware. + + "GlobalConfiguration": { + "AdministrationPath": "/administration" + } + +This will get the admin area set up but not the authentication. Please note that this is a very basic approach to +this problem and if needed we can obviously improve on this! + +You need to set 3 environmental variables. + + OCELOT_USERNAME + OCELOT_HASH + OCELOT_SALT + +These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to +use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to +supply username and password. + +In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() +this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing) +using SHA256 rather than SHA1. + +Now if you went with the configuration options above and want to access the API you can use the postman scripts +called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these +will need to be changed if you are running Ocelot on a different url to http://localhost:5000. + +The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST +a configuration. ## Service Discovery @@ -388,43 +427,6 @@ In orde to use caching on a route in your ReRoute configuration add this setting In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. -## Administration - -Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated -using bearer tokens that you request from iteself. This is provided by the amazing [IdentityServer](https://github.com/IdentityServer/IdentityServer4) -project that I have been using for a few years now. Check them out. - -In order to enable the administration section you need to do a few things. First of all add this to your -initial configuration.json. The value can be anything you want and it is obviously reccomended don't use -a url you would like to route through with Ocelot as this will not work. The administration uses the -MapWhen functionality of asp.net core and all requests to root/administration will be sent there not -to the Ocelot middleware. - - "GlobalConfiguration": { - "AdministrationPath": "/administration" - } - -This will get the admin area set up but not the authentication. You need to set 3 environmental variables. - - OCELOT_USERNAME - OCELOT_HASH - OCELOT_SALT - -These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to -use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to -supply username and password. - -In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() -this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing) -using SHA256 rather than SHA1. - -Now if you went with the configuration options above and want to access the API you can make the following requests. - - - - - - ## Ocelot Middleware injection and overrides Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware diff --git a/ocelot.postman_collection.json b/ocelot.postman_collection.json new file mode 100644 index 00000000..1da66b38 --- /dev/null +++ b/ocelot.postman_collection.json @@ -0,0 +1,117 @@ +{ + "id": "23a49657-e24b-b967-7ec0-943ff1368680", + "name": "Ocelot Admin", + "description": "", + "order": [ + "59162efa-27ce-c230-f523-81d31ead603d", + "e0defe09-c1b2-9e95-8237-67df4bbab284", + "30007c41-565c-5b87-ea34-42170dd386d7" + ], + "folders": [], + "timestamp": 1488042899799, + "owner": "212120", + "public": false, + "requests": [ + { + "id": "30007c41-565c-5b87-ea34-42170dd386d7", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1487515927978, + "name": "POST http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "isFromCollection": true, + "collectionRequestId": "59162efa-27ce-c230-f523-81d31ead603d" + }, + { + "id": "59162efa-27ce-c230-f523-81d31ead603d", + "headers": "Authorization: Bearer {{AccessToken}}\nContent-Type: application/json\n", + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1488044268493, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "rawModeData": "{\n \"reRoutes\": [\n {\n \"downstreamPathTemplate\": \"/\",\n \"upstreamPathTemplate\": \"/identityserverexample\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": \"IdentityServer\",\n \"providerRootUrl\": \"http://localhost:52888\",\n \"scopeName\": \"api\",\n \"requireHttps\": false,\n \"additionalScopes\": [\n \"openid\",\n \"offline_access\"\n ],\n \"scopeSecret\": \"secret\"\n },\n \"addHeadersToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"addClaimsToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"routeClaimsRequirement\": {\n \"UserType\": \"registered\"\n },\n \"addQueriesToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"requestIdKey\": \"OcRequestId\",\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"localhost\",\n \"downstreamPort\": 52876,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/\",\n \"upstreamPathTemplate\": \"/posts\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"www.bbc.co.uk\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}/comments\",\n \"upstreamPathTemplate\": \"/posts/{postId}/comments\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/comments\",\n \"upstreamPathTemplate\": \"/comments\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts\",\n \"upstreamPathTemplate\": \"/posts\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Patch\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products\",\n \"upstreamPathTemplate\": \"/products\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 0,\n \"durationOfBreak\": 0,\n \"timeoutValue\": 0\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products\",\n \"upstreamPathTemplate\": \"/products\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers\",\n \"upstreamPathTemplate\": \"/customers\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers\",\n \"upstreamPathTemplate\": \"/customers\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts\",\n \"upstreamPathTemplate\": \"/posts/\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n }\n ],\n \"globalConfiguration\": {\n \"requestIdKey\": \"OcRequestId\",\n \"serviceDiscoveryProvider\": {\n \"provider\": null,\n \"host\": null,\n \"port\": 0\n },\n \"administrationPath\": \"/admin\"\n }\n}" + }, + { + "id": "e0defe09-c1b2-9e95-8237-67df4bbab284", + "headers": "", + "url": "http://localhost:5000/admin/connect/token", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1487515922748, + "name": "POST http://localhost:5000/admin/connect/token", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "rawModeData": null, + "descriptionFormat": null, + "isFromCollection": true, + "collectionRequestId": "e23e29a1-6abb-abd3-141a-f2202e3f582b" + } + ] +} \ No newline at end of file From 56971836666f369f06ba2651517f61feeca6e77d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 19:20:09 +0000 Subject: [PATCH 24/25] updated readme --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 885c556f..4e765516 100644 --- a/README.md +++ b/README.md @@ -119,19 +119,25 @@ Then in your Program.cs you will want to have the following. This can be changed don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. ```csharp -IWebHostBuilder builder = new WebHostBuilder(); + public class Program + { + public static void Main(string[] args) + { + IWebHostBuilder builder = new WebHostBuilder(); -builder.ConfigureServices(s => { - s.AddSingleton(builder); -}); + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); -builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); -var host = builder.Build(); + var host = builder.Build(); -host.Run(); + host.Run(); + } + } ``` Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot From b09be0571f2b2c40880767d1449c10dcb6766806 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 19:25:45 +0000 Subject: [PATCH 25/25] moved codescene thing as quite big --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e765516..642f843f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) - Attempt at a .NET Api Gateway This project is aimed at people using .NET running @@ -540,6 +538,8 @@ forwarded to the downstream service. Obviously this would break everything :( and doesnt check the response is OK. I think the fact you can even call stuff that isnt available is annoying. Let alone it be null. +[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) + ## Coming up You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1)