From 57580afa7448c4a1c0260672a48f82b336684882 Mon Sep 17 00:00:00 2001 From: Thiago Loureiro Date: Mon, 20 May 2019 16:25:44 +0800 Subject: [PATCH] Release/13.6.0 (#895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed Format Issue for Kubernetes ServiceDiscoveryProvider * Fixes broken links (#858) * Fix link to issue 262 * Fixes broken link to issue 340 * Fixed broken link to issue 340 (#857) * Update information for Okta Authorization (#853) * +dynamic claim variables (#855) incl. tests * IOcelotPipelineBuilder.Use(): Return IOcelotPipelineBuilder (#875) Fixes ThreeMammals/Ocelot#685 * Fix UpstreamHost checking when reroutes duplicate validation (#864) * Format json in reame (#877) Format json file in AdministrationApi ReadMe * kubernetes use in cluster (#882) * refactor :kubernetes use in cluster * feat:delete KubeClient * add more flexible method to config ocelot pipeline (#880) * update k8s doc & samples (#885) * refactor :kubernetes use in cluster * feat:delete KubeClient * feat : update k8s doc & samples * Update kubernetes.rst * Fix/issue666 (#889) * cache key now can generate from query string for request with Get Methods and request content for requests with post methods * MD5Helper Added. OutputCacheMiddleware now can generate cache key using method, url and content * unit test created for CacheKeyGenerator * CacheKeyGenerator Registered in OcelotBuilder as singletone * Fix issue #890 IDefinedAggregator can't handle error codes from downstream requests (#892) * Release/13.2.0 (#834) * Fix formatting in getting started page (#752) * updated release docs (#745) * Update README.md (#756) Fixed typo "Ocleot" * Fixed typo there => their (#763) * Some Typo fixes (#765) * Typo algorythm => algorithm (#764) * Typo querystring => query string (#766) * Typo usual => usually (#767) * Typos (#768) * kubernetes provider (#772) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * fix issue #661 for Advanced aggregations (#704) * Add Advanced Aggregation Feature * fix overwrite error * distinct data for better performance * remove constructor parameter * fix tests issue * fix tests * fix tests issue * Add UnitTest and AcceptanceTest * fix responseKeys typo * Update SimpleJsonResponseAggregator.cs * change port * Fix code example for SSL Errors (#780) DangerousAcceptAnyServerCertificateValidator has to be set to "true" to disable certification validation, not "false". * Changed wording for ease of reading (#776) Just some wording changes for clarification. * Ignore response content if null (fix #785) (#786) * fix bug #791 (#795) * Update loadbalancer.rst (#796) * UriBuilder - remove leading question mark #747 (#794) * Update qualityofservice.rst (#801) Tiny typo * K8s package (#804) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * Okta integration (#807) Okta integration * update cliamsParser (#798) * update cliamsParser * update using * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization (#790) * IOcelotBuilder opens the IMvcCoreBuilder property for easy customization * Adjustment code * nuget package (#809) * feat: Kubernetes ServiceDiscoveryProvider * 编写k8s测试例子 * feat:fix kube config * feat: remove port * feat : complete the k8s test * feat : add kubeserviceDiscovery test * feat : add kube provider unittest * feat :add kubetnetes docs how to use ocelot with kubetnetes docs * keep the configuration as simple as possible, no qos, no cache * fix: use http * add PollingKubeServiceDiscovery * feat : refactor logger * feat : add pollkube docs * feat:Remove unnecessary code * feat : code-block json * feat: publish package Ocelot.Provider.Kubernetes * feat : nuget package * fix: Namesapce Spelling wrong * fix:Namesapce Spelling Wrong * Fix: errors when using rate limiting (#811) * Fix: errors when using rate limiting Add: QuotaExceededError class for requesting too much Add: QuotaExceededError error code Add: Add an error when limit is reached Reflact: Extract GetResponseMessage method for getting default or configured response message for requ * Fix: modify check_we_have_considered_all_errors_in_these_tests for adding a new OcelotErrorCode * added missing COPY csproj files (#821) * Add note on In-Process hosting (#816) When using ASP.NET Core 2.2 with In-Process hosting in IIS it's important to use .UseIIS() instead of .UseIISIntegration(). * Fix bug: (#810) If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Fixed Dockerfile (missing Kubernetes) * Revert "Fix bug: (#810)" (#823) This reverts commit 19c80afb05290fac3a144f652cd663c8b513a559. * remove duplicate `IHttpRequester` register (#819) * remove duplicate `IHttpRequester` register * reserve the first * fix HttpRequesterMiddleware does not call next bug (#830) call next so that we can do something with the response, such as add some custom header etc... * Removed Packing to fix issues, will be sorted out after create a nuget package on Nuget.Org (#831) * Allows access to unpass node (#825) * Fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * fix bug: If the registered Consul node is unexpectedly down and not restarted immediately, other services should continue to find the registered service. * Updated FluentValidations Nuget Package (#833) * Removed Warnings * Make the full DownstreamContext available to user defined aggregators This allows error codes to be handled --- README.md | 1 + docs/features/authentication.rst | 47 +++++++++----- docs/features/kubernetes.rst | 33 +++++++--- docs/features/routing.rst | 2 +- docs/features/servicediscovery.rst | 4 +- samples/AdministrationApi/README.md | 4 +- .../OelotKube/ApiGateway/ApiGateway.csproj | 7 +- samples/OelotKube/ApiGateway/Dockerfile | 8 +-- samples/OelotKube/OelotKube.sln | 16 +---- .../OcelotBuilderExtensions.cs | 4 ++ .../KubeProvider.cs | 5 +- .../KubeRegistryConfiguration.cs | 8 --- .../KubernetesProviderFactory.cs | 8 +-- .../Ocelot.Provider.Kubernetes.csproj | 8 ++- .../OcelotBuilderExtensions.cs | 7 +- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 64 ++++++++++++++++--- src/Ocelot/Authorisation/IClaimsAuthoriser.cs | 10 ++- .../Middleware/AuthorisationMiddleware.cs | 4 +- src/Ocelot/Cache/CacheKeyGenerator.cs | 19 ++++++ src/Ocelot/Cache/ICacheKeyGenerator.cs | 7 ++ src/Ocelot/Cache/MD5Helper.cs | 22 +++++++ .../Cache/Middleware/OutputCacheMiddleware.cs | 14 ++-- .../FileConfigurationFluentValidator.cs | 2 +- .../DependencyInjection/OcelotBuilder.cs | 1 + .../Multiplexer/IDefinedAggregator.cs | 2 +- .../UserDefinedResponseAggregator.cs | 3 +- .../Middleware/OcelotMiddlewareExtensions.cs | 40 +++++++++--- .../Pipeline/IOcelotPipelineBuilder.cs | 2 +- .../Pipeline/OcelotPipelineBuilder.cs | 2 +- .../Request/Middleware/DownstreamRequest.cs | 3 + test/Ocelot.AcceptanceTests/AggregateTests.cs | 8 +-- .../AuthorisationMiddlewareTests.cs | 12 +++- .../Authorization/ClaimsAuthoriserTests.cs | 52 ++++++++++++++- .../Cache/CacheKeyGeneratorTests.cs | 34 ++++++++++ .../Cache/OutputCacheMiddlewareTests.cs | 5 +- .../OutputCacheMiddlewareRealCacheTests.cs | 9 ++- .../FileConfigurationFluentValidatorTests.cs | 2 +- .../KubeServiceDiscoveryProviderTests.cs | 20 ++++-- .../UserDefinedResponseAggregatorTests.cs | 8 +-- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 4 ++ 40 files changed, 383 insertions(+), 128 deletions(-) create mode 100644 src/Ocelot/Cache/CacheKeyGenerator.cs create mode 100644 src/Ocelot/Cache/ICacheKeyGenerator.cs create mode 100644 src/Ocelot/Cache/MD5Helper.cs create mode 100644 test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs diff --git a/README.md b/README.md index 4c69c3cc..222d9abc 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Request Aggregation * Service Discovery with Consul & Eureka * Service Fabric +* Kubernetes * WebSockets * Authentication * Authorisation diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index cb2ec08d..ba230a81 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -138,26 +138,39 @@ Then map the authentication provider key to a ReRoute in your configuration e.g. Okta ^^^^ -Add nuget package : `"Okta.AspNetCore" https://www.nuget.org/packages/Okta.AspNetCore/`_ +Add the following to your startup Configure method: -In a StartUp.cs file add to a method Configure next lines: -app.UseAuthentication(); -app.UseOcelot().Wait(); +.. code-block:: csharp -In a StartUp.cs file add to a method ConfigureServices lines: + app + .UseAuthentication() + .UseOcelot() + .Wait(); + + +Add the following, at minimum, to your startup ConfigureServices method: + +.. code-block:: csharp + + services + .AddAuthentication() + .AddJwtBearer(oktaProviderKey, options => + { + options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience + options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} + }); + services.AddOcelot(configuration); + + +NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope" + + +.. code-block:: csharp + + // Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); -services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme; - options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme; - options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme; - }) - .AddOktaWebApi(new OktaWebApiOptions - { - OktaDomain = _cfg["Okta:OktaDomain"] - - }); -services.AddOcelot(_cfg); `Issue 446 `_ that contains some code and examples that might help with Okta integration. diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst index 289bb211..9f8c8f9f 100644 --- a/docs/features/kubernetes.rst +++ b/docs/features/kubernetes.rst @@ -14,11 +14,22 @@ Then add the following to your ConfigureServices method. s.AddOcelot() .AddKubernetes(); -If you have services deployed in kubernetes you will normally use the naming service to access them. +If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RABC authorization + +.. code-block::csharp + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); + } + +You can replicate a Permissive. Using RBAC role bindings. +`Permissive RBAC Permissions `_, k8s api server and token will read from pod . + +.. code-block::json +kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the -kubernetes service name. We also need to set up the ServiceDiscoveryProvider in -GlobalConfiguration. The example here shows a typical configuration. It assumes kubernetes api server is running on 192.168.0.13 and that api service is on port 443. +kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. .. code-block:: json @@ -43,19 +54,21 @@ GlobalConfiguration. The example here shows a typical configuration. It assumes } } } +Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type. + Note: Host、 Port and Token are no longer in use。 You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. .. code-block:: json -"ServiceDiscoveryProvider": { + "ServiceDiscoveryProvider": { "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "pollkube" - "PollingInterval": 100 -} + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "pollkube" + "PollingInterval": 100 + } The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 9a52fb19..0f2e5efc 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -181,7 +181,7 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!). Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if this sounds interesting to you. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 55d446ab..f36f9f78 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -113,7 +113,7 @@ Ocelot will add this token to the Consul client that it uses to make requests an Eureka ^^^^^^ -This feature was requested as part of `Issue 262 `_ . to add support for Netflix's +This feature was requested as part of `Issue 262 `_ . to add support for Netflix's Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something to do with `Pivotal `_! Anyway enough of the background. @@ -158,7 +158,7 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. An example of this would be calling Ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of the path which is product and use it as a key to look up the service in Consul. If Consul returns a service Ocelot will request it on whatever host and port comes back from Consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. diff --git a/samples/AdministrationApi/README.md b/samples/AdministrationApi/README.md index 4477bf9b..36ed2a67 100644 --- a/samples/AdministrationApi/README.md +++ b/samples/AdministrationApi/README.md @@ -1,3 +1,4 @@ +```json { "reRoutes": [ { @@ -89,4 +90,5 @@ "useProxy": true } } -} \ No newline at end of file +} +``` diff --git a/samples/OelotKube/ApiGateway/ApiGateway.csproj b/samples/OelotKube/ApiGateway/ApiGateway.csproj index aef8c150..0dc4f632 100644 --- a/samples/OelotKube/ApiGateway/ApiGateway.csproj +++ b/samples/OelotKube/ApiGateway/ApiGateway.csproj @@ -10,11 +10,8 @@ - - - - - + + diff --git a/samples/OelotKube/ApiGateway/Dockerfile b/samples/OelotKube/ApiGateway/Dockerfile index 19bd33c2..1e3fcd11 100644 --- a/samples/OelotKube/ApiGateway/Dockerfile +++ b/samples/OelotKube/ApiGateway/Dockerfile @@ -1,12 +1,10 @@ -FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.1-stretch-slim AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.1-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.1-stretch AS build WORKDIR /src COPY ["ApiGateway/ApiGateway.csproj", "ApiGateway/"] -COPY ["../../src/Ocelot/Ocelot.csproj", "../../src/Ocelot/"] -COPY ["../../src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj", "../../src/Ocelot.Provider.Kubernetes/"] RUN dotnet restore "ApiGateway/ApiGateway.csproj" COPY . . WORKDIR "/src/ApiGateway" @@ -18,4 +16,4 @@ RUN dotnet publish "ApiGateway.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . -ENTRYPOINT ["dotnet", "ApiGateway.dll"] +ENTRYPOINT ["dotnet", "ApiGateway.dll"] \ No newline at end of file diff --git a/samples/OelotKube/OelotKube.sln b/samples/OelotKube/OelotKube.sln index aa57e0dd..295fe248 100644 --- a/samples/OelotKube/OelotKube.sln +++ b/samples/OelotKube/OelotKube.sln @@ -1,14 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2048 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot", "..\..\src\Ocelot\Ocelot.csproj", "{E8551073-622E-45FA-AD09-038EB8AAFFBC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Kubernetes", "..\..\src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{EF973868-98A6-4864-BF66-65B5A8C123FE}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DownstreamService", "DownstreamService\DownstreamService.csproj", "{86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}" EndProject Global @@ -21,14 +17,6 @@ Global {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9AFBFD7-EF20-48E5-BB30-5C63C59D7C1C}.Release|Any CPU.Build.0 = Release|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8551073-622E-45FA-AD09-038EB8AAFFBC}.Release|Any CPU.Build.0 = Release|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF973868-98A6-4864-BF66-65B5A8C123FE}.Release|Any CPU.Build.0 = Release|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU {86FFAE3C-648F-4CDE-A260-37C8EBFBF4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs b/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs index d96ce919..e9841890 100644 --- a/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Cache.CacheManager/OcelotBuilderExtensions.cs @@ -33,6 +33,10 @@ builder.Services.RemoveAll(typeof(IOcelotCache)); builder.Services.AddSingleton>(fileConfigCacheManagerOutputCache); builder.Services.AddSingleton>(fileConfigCacheManager); + + builder.Services.RemoveAll(typeof(ICacheKeyGenerator)); + builder.Services.AddSingleton(); + return builder; } } diff --git a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs index 388dfb6a..5baa7e31 100644 --- a/src/Ocelot.Provider.Kubernetes/KubeProvider.cs +++ b/src/Ocelot.Provider.Kubernetes/KubeProvider.cs @@ -15,13 +15,14 @@ namespace Ocelot.Provider.Kubernetes private IOcelotLogger logger; private IKubeApiClient kubeApi; - public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClientFactory kubeClientFactory) + public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClient kubeApi) { this.kubeRegistryConfiguration = kubeRegistryConfiguration; this.logger = factory.CreateLogger(); - this.kubeApi = kubeClientFactory.Get(kubeRegistryConfiguration); + this.kubeApi = kubeApi; } + public async Task> Get() { var service = await kubeApi.ServicesV1() diff --git a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs index 8e5f800e..5c35bbeb 100644 --- a/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs +++ b/src/Ocelot.Provider.Kubernetes/KubeRegistryConfiguration.cs @@ -5,16 +5,8 @@ namespace Ocelot.Provider.Kubernetes { public class KubeRegistryConfiguration { - public Uri ApiEndPoint { get; set; } - public string KubeNamespace { get; set; } public string KeyOfServiceInK8s { get; set; } - - public KubeAuthStrategy AuthStrategy { get; set; } - - public string AccessToken { get; set; } - - public bool AllowInsecure { get; set; } } } diff --git a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs index b0bb35d7..c33839c6 100644 --- a/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs +++ b/src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs @@ -16,18 +16,14 @@ namespace Ocelot.Provider.Kubernetes private static ServiceDiscovery.Providers.IServiceDiscoveryProvider GetkubeProvider(IServiceProvider provider, Configuration.ServiceProviderConfiguration config, string name, IOcelotLoggerFactory factory) { - var kubeClientFactory = provider.GetService(); + var kubeClient = provider.GetService(); var k8sRegistryConfiguration = new KubeRegistryConfiguration() { - ApiEndPoint = new Uri($"https://{config.Host}:{config.Port}"), KeyOfServiceInK8s = name, KubeNamespace = config.Namespace, - AuthStrategy = KubeAuthStrategy.BearerToken, - AccessToken = config.Token, - AllowInsecure = true // Don't validate server certificate }; - var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClientFactory); + var k8sServiceDiscoveryProvider = new Kube(k8sRegistryConfiguration, factory, kubeClient); if (config.Type?.ToLower() == "pollkube") { return new PollKube(config.PollingInterval, factory, k8sServiceDiscoveryProvider); diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj index dbe39207..13a73143 100644 --- a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -25,7 +25,13 @@ - + + + + + + + diff --git a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs index 80772167..f979586a 100644 --- a/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Provider.Kubernetes/OcelotBuilderExtensions.cs @@ -1,14 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; +using KubeClient; +using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; namespace Ocelot.Provider.Kubernetes { public static class OcelotBuilderExtensions { - public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder) + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true) { builder.Services.AddSingleton(KubernetesProviderFactory.Get); - builder.Services.AddSingleton(); + builder.Services.AddKubeClient(usePodServiceAccount); return builder; } } diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 8fd910c8..75a166f2 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,6 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Security.Claims; +using System.Text.RegularExpressions; + +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.Authorisation { @@ -15,8 +22,11 @@ namespace Ocelot.Authorisation _claimsParser = claimsParser; } - public Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) - { + public Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues + ){ foreach (var required in routeClaimsRequirement) { var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); @@ -27,12 +37,50 @@ namespace Ocelot.Authorisation } if (values.Data != null) - { - var authorised = values.Data.Contains(required.Value); - if (!authorised) + { + // dynamic claim + var match = Regex.Match(required.Value, @"^{(?.+)}$"); + if (match.Success) { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + var variableName = match.Captures[0].Value; + + var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray(); + if (matchingPlaceholders.Length == 1) + { + // match + var actualValue = matchingPlaceholders[0].Value; + var authorised = values.Data.Contains(actualValue); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}")); + } + } + else + { + // config error + if (matchingPlaceholders.Length == 0) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p=>p.Name))}")); + } + else + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p=>p.Name.Equals(variableName)).Select(p => p.Value))}")); + } + } + + } + else + { + // static claim + var authorised = values.Data.Contains(required.Value); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + } } } else diff --git a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs index 4abd799e..9f9ce3f8 100644 --- a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/IClaimsAuthoriser.cs @@ -1,5 +1,9 @@ using System.Security.Claims; + +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.Authorisation { @@ -7,6 +11,10 @@ namespace Ocelot.Authorisation public interface IClaimsAuthoriser { - Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement); + Response Authorise( + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues + ); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f3e4e21d..8e906d5c 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -57,8 +57,8 @@ if (IsAuthorisedRoute(context.DownstreamReRoute)) { Logger.LogInformation("route is authorised"); - - var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); + + var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues); if (authorised.IsError) { diff --git a/src/Ocelot/Cache/CacheKeyGenerator.cs b/src/Ocelot/Cache/CacheKeyGenerator.cs new file mode 100644 index 00000000..c33c6104 --- /dev/null +++ b/src/Ocelot/Cache/CacheKeyGenerator.cs @@ -0,0 +1,19 @@ +using System.Text; +using System.Threading.Tasks; +using Ocelot.Middleware; + +namespace Ocelot.Cache { + public class CacheKeyGenerator : ICacheKeyGenerator { + public string GenerateRequestCacheKey(DownstreamContext context) { + string hashedContent = null; + StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"); + if (context.DownstreamRequest.Content != null) { + string requestContentString = Task.Run(async () => await context.DownstreamRequest.Content.ReadAsStringAsync()).Result; + downStreamUrlKeyBuilder.Append(requestContentString); + } + + hashedContent = MD5Helper.GenerateMd5(downStreamUrlKeyBuilder.ToString()); + return hashedContent; + } + } +} diff --git a/src/Ocelot/Cache/ICacheKeyGenerator.cs b/src/Ocelot/Cache/ICacheKeyGenerator.cs new file mode 100644 index 00000000..5a65eb8a --- /dev/null +++ b/src/Ocelot/Cache/ICacheKeyGenerator.cs @@ -0,0 +1,7 @@ +using Ocelot.Middleware; + +namespace Ocelot.Cache { + public interface ICacheKeyGenerator { + string GenerateRequestCacheKey(DownstreamContext context); + } +} diff --git a/src/Ocelot/Cache/MD5Helper.cs b/src/Ocelot/Cache/MD5Helper.cs new file mode 100644 index 00000000..d98bb8c5 --- /dev/null +++ b/src/Ocelot/Cache/MD5Helper.cs @@ -0,0 +1,22 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Ocelot.Cache { + public static class MD5Helper { + public static string GenerateMd5(byte[] contentBytes) { + MD5 md5 = MD5.Create(); + byte[] hash = md5.ComputeHash(contentBytes); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hash.Length; i++) { + sb.Append(hash[i].ToString("X2")); + } + + return sb.ToString(); + } + + public static string GenerateMd5(string contentString) { + byte[] contentBytes = Encoding.Unicode.GetBytes(contentString); + return GenerateMd5(contentBytes); + } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 5b96e79c..925e90c6 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -7,19 +7,23 @@ using Ocelot.Logging; using Ocelot.Middleware; using System.IO; + using System.Text; public class OutputCacheMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; private readonly IOcelotCache _outputCache; + private readonly ICacheKeyGenerator _cacheGeneratot; public OutputCacheMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IOcelotCache outputCache) + IOcelotCache outputCache, + ICacheKeyGenerator cacheGeneratot) :base(loggerFactory.CreateLogger()) { _next = next; _outputCache = outputCache; + _cacheGeneratot = cacheGeneratot; } public async Task Invoke(DownstreamContext context) @@ -31,10 +35,11 @@ } var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"; + string downStreamRequestCacheKey = _cacheGeneratot.GenerateRequestCacheKey(context); Logger.LogDebug($"Started checking cache for {downstreamUrlKey}"); - var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region); + var cached = _outputCache.Get(downStreamRequestCacheKey, context.DownstreamReRoute.CacheOptions.Region); if (cached != null) { @@ -61,12 +66,13 @@ cached = await CreateCachedResponse(context.DownstreamResponse); - _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); + _outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); } - private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response) + private void SetHttpResponseMessageThisRequest(DownstreamContext context, + DownstreamResponse response) { context.DownstreamResponse = response; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 3b1588d2..728c218f 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -122,7 +122,7 @@ { var matchingReRoutes = reRoutes .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate - && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)) + && (r.UpstreamHost == reRoute.UpstreamHost || reRoute.UpstreamHost == null)) .ToList(); if (matchingReRoutes.Count == 1) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 9f81e11d..3fee15e7 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -107,6 +107,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs index b1585165..48234782 100644 --- a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs @@ -5,6 +5,6 @@ namespace Ocelot.Middleware.Multiplexer { public interface IDefinedAggregator { - Task Aggregate(List responses); + Task Aggregate(List responses); } } diff --git a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs index aa44466d..0080b60e 100644 --- a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Ocelot.Configuration; @@ -21,7 +20,7 @@ namespace Ocelot.Middleware.Multiplexer if (!aggregator.IsError) { var aggregateResponse = await aggregator.Data - .Aggregate(downstreamResponses.Select(x => x.DownstreamResponse).ToList()); + .Aggregate(downstreamResponses); originalContext.DownstreamResponse = aggregateResponse; } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 78d32346..a841df5a 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,22 +1,22 @@ namespace Ocelot.Middleware { - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Options; - using System.Diagnostics; using DependencyInjection; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; - using Ocelot.Responses; using Ocelot.Logging; using Ocelot.Middleware.Pipeline; - using Microsoft.Extensions.DependencyInjection; + using Ocelot.Responses; + using System; + using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; public static class OcelotMiddlewareExtensions { @@ -42,6 +42,30 @@ return CreateOcelotPipeline(builder, pipelineConfiguration); } + public static Task UseOcelot(this IApplicationBuilder app, Action builderAction) + => UseOcelot(app, builderAction, new OcelotPipelineConfiguration()); + + public static async Task UseOcelot(this IApplicationBuilder app, Action builderAction, OcelotPipelineConfiguration configuration) + { + await CreateConfiguration(app); // initConfiguration + + ConfigureDiagnosticListener(app); + + var ocelotPipelineBuilder = new OcelotPipelineBuilder(app.ApplicationServices); + builderAction?.Invoke(ocelotPipelineBuilder, configuration ?? new OcelotPipelineConfiguration()); + + var ocelotDelegate = ocelotPipelineBuilder.Build(); + app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + + app.Use(async (context, task) => + { + var downstreamContext = new DownstreamContext(context); + await ocelotDelegate.Invoke(downstreamContext); + }); + + return app; + } + private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); diff --git a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs index 9cb0db56..a10a2369 100644 --- a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs @@ -9,7 +9,7 @@ namespace Ocelot.Middleware.Pipeline public interface IOcelotPipelineBuilder { IServiceProvider ApplicationServices { get; } - OcelotPipelineBuilder Use(Func middleware); + IOcelotPipelineBuilder Use(Func middleware); OcelotRequestDelegate Build(); IOcelotPipelineBuilder New(); } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs index 5877ab62..58f71122 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs @@ -27,7 +27,7 @@ namespace Ocelot.Middleware.Pipeline public IServiceProvider ApplicationServices { get; } - public OcelotPipelineBuilder Use(Func middleware) + public IOcelotPipelineBuilder Use(Func middleware) { _middlewares.Add(middleware); return this; diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 665039d0..49d27efe 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -19,6 +19,7 @@ namespace Ocelot.Request.Middleware Headers = _request.Headers; AbsolutePath = _request.RequestUri.AbsolutePath; Query = _request.RequestUri.Query; + Content = _request.Content; } public HttpRequestHeaders Headers { get; } @@ -37,6 +38,8 @@ namespace Ocelot.Request.Middleware public string Query { get; set; } + public HttpContent Content { get; set; } + public HttpRequestMessage ToHttpRequestMessage() { var uriBuilder = new UriBuilder diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index e3d99854..6de4d8c7 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -646,14 +646,14 @@ namespace Ocelot.AcceptanceTests _dep = dep; } - public async Task Aggregate(List responses) + public async Task Aggregate(List responses) { - var one = await responses[0].Content.ReadAsStringAsync(); - var two = await responses[1].Content.ReadAsStringAsync(); + var one = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); + var two = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var merge = $"{one}, {two}"; merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); - var headers = responses.SelectMany(x => x.Headers).ToList(); + var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); } } diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index a2c2fadd..eaa2441f 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -69,15 +69,21 @@ namespace Ocelot.UnitTests.Authorization private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) + .Setup(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>())) .Returns(expected); } private void ThenTheAuthServiceIsCalledCorrectly() { _authService - .Verify(x => x.Authorise(It.IsAny(), - It.IsAny>()), Times.Once); + .Verify(x => x.Authorise( + It.IsAny(), + It.IsAny>(), + It.IsAny>()) + , Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index e2990864..11fb341d 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -1,7 +1,11 @@ using System.Collections.Generic; using System.Security.Claims; using Ocelot.Authorisation; +using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; + using Shouldly; using TestStack.BDDfy; using Xunit; @@ -15,6 +19,7 @@ namespace Ocelot.UnitTests.Authorization private readonly ClaimsAuthoriser _claimsAuthoriser; private ClaimsPrincipal _claimsPrincipal; private Dictionary _requirement; + private List _urlPathPlaceholderNameAndValues; private Response _result; public ClaimsAuthoriserTests() @@ -38,6 +43,46 @@ namespace Ocelot.UnitTests.Authorization .BDDfy(); } + [Fact] + public void should_authorize_dynamic_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("userid", "14"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + new PlaceholderNameAndValue("{userId}", "14") + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsAuthorised()) + .BDDfy(); + } + + [Fact] + public void should_not_authorize_dynamic_user() + { + this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("userid", "15"), + })))) + .And(x => x.GivenARouteClaimsRequirement(new Dictionary + { + {"userid", "{userId}"} + })) + .And(x => x.GivenAPlaceHolderNameAndValueList(new List + { + new PlaceholderNameAndValue("{userId}", "14") + })) + .When(x => x.WhenICallTheAuthoriser()) + .Then(x => x.ThenTheUserIsntAuthorised()) + .BDDfy(); + } + [Fact] public void should_authorise_user_multiple_claims_of_same_type() { @@ -78,9 +123,14 @@ namespace Ocelot.UnitTests.Authorization _requirement = requirement; } + private void GivenAPlaceHolderNameAndValueList(List urlPathPlaceholderNameAndValues) + { + _urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues; + } + private void WhenICallTheAuthoriser() { - _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement); + _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues); } private void ThenTheUserIsAuthorised() diff --git a/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs new file mode 100644 index 00000000..362fcc9a --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs @@ -0,0 +1,34 @@ +using System.Net.Http; +using Microsoft.AspNetCore.Http; +using Ocelot.Cache; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache { + public class CacheKeyGeneratorTests { + private readonly ICacheKeyGenerator _cacheKeyGenerator; + private readonly DownstreamContext _downstreamContext; + + public CacheKeyGeneratorTests() { + _cacheKeyGenerator = new CacheKeyGenerator(); + _cacheKeyGenerator = new CacheKeyGenerator(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()) { + DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")) + }; + } + + [Fact] + public void should_generate_cache_key_from_context() { + this.Given(x => x.GivenCacheKeyFromContext(_downstreamContext)) + .BDDfy(); + } + + private void GivenCacheKeyFromContext(DownstreamContext context) { + string generatedCacheKey = _cacheKeyGenerator.GenerateRequestCacheKey(context); + string cachekey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); + generatedCacheKey.ShouldBe(cachekey); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 6f32739b..62d972f9 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -27,14 +27,17 @@ private OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private readonly OcelotRequestDelegate _next; + private readonly ICacheKeyGenerator _cacheKeyGenerator; private CachedResponse _response; + public OutputCacheMiddlewareTests() { _cache = new Mock>(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); + _cacheKeyGenerator = new CacheKeyGenerator(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); @@ -89,7 +92,7 @@ private void WhenICallTheMiddleware() { - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object, _cacheKeyGenerator); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } diff --git a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs index 424c8efd..853354ef 100644 --- a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs @@ -23,6 +23,7 @@ public class OutputCacheMiddlewareRealCacheTests { private readonly IOcelotCache _cacheManager; + private readonly ICacheKeyGenerator _cacheKeyGenerator; private readonly OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; @@ -32,17 +33,18 @@ public OutputCacheMiddlewareRealCacheTests() { _loggerFactory = new Mock(); - _logger = new Mock(); + _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => { x.WithDictionaryHandle(); }); _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + _cacheKeyGenerator = new CacheKeyGenerator(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); _next = context => Task.CompletedTask; - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _cacheKeyGenerator); } [Fact] @@ -69,7 +71,8 @@ private void ThenTheContentTypeHeaderIsCached() { - var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); + string cacheKey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); + var result = _cacheManager.Get(cacheKey, "kanken"); var header = result.ContentHeaders["Content-Type"]; header.First().ShouldBe("application/json"); } diff --git a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs index c7ffbb05..a77d7c60 100644 --- a/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/Validation/FileConfigurationFluentValidatorTests.cs @@ -1023,7 +1023,7 @@ Host = "bb.co.uk" } }, - UpstreamHost = "host1" + UpstreamHost = "host2" } } })) diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs index ccf44fcd..d2abded6 100644 --- a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs @@ -1,4 +1,5 @@ -using KubeClient.Models; +using KubeClient; +using KubeClient.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -31,7 +32,7 @@ namespace Ocelot.UnitTests.Kubernetes private readonly Mock _factory; private readonly Mock _logger; private string _receivedToken; - private readonly IKubeApiClientFactory _clientFactory; + private readonly IKubeApiClient _clientFactory; public KubeServiceDiscoveryProviderTests() { @@ -42,15 +43,20 @@ namespace Ocelot.UnitTests.Kubernetes _fakekubeServiceDiscoveryUrl = $"http://{_kubeHost}:{_port}"; _serviceEntries = new ServiceV1(); _factory = new Mock(); - _clientFactory = new KubeApiClientFactory(); + + var option = new KubeClientOptions + { + ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), + AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", + AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, + AllowInsecure = true + }; + + _clientFactory = KubeApiClient.Create(option); _logger = new Mock(); _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); var config = new KubeRegistryConfiguration() { - ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), - AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", - AllowInsecure = true, - AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, KeyOfServiceInK8s = _serviceName, KubeNamespace = _namespaces }; diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs index daef2a2c..4ebb592c 100644 --- a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs @@ -140,12 +140,12 @@ namespace Ocelot.UnitTests.Middleware public class TestDefinedAggregator : IDefinedAggregator { - public async Task Aggregate(List responses) + public async Task Aggregate(List responses) { - var tom = await responses[0].Content.ReadAsStringAsync(); - var laura = await responses[1].Content.ReadAsStringAsync(); + var tom = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); + var laura = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); var content = $"{tom}, {laura}"; - var headers = responses.SelectMany(x => x.Headers).ToList(); + var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers, "some reason"); } } diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index ae11a695..f4222635 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -19,6 +19,10 @@ True + + + +