diff --git a/Ocelot.sln b/Ocelot.sln index 57841966..8557f70a 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26730.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}" EndProject @@ -12,9 +12,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake build.ps1 = build.ps1 - build.readme.md = build.readme.md - configuration-explanation.txt = configuration-explanation.txt - configuration.yaml = configuration.yaml GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md @@ -84,4 +81,7 @@ Global {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48} + EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 2680b631..6f7c7ed3 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,11 @@ The response from the downstream service is stored in a per request scoped repos and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. - + ## How to install Ocelot is designed to work with ASP.NET core only and is currently -built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. +built to netcoreapp2.0 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. Install Ocelot and it's dependencies using NuGet. diff --git a/build.cake b/build.cake index 9960c588..e988060e 100644 --- a/build.cake +++ b/build.cake @@ -1,6 +1,7 @@ #tool "nuget:?package=GitVersion.CommandLine" #tool "nuget:?package=GitReleaseNotes" -#addin "nuget:?package=Cake.Json" +#addin nuget:?package=Cake.Json +#addin nuget:?package=Newtonsoft.Json&version=9.0.1 #tool "nuget:?package=OpenCover" #tool "nuget:?package=ReportGenerator" #tool coveralls.net @@ -260,11 +261,19 @@ Task("ReleasePackagesToUnstableFeed") Task("EnsureStableReleaseRequirements") .Does(() => { + Information("Check if stable release..."); + if (!AppVeyor.IsRunningOnAppVeyor) { throw new Exception("Stable release should happen via appveyor"); } - + + Information("Running on AppVeyor..."); + + Information("IsTag = " + AppVeyor.Environment.Repository.Tag.IsTag); + + Information("Name = " + AppVeyor.Environment.Repository.Tag.Name); + var isTag = AppVeyor.Environment.Repository.Tag.IsTag && !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); @@ -273,6 +282,8 @@ Task("EnsureStableReleaseRequirements") { throw new Exception("Stable release should happen from a published GitHub release"); } + + Information("Release is stable..."); }); Task("UpdateVersionInfo") @@ -287,19 +298,48 @@ Task("DownloadGitHubReleaseArtifacts") .IsDependentOn("UpdateVersionInfo") .Does(() => { - EnsureDirectoryExists(packagesDir); + try + { + Information("DownloadGitHubReleaseArtifacts"); - var releaseUrl = tagsUrl + releaseTag; - var assets_url = ParseJson(GetResource(releaseUrl)) - .GetValue("assets_url") - .Value(); + EnsureDirectoryExists(packagesDir); - foreach(var asset in DeserializeJson(GetResource(assets_url))) - { - var file = packagesDir + File(asset.Value("name")); - Information("Downloading " + file); - DownloadFile(asset.Value("browser_download_url"), file); - } + Information("Directory exists..."); + + var releaseUrl = tagsUrl + releaseTag; + + Information("Release url " + releaseUrl); + + //var releaseJson = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)); + + var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) + .GetValue("assets_url") + .Value(); + + Information("Assets url " + assets_url); + + var assets = GetResource(assets_url); + + Information("Assets " + assets_url); + + foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject(assets)) + { + Information("In the loop.."); + + var file = packagesDir + File(asset.Value("name")); + + Information("Downloading " + file); + + DownloadFile(asset.Value("browser_download_url"), file); + } + + Information("Out of the loop..."); + } + catch(Exception exception) + { + Information("There was an exception " + exception); + throw; + } }); Task("ReleasePackagesToStableFeed") @@ -394,19 +434,31 @@ private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFi /// gets the resource from the specified url private string GetResource(string url) { - Information("Getting resource from " + url); + try + { + Information("Getting resource from " + url); - var assetsRequest = System.Net.WebRequest.CreateHttp(url); - assetsRequest.Method = "GET"; - assetsRequest.Accept = "application/vnd.github.v3+json"; - assetsRequest.UserAgent = "BuildScript"; + var assetsRequest = System.Net.WebRequest.CreateHttp(url); + assetsRequest.Method = "GET"; + assetsRequest.Accept = "application/vnd.github.v3+json"; + assetsRequest.UserAgent = "BuildScript"; - using (var assetsResponse = assetsRequest.GetResponse()) - { - var assetsStream = assetsResponse.GetResponseStream(); - var assetsReader = new StreamReader(assetsStream); - return assetsReader.ReadToEnd(); - } + using (var assetsResponse = assetsRequest.GetResponse()) + { + var assetsStream = assetsResponse.GetResponseStream(); + var assetsReader = new StreamReader(assetsStream); + var response = assetsReader.ReadToEnd(); + + Information("Response is " + response); + + return response; + } + } + catch(Exception exception) + { + Information("There was an exception " + exception); + throw; + } } private bool ShouldPublishToUnstableFeed(string filter, string branchName) diff --git a/build.ps1 b/build.ps1 index 44de5793..265bd123 100644 --- a/build.ps1 +++ b/build.ps1 @@ -21,34 +21,35 @@ The build script target to run. The build configuration to use. .PARAMETER Verbosity Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. .PARAMETER Experimental -Tells Cake to use the latest Roslyn release. -.PARAMETER WhatIf -Performs a dry run of the build script. -No tasks will be executed. +Uses the nightly builds of the Roslyn script engine. .PARAMETER Mono -Tells Cake to use the Mono scripting engine. +Uses the Mono Compiler rather than the Roslyn script engine. .PARAMETER SkipToolPackageRestore Skips restoring of packages. .PARAMETER ScriptArgs Remaining arguments are added here. .LINK -http://cakebuild.net +https://cakebuild.net #> [CmdletBinding()] Param( [string]$Script = "build.cake", - [string]$Target = "Default", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", + [string]$Target, + [string]$Configuration, [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity = "Verbose", + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, [switch]$Experimental, - [Alias("DryRun","Noop")] - [switch]$WhatIf, [switch]$Mono, [switch]$SkipToolPackageRestore, [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] @@ -80,6 +81,15 @@ function MD5HashFile([string] $filePath) } } +function GetProxyEnabledWebClient +{ + $wc = New-Object System.Net.WebClient + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.Proxy = $proxy + return $wc +} + Write-Host "Preparing to run build script..." if(!$PSScriptRoot){ @@ -87,31 +97,15 @@ if(!$PSScriptRoot){ } $TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" +$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" - -# Should we use mono? -$UseMono = ""; -if($Mono.IsPresent) { - Write-Verbose -Message "Using the Mono based scripting engine." - $UseMono = "-mono" -} - -# Should we use the new Roslyn? -$UseExperimental = ""; -if($Experimental.IsPresent -and !($Mono.IsPresent)) { - Write-Verbose -Message "Using experimental version of Roslyn." - $UseExperimental = "-experimental" -} - -# Is this a dry run? -$UseDryRun = ""; -if($WhatIf.IsPresent) { - $UseDryRun = "-dryrun" -} +$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" +$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" # Make sure tools folder exists if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { @@ -121,8 +115,10 @@ if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { # Make sure that packages.config exist. if (!(Test-Path $PACKAGES_CONFIG)) { - Write-Verbose -Message "Downloading packages.config..." - try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Write-Verbose -Message "Downloading packages.config..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { Throw "Could not download packages.config." } } @@ -130,7 +126,7 @@ if (!(Test-Path $PACKAGES_CONFIG)) { # Try find NuGet.exe in path if not exists if (!(Test-Path $NUGET_EXE)) { Write-Verbose -Message "Trying to find nuget.exe in PATH..." - $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." @@ -142,7 +138,8 @@ if (!(Test-Path $NUGET_EXE)) { if (!(Test-Path $NUGET_EXE)) { Write-Verbose -Message "Downloading NuGet.exe..." try { - (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + $wc = GetProxyEnabledWebClient + $wc.DownloadFile($NUGET_URL, $NUGET_EXE) } catch { Throw "Could not download NuGet.exe." } @@ -175,6 +172,41 @@ if(-Not $SkipToolPackageRestore.IsPresent) { $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" } Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore addins from NuGet +if (Test-Path $ADDINS_PACKAGES_CONFIG) { + Push-Location + Set-Location $ADDINS_DIR + + Write-Verbose -Message "Restoring addins from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet addins." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + + Pop-Location +} + +# Restore modules from NuGet +if (Test-Path $MODULES_PACKAGES_CONFIG) { + Push-Location + Set-Location $MODULES_DIR + + Write-Verbose -Message "Restoring modules from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet modules." + } + + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location } @@ -183,7 +215,20 @@ if (!(Test-Path $CAKE_EXE)) { Throw "Could not find Cake.exe at $CAKE_EXE" } + + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +if ($Mono) { $cakeArguments += "-mono" } +$cakeArguments += $ScriptArgs + # Start Cake Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE \ No newline at end of file +&$CAKE_EXE $cakeArguments +exit $LASTEXITCODE diff --git a/global.json b/global.json index 31a5f7fc..332404a7 100644 --- a/global.json +++ b/global.json @@ -1 +1,6 @@ -{"projects":["src","test"]} \ No newline at end of file +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "2.0.0" + } +} \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs b/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs deleted file mode 100644 index 3cb662b8..00000000 --- a/src/Ocelot/Authentication/Handler/AuthenticationHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ocelot.Authentication.Handler -{ - public class AuthenticationHandler - { - public AuthenticationHandler(string provider, IHandler handler) - { - Provider = provider; - Handler = handler; - } - - public string Provider { get; private set; } - public IHandler Handler { get; private set; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs deleted file mode 100644 index b4c1503a..00000000 --- a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Ocelot.Responses; - -namespace Ocelot.Authentication.Handler.Creator -{ - using Ocelot.Configuration; - - using AuthenticationOptions = Configuration.AuthenticationOptions; - - /// - /// Cannot unit test things in this class due to use of extension methods - /// - public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator - { - public Response Create(IApplicationBuilder app, AuthenticationOptions authOptions) - { - var builder = app.New(); - - if (authOptions.Provider.ToLower() == "jwt") - { - var authenticationConfig = authOptions.Config as JwtConfig; - - builder.UseJwtBearerAuthentication( - new JwtBearerOptions() - { - Authority = authenticationConfig.Authority, - Audience = authenticationConfig.Audience - }); - } - else - { - var authenticationConfig = authOptions.Config as IdentityServerConfig; - - builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions - { - Authority = authenticationConfig.ProviderRootUrl, - ApiName = authenticationConfig.ApiName, - RequireHttpsMetadata = authenticationConfig.RequireHttps, - AllowedScopes = authOptions.AllowedScopes, - SupportedTokens = SupportedTokens.Both, - ApiSecret = authenticationConfig.ApiSecret - }); - } - - var authenticationNext = builder.Build(); - - return new OkResponse(authenticationNext); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs deleted file mode 100644 index 9d92c81d..00000000 --- a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Ocelot.Responses; - -namespace Ocelot.Authentication.Handler.Creator -{ - using AuthenticationOptions = Configuration.AuthenticationOptions; - - public interface IAuthenticationHandlerCreator - { - Response Create(IApplicationBuilder app, AuthenticationOptions authOptions); - } -} diff --git a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs deleted file mode 100644 index 60253816..00000000 --- a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Builder; -using Ocelot.Authentication.Handler.Creator; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Authentication.Handler.Factory -{ - using AuthenticationOptions = Configuration.AuthenticationOptions; - - public class AuthenticationHandlerFactory : IAuthenticationHandlerFactory - { - private readonly IAuthenticationHandlerCreator _creator; - - public AuthenticationHandlerFactory(IAuthenticationHandlerCreator creator) - { - _creator = creator; - } - - public Response Get(IApplicationBuilder app, AuthenticationOptions authOptions) - { - var handler = _creator.Create(app, authOptions); - - if (!handler.IsError) - { - return new OkResponse( - new AuthenticationHandler(authOptions.Provider, new RequestDelegateHandler(handler.Data))); - } - - return new ErrorResponse(new List - { - new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for {authOptions.Provider}") - }); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs deleted file mode 100644 index abc09ed8..00000000 --- a/src/Ocelot/Authentication/Handler/Factory/IAuthenticationHandlerFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Responses; - -namespace Ocelot.Authentication.Handler.Factory -{ - using AuthenticationOptions = Configuration.AuthenticationOptions; - - public interface IAuthenticationHandlerFactory - { - Response Get(IApplicationBuilder app, AuthenticationOptions authOptions); - } -} diff --git a/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs b/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs deleted file mode 100644 index 7e18b203..00000000 --- a/src/Ocelot/Authentication/Handler/Factory/UnableToCreateAuthenticationHandlerError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Authentication.Handler.Factory -{ - public class UnableToCreateAuthenticationHandlerError : Error - { - public UnableToCreateAuthenticationHandlerError(string message) - : base(message, OcelotErrorCode.UnableToCreateAuthenticationHandlerError) - { - } - } -} diff --git a/src/Ocelot/Authentication/Handler/IHandler.cs b/src/Ocelot/Authentication/Handler/IHandler.cs deleted file mode 100644 index 99d240e8..00000000 --- a/src/Ocelot/Authentication/Handler/IHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Ocelot.Authentication.Handler -{ - public interface IHandler - { - Task Handle(HttpContext context); - } -} \ No newline at end of file diff --git a/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs b/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs deleted file mode 100644 index 291e8ec3..00000000 --- a/src/Ocelot/Authentication/Handler/RequestDelegateHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Ocelot.Authentication.Handler -{ - public class RequestDelegateHandler : IHandler - { - private readonly RequestDelegate _requestDelegate; - - public RequestDelegateHandler(RequestDelegate requestDelegate) - { - _requestDelegate = requestDelegate; - } - - public async Task Handle(HttpContext context) - { - await _requestDelegate.Invoke(context); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs b/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs deleted file mode 100644 index 6aca01be..00000000 --- a/src/Ocelot/Authentication/JsonConverters/AuthenticationConfigConverter.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Ocelot.Configuration; - -namespace Ocelot.Authentication.JsonConverters -{ - public class AuthenticationConfigConverter : JsonConverter - { - public override bool CanWrite => false; - - public override bool CanRead => true; - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("Use default serialization."); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jsonObject = JObject.Load(reader); - var setting = default(IAuthenticationConfig); - - if (jsonObject["Provider"] != null) - { - switch (jsonObject["Provider"].Value()) - { - case "Jwt": - setting = new JwtConfig( - jsonObject["Authority"].Value(), - jsonObject["Audience"].Value()); - break; - - default: - setting = new IdentityServerConfig( - jsonObject["ProviderRootUrl"].Value(), - jsonObject["ApiName"].Value(), - jsonObject["RequireHttps"].Value(), - jsonObject["ApiSecret"].Value()); - break; - } - } - else - { - setting = new IdentityServerConfig(string.Empty, string.Empty, false, string.Empty); - } - - serializer.Populate(jsonObject.CreateReader(), setting); - return setting; - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(IAuthenticationConfig); - } - } - - -} diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index f6ecd654..1d820600 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Ocelot.Authentication.Handler.Factory; using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.Infrastructure.Extensions; @@ -16,18 +16,16 @@ namespace Ocelot.Authentication.Middleware { private readonly RequestDelegate _next; private readonly IApplicationBuilder _app; - private readonly IAuthenticationHandlerFactory _authHandlerFactory; + private readonly IAuthenticationSchemeProvider _authSchemeProvider; private readonly IOcelotLogger _logger; public AuthenticationMiddleware(RequestDelegate next, IApplicationBuilder app, IRequestScopedDataRepository requestScopedDataRepository, - IAuthenticationHandlerFactory authHandlerFactory, IOcelotLoggerFactory loggerFactory) : base(requestScopedDataRepository) { _next = next; - _authHandlerFactory = authHandlerFactory; _app = app; _logger = loggerFactory.CreateLogger(); } @@ -37,18 +35,10 @@ namespace Ocelot.Authentication.Middleware if (IsAuthenticatedRoute(DownstreamRoute.ReRoute)) { _logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - - var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions); - - if (authenticationHandler.IsError) - { - _logger.LogError($"Error getting authentication handler for {context.Request.Path}. {authenticationHandler.Errors.ToErrorString()}"); - SetPipelineError(authenticationHandler.Errors); - return; - } - - await authenticationHandler.Data.Handler.Handle(context); - + + var result = await context.AuthenticateAsync(DownstreamRoute.ReRoute.AuthenticationOptions.AuthenticationProviderKey); + + context.User = result.Principal; if (context.User.Identity.IsAuthenticated) { @@ -65,7 +55,6 @@ namespace Ocelot.Authentication.Middleware _logger.LogError($"Client has NOT been authenticated for {context.Request.Path} and pipeline error set. {error.ToErrorString()}"); SetPipelineError(error); - return; } } else diff --git a/src/Ocelot/Configuration/AuthenticationOptions.cs b/src/Ocelot/Configuration/AuthenticationOptions.cs index 1c71d68b..21e53ad7 100644 --- a/src/Ocelot/Configuration/AuthenticationOptions.cs +++ b/src/Ocelot/Configuration/AuthenticationOptions.cs @@ -2,52 +2,15 @@ namespace Ocelot.Configuration { - using Newtonsoft.Json; - public class AuthenticationOptions { - public AuthenticationOptions(string provider, List allowedScopes, IAuthenticationConfig config) + public AuthenticationOptions(List allowedScopes, string authenticationProviderKey) { - Provider = provider; AllowedScopes = allowedScopes; - Config = config; + AuthenticationProviderKey = authenticationProviderKey; } - public string Provider { get; private set; } - public List AllowedScopes { get; private set; } - - public IAuthenticationConfig Config { get; private set; } + public string AuthenticationProviderKey { get; private set; } } - - public class IdentityServerConfig : IAuthenticationConfig - { - public IdentityServerConfig(string providerRootUrl, string apiName, bool requireHttps, string apiSecret) - { - ProviderRootUrl = providerRootUrl; - ApiName = apiName; - RequireHttps = requireHttps; - ApiSecret = apiSecret; - } - - public string ProviderRootUrl { get; private set; } - public string ApiName { get; private set; } - public string ApiSecret { get; private set; } - public bool RequireHttps { get; private set; } - } - - public class JwtConfig : IAuthenticationConfig - { - public JwtConfig(string authority, string audience) - { - Audience = audience; - Authority = authority; - } - - public string Audience { get; } - - public string Authority { get; } - } - - public interface IAuthenticationConfig {} } diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs index ea43a23e..8146236e 100644 --- a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs @@ -4,18 +4,8 @@ namespace Ocelot.Configuration.Builder { public class AuthenticationOptionsBuilder { - - private string _provider; - - private List _allowedScopes; - - private IAuthenticationConfig _identityServerConfig; - - public AuthenticationOptionsBuilder WithProvider(string provider) - { - _provider = provider; - return this; - } + private List _allowedScopes = new List(); + private string _authenticationProviderKey; public AuthenticationOptionsBuilder WithAllowedScopes(List allowedScopes) { @@ -23,76 +13,15 @@ namespace Ocelot.Configuration.Builder return this; } - public AuthenticationOptionsBuilder WithConfig(IAuthenticationConfig config) + public AuthenticationOptionsBuilder WithAuthenticationProviderKey(string authenticationProviderKey) { - _identityServerConfig = config; + _authenticationProviderKey = authenticationProviderKey; return this; } public AuthenticationOptions Build() { - return new AuthenticationOptions(_provider, _allowedScopes, _identityServerConfig); - } - } - - public class IdentityServerConfigBuilder - { - private string _providerRootUrl; - private string _apiName; - private string _apiSecret; - private bool _requireHttps; - - public IdentityServerConfigBuilder WithProviderRootUrl(string providerRootUrl) - { - _providerRootUrl = providerRootUrl; - return this; - } - - public IdentityServerConfigBuilder WithApiName(string apiName) - { - _apiName = apiName; - return this; - } - - public IdentityServerConfigBuilder WithApiSecret(string apiSecret) - { - _apiSecret = apiSecret; - return this; - } - - public IdentityServerConfigBuilder WithRequireHttps(bool requireHttps) - { - _requireHttps = requireHttps; - return this; - } - - public IdentityServerConfig Build() - { - return new IdentityServerConfig(_providerRootUrl, _apiName, _requireHttps, _apiSecret); - } - } - - public class JwtConfigBuilder - { - public string _authority; - - public string _audience; - - public JwtConfigBuilder WithAuthority(string authority) - { - _authority = authority; - return this; - } - - public JwtConfigBuilder WithAudience(string audience) - { - _audience = audience; - return this; - } - - public JwtConfig Build() - { - return new JwtConfig(_authority, _audience); + return new AuthenticationOptions(_allowedScopes, _authenticationProviderKey); } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 852c4870..272242c8 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -29,8 +29,10 @@ namespace Ocelot.Configuration.Builder private ServiceProviderConfiguration _serviceProviderConfiguraion; private bool _useQos; private QoSOptions _qosOptions; + private HttpHandlerOptions _httpHandlerOptions; public bool _enableRateLimiting; public RateLimitOptions _rateLimitOptions; + private string _authenticationProviderKey; public ReRouteBuilder WithLoadBalancer(string loadBalancer) { @@ -176,6 +178,17 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithAuthenticationProviderKey(string authenticationProviderKey) + { + _authenticationProviderKey = authenticationProviderKey; + return this; + } + + public ReRouteBuilder WithHttpHandlerOptions(HttpHandlerOptions input) + { + _httpHandlerOptions = input; + return this; + } public ReRoute Build() { @@ -203,7 +216,8 @@ namespace Ocelot.Configuration.Builder _useQos, _qosOptions, _enableRateLimiting, - _rateLimitOptions); + _rateLimitOptions, + _httpHandlerOptions); } } } diff --git a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs index 284ec33c..1e3f9aa0 100644 --- a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs @@ -1,27 +1,12 @@ -using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; -using Ocelot.Creator.Configuration; namespace Ocelot.Configuration.Creator { public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator { - private readonly IAuthenticationProviderConfigCreator _creator; - - public AuthenticationOptionsCreator(IAuthenticationProviderConfigCreator creator) + public AuthenticationOptions Create(FileReRoute reRoute) { - _creator = creator; - } - - public AuthenticationOptions Create(FileReRoute fileReRoute) - { - var authenticationConfig = _creator.Create(fileReRoute.AuthenticationOptions); - - return new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithConfig(authenticationConfig) - .Build(); + return new AuthenticationOptions(reRoute.AuthenticationOptions.AllowedScopes, reRoute.AuthenticationOptions.AuthenticationProviderKey); } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs deleted file mode 100644 index c7a25799..00000000 --- a/src/Ocelot/Configuration/Creator/AuthenticationProviderConfigCreator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Ocelot.Creator.Configuration; - -namespace Ocelot.Configuration.Creator -{ - using Ocelot.Configuration.Builder; - using Ocelot.Configuration.File; - - public class AuthenticationProviderConfigCreator : IAuthenticationProviderConfigCreator - { - public IAuthenticationConfig Create(FileAuthenticationOptions authenticationOptions) - { - if (authenticationOptions.Provider?.ToLower() == "jwt") - { - return CreateJwt(authenticationOptions); - } - - return CreateIdentityServer(authenticationOptions); - } - - private JwtConfig CreateJwt(FileAuthenticationOptions authenticationOptions) - { - return new JwtConfigBuilder() - .WithAudience(authenticationOptions.JwtConfig?.Audience) - .WithAuthority(authenticationOptions.JwtConfig?.Authority) - .Build(); - } - - private IdentityServerConfig CreateIdentityServer(FileAuthenticationOptions authenticationOptions) - { - return new IdentityServerConfigBuilder() - .WithApiName(authenticationOptions.IdentityServerConfig?.ApiName) - .WithApiSecret(authenticationOptions.IdentityServerConfig?.ApiSecret) - .WithProviderRootUrl(authenticationOptions.IdentityServerConfig?.ProviderRootUrl) - .WithRequireHttps(authenticationOptions.IdentityServerConfig.RequireHttps).Build(); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c6f5f4fe..9fe77bbf 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -38,6 +38,7 @@ namespace Ocelot.Configuration.Creator private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; private readonly IRegionCreator _regionCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -55,7 +56,8 @@ namespace Ocelot.Configuration.Creator IQoSOptionsCreator qosOptionsCreator, IReRouteOptionsCreator fileReRouteOptionsCreator, IRateLimitOptionsCreator rateLimitOptionsCreator, - IRegionCreator regionCreator + IRegionCreator regionCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator ) { _regionCreator = regionCreator; @@ -74,6 +76,7 @@ namespace Ocelot.Configuration.Creator _serviceProviderConfigCreator = serviceProviderConfigCreator; _qosOptionsCreator = qosOptionsCreator; _fileReRouteOptionsCreator = fileReRouteOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; } public async Task> Create() @@ -92,7 +95,7 @@ namespace Ocelot.Configuration.Creator private async Task SetUpConfiguration(FileConfiguration fileConfiguration) { - var response = _configurationValidator.IsValid(fileConfiguration); + var response = await _configurationValidator.IsValid(fileConfiguration); if (response.Data.IsError) { @@ -143,6 +146,8 @@ namespace Ocelot.Configuration.Creator var region = _regionCreator.Create(fileReRoute); + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); + var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) @@ -168,6 +173,7 @@ namespace Ocelot.Configuration.Creator .WithQosOptions(qosOptions) .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) .WithRateLimitOptions(rateLimitOption) + .WithHttpHandlerOptions(httpHandlerOptions) .Build(); await SetupLoadBalancer(reRoute); @@ -178,7 +184,7 @@ namespace Ocelot.Configuration.Creator private string CreateReRouteKey(FileReRoute fileReRoute) { //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain - var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; + var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}"; return loadBalancerKey; } diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs new file mode 100644 index 00000000..52aa7694 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -0,0 +1,13 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator + { + public HttpHandlerOptions Create(FileReRoute fileReRoute) + { + return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect, + fileReRoute.HttpHandlerOptions.UseCookieContainer); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs index e5e82ca8..b7ea8141 100644 --- a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs @@ -1,9 +1,10 @@ +using System.Collections.Generic; using Ocelot.Configuration.File; namespace Ocelot.Configuration.Creator { public interface IAuthenticationOptionsCreator { - AuthenticationOptions Create(FileReRoute fileReRoute); + AuthenticationOptions Create(FileReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IHttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IHttpHandlerOptionsCreator.cs new file mode 100644 index 00000000..34e5ffd1 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IHttpHandlerOptionsCreator.cs @@ -0,0 +1,12 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + /// + /// Describes creation of HttpHandlerOptions + /// + public interface IHttpHandlerOptionsCreator + { + HttpHandlerOptions Create(FileReRoute fileReRoute); + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index 21e46932..f98a77c6 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -36,7 +36,7 @@ namespace Ocelot.Configuration.Creator private bool IsAuthenticated(FileReRoute fileReRoute) { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); } private bool IsAuthorised(FileReRoute fileReRoute) diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 95b339a9..4e02a158 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -41,8 +41,8 @@ namespace Ocelot.Configuration.Creator } var route = reRoute.ReRouteIsCaseSensitive - ? $"{upstreamTemplate}{RegExMatchEndString}" - : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; + ? $"^{upstreamTemplate}{RegExMatchEndString}" + : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; return route; } diff --git a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs index 6333993d..81fc9d28 100644 --- a/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs +++ b/src/Ocelot/Configuration/File/FileAuthenticationOptions.cs @@ -7,13 +7,9 @@ namespace Ocelot.Configuration.File public FileAuthenticationOptions() { AllowedScopes = new List(); - IdentityServerConfig = new FileIdentityServerConfig(); - JwtConfig = new FileJwtConfig(); } - public string Provider { get; set; } + public string AuthenticationProviderKey {get; set;} public List AllowedScopes { get; set; } - public FileIdentityServerConfig IdentityServerConfig { get; set; } - public FileJwtConfig JwtConfig { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs new file mode 100644 index 00000000..ea352767 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs @@ -0,0 +1,15 @@ +namespace Ocelot.Configuration.File +{ + public class FileHttpHandlerOptions + { + public FileHttpHandlerOptions() + { + AllowAutoRedirect = true; + UseCookieContainer = true; + } + + public bool AllowAutoRedirect { get; set; } + + public bool UseCookieContainer { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 59483aee..9abb5a7d 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -11,16 +11,16 @@ namespace Ocelot.Configuration.File AddClaimsToRequest = new Dictionary(); RouteClaimsRequirement = new Dictionary(); AddQueriesToRequest = new Dictionary(); - AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); QoSOptions = new FileQoSOptions(); RateLimitOptions = new FileRateLimitRule(); + AuthenticationOptions = new FileAuthenticationOptions(); + HttpHandlerOptions = new FileHttpHandlerOptions(); } public string DownstreamPathTemplate { get; set; } public string UpstreamPathTemplate { get; set; } public List UpstreamHttpMethod { get; set; } - public FileAuthenticationOptions AuthenticationOptions { get; set; } public Dictionary AddHeadersToRequest { get; set; } public Dictionary AddClaimsToRequest { get; set; } public Dictionary RouteClaimsRequirement { get; set; } @@ -35,5 +35,7 @@ namespace Ocelot.Configuration.File public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} public FileRateLimitRule RateLimitOptions { get; set; } + public FileAuthenticationOptions AuthenticationOptions { get; set; } + public FileHttpHandlerOptions HttpHandlerOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/HttpHandlerOptions.cs b/src/Ocelot/Configuration/HttpHandlerOptions.cs new file mode 100644 index 00000000..0ec72d1d --- /dev/null +++ b/src/Ocelot/Configuration/HttpHandlerOptions.cs @@ -0,0 +1,25 @@ +namespace Ocelot.Configuration +{ + /// + /// Describes configuration parameters for http handler, + /// that is created to handle a request to service + /// + public class HttpHandlerOptions + { + public HttpHandlerOptions(bool allowAutoRedirect, bool useCookieContainer) + { + AllowAutoRedirect = allowAutoRedirect; + UseCookieContainer = useCookieContainer; + } + + /// + /// Specify if auto redirect is enabled + /// + public bool AllowAutoRedirect { get; private set; } + + /// + /// Specify is handler has to use a cookie container + /// + public bool UseCookieContainer { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 2b8734c4..cfc8b9ba 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -29,7 +29,8 @@ namespace Ocelot.Configuration bool isQos, QoSOptions qosOptions, bool enableEndpointRateLimiting, - RateLimitOptions ratelimitOptions) + RateLimitOptions ratelimitOptions, + HttpHandlerOptions httpHandlerOptions) { ReRouteKey = reRouteKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; @@ -58,6 +59,7 @@ namespace Ocelot.Configuration QosOptionsOptions = qosOptions; EnableEndpointEndpointRateLimiting = enableEndpointRateLimiting; RateLimitOptions = ratelimitOptions; + HttpHandlerOptions = httpHandlerOptions; } public string ReRouteKey {get;private set;} @@ -84,5 +86,6 @@ namespace Ocelot.Configuration public ServiceProviderConfiguration ServiceProviderConfiguraion { get; private set; } public bool EnableEndpointEndpointRateLimiting { get; private set; } public RateLimitOptions RateLimitOptions { get; private set; } + public HttpHandlerOptions HttpHandlerOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs index 6fa6f72a..25c5b00a 100644 --- a/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulOcelotConfigurationRepository.cs @@ -3,8 +3,6 @@ using System.Text; using System.Threading.Tasks; using Consul; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Ocelot.Authentication.JsonConverters; using Ocelot.Responses; using Ocelot.ServiceDiscovery; @@ -49,9 +47,7 @@ namespace Ocelot.Configuration.Repository var json = Encoding.UTF8.GetString(bytes); - var settings = new JsonSerializerSettings(); - settings.Converters.Add(new AuthenticationConfigConverter()); - var consulConfig = JsonConvert.DeserializeObject(json, settings); + var consulConfig = JsonConvert.DeserializeObject(json); return new OkResponse(consulConfig); } diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateDoesntStartWithForwardSlash.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateDoesntStartWithForwardSlash.cs new file mode 100644 index 00000000..2f09dbfb --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateDoesntStartWithForwardSlash.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class PathTemplateDoesntStartWithForwardSlash : Error + { + public PathTemplateDoesntStartWithForwardSlash(string message) + : base(message, OcelotErrorCode.PathTemplateDoesntStartWithForwardSlash) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 54a23f25..7b4f6dbe 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -1,7 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Ocelot.Authentication.Handler; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Ocelot.Configuration.File; using Ocelot.Errors; using Ocelot.Responses; @@ -10,7 +10,14 @@ namespace Ocelot.Configuration.Validator { public class FileConfigurationValidator : IConfigurationValidator { - public Response IsValid(FileConfiguration configuration) + private readonly IAuthenticationSchemeProvider _provider; + + public FileConfigurationValidator(IAuthenticationSchemeProvider provider) + { + _provider = provider; + } + + public async Task> IsValid(FileConfiguration configuration) { var result = CheckForDuplicateReRoutes(configuration); @@ -19,7 +26,21 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - result = CheckForUnsupportedAuthenticationProviders(configuration); + result = CheckDownstreamTemplatePathBeingsWithForwardSlash(configuration); + + if (result.IsError) + { + return new OkResponse(result); + } + + result = CheckUpstreamTemplatePathBeingsWithForwardSlash(configuration); + + if (result.IsError) + { + return new OkResponse(result); + } + + result = await CheckForUnsupportedAuthenticationProviders(configuration); if (result.IsError) { @@ -42,25 +63,67 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - private ConfigurationValidationResult CheckForUnsupportedAuthenticationProviders(FileConfiguration configuration) + private ConfigurationValidationResult CheckDownstreamTemplatePathBeingsWithForwardSlash(FileConfiguration configuration) + { + var errors = new List(); + + foreach(var reRoute in configuration.ReRoutes) + { + if(!reRoute.DownstreamPathTemplate.StartsWith("/")) + { + errors.Add(new PathTemplateDoesntStartWithForwardSlash($"{reRoute.DownstreamPathTemplate} doesnt start with forward slash")); + } + } + + if(errors.Any()) + { + return new ConfigurationValidationResult(true, errors); + } + + return new ConfigurationValidationResult(false, errors); + } + + private ConfigurationValidationResult CheckUpstreamTemplatePathBeingsWithForwardSlash(FileConfiguration configuration) + { + var errors = new List(); + + foreach(var reRoute in configuration.ReRoutes) + { + if(!reRoute.UpstreamPathTemplate.StartsWith("/")) + { + errors.Add(new PathTemplateDoesntStartWithForwardSlash($"{reRoute.DownstreamPathTemplate} doesnt start with forward slash")); + } + } + + if(errors.Any()) + { + return new ConfigurationValidationResult(true, errors); + } + + return new ConfigurationValidationResult(false, errors); + } + + private async Task CheckForUnsupportedAuthenticationProviders(FileConfiguration configuration) { var errors = new List(); foreach (var reRoute in configuration.ReRoutes) { - var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider); + var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions.AuthenticationProviderKey); if (!isAuthenticated) { continue; } - if (IsSupportedAuthenticationProvider(reRoute.AuthenticationOptions?.Provider)) + var data = await _provider.GetAllSchemesAsync(); + var schemes = data.ToList(); + if (schemes.Any(x => x.Name == reRoute.AuthenticationOptions.AuthenticationProviderKey)) { continue; } - var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); + var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions.AuthenticationProviderKey} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); errors.Add(error); } @@ -69,13 +132,6 @@ namespace Ocelot.Configuration.Validator : new ConfigurationValidationResult(false); } - private bool IsSupportedAuthenticationProvider(string provider) - { - SupportedAuthenticationProviders supportedProvider; - - return Enum.TryParse(provider, true, out supportedProvider); - } - private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration) { var errors = new List(); diff --git a/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs index 09bf7dae..5f6ae6ba 100644 --- a/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/IConfigurationValidator.cs @@ -1,10 +1,11 @@ -using Ocelot.Configuration.File; +using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Responses; namespace Ocelot.Configuration.Validator { public interface IConfigurationValidator { - Response IsValid(FileConfiguration configuration); + Task> IsValid(FileConfiguration configuration); } } diff --git a/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs b/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs deleted file mode 100644 index f5cd4fda..00000000 --- a/src/Ocelot/Creator/Configuration/IAuthenticationProviderConfigCreator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.Configuration.File; - -namespace Ocelot.Creator.Configuration -{ - public interface IAuthenticationProviderConfigCreator - { - IAuthenticationConfig Create(FileAuthenticationOptions authenticationOptions); - } -} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index d55a8573..430ae18d 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -4,8 +4,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Ocelot.Authentication.Handler.Creator; -using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; using Ocelot.Cache; using Ocelot.Claims; @@ -38,13 +36,15 @@ using Ocelot.Responder; using Ocelot.ServiceDiscovery; using System; using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net.Http; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using Microsoft.IdentityModel.Tokens; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Ocelot.Configuration; -using Ocelot.Creator.Configuration; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; namespace Ocelot.DependencyInjection @@ -72,10 +72,8 @@ namespace Ocelot.DependencyInjection services.Configure(configurationRoot); services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -84,59 +82,7 @@ namespace Ocelot.DependencyInjection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - - var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); - - if(identityServerConfiguration != null) - { - services.TryAddSingleton(identityServerConfiguration); - services.TryAddSingleton(); - var identityServerBuilder = services - .AddIdentityServer(options => { - options.IssuerUri = "Ocelot"; - }) - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = identityServerConfiguration.ApiName, - Description = identityServerConfiguration.Description, - Enabled = identityServerConfiguration.Enabled, - DisplayName = identityServerConfiguration.ApiName, - Scopes = identityServerConfiguration.AllowedScopes.Select(x => new Scope(x)).ToList(), - ApiSecrets = new List - { - new Secret - { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } - } - }) - .AddInMemoryClients(new List - { - 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 - } - }).AddResourceOwnerValidator(); - - if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) - { - identityServerBuilder.AddTemporarySigningCredential(); - } - else - { - var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); - identityServerBuilder.AddSigningCredential(cert); - } - } + services.TryAddSingleton(); var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; @@ -175,11 +121,10 @@ namespace Ocelot.DependencyInjection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); 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 @@ -190,8 +135,89 @@ namespace Ocelot.DependencyInjection //Used to log the the start and ending of middleware services.TryAddSingleton(); services.AddMiddlewareAnalysis(); + services.AddWebEncoders(); + + var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); + + if (identityServerConfiguration != null) + { + services.AddIdentityServer(identityServerConfiguration, configurationRoot); + } return services; } + + private static void AddIdentityServer(this IServiceCollection services, IIdentityServerConfiguration identityServerConfiguration, IConfigurationRoot configurationRoot) + { + services.TryAddSingleton(identityServerConfiguration); + services.TryAddSingleton(); + var identityServerBuilder = services + .AddIdentityServer(o => { + o.IssuerUri = "Ocelot"; + }) + .AddInMemoryApiResources(Resources(identityServerConfiguration)) + .AddInMemoryClients(Client(identityServerConfiguration)) + .AddResourceOwnerValidator(); + + //todo - refactor a method so we know why this is happening + var whb = services.First(x => x.ServiceType == typeof(IWebHostBuilder)); + var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance); + var baseSchemeUrlAndPort = urlFinder.Find(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) + .AddIdentityServerAuthentication(o => + { + var adminPath = configurationRoot.GetValue("GlobalConfiguration:AdministrationPath", string.Empty); + o.Authority = baseSchemeUrlAndPort + adminPath; + o.ApiName = identityServerConfiguration.ApiName; + o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = identityServerConfiguration.ApiSecret; + }); + + //todo - refactor naming.. + if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) + { + identityServerBuilder.AddDeveloperSigningCredential(); + } + else + { + //todo - refactor so calls method? + var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); + identityServerBuilder.AddSigningCredential(cert); + } + } + + private static List Resources(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) + { + ApiSecrets = new List + { + new Secret + { + Value = identityServerConfiguration.ApiSecret.Sha256() + } + } + } + }; + } + + private static List Client(IIdentityServerConfiguration identityServerConfiguration) + { + return new List + { + new Client + { + ClientId = identityServerConfiguration.ApiName, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, + AllowedScopes = { identityServerConfiguration.ApiName } + } + }; + } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index bf0d5a52..cd2c098f 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -6,6 +6,7 @@ using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; using Ocelot.Responses; +using Ocelot.Utilities; namespace Ocelot.DownstreamRouteFinder.Finder { @@ -24,6 +25,8 @@ namespace Ocelot.DownstreamRouteFinder.Finder public async Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { + upstreamUrlPath = upstreamUrlPath.SetLastCharacterAs('/'); + var configuration = await _configProvider.Get(); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(upstreamHttpMethod.ToLower())); diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 51cb3a0b..f421d6d8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -30,7 +30,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware public async Task Invoke(HttpContext context) { - var upstreamUrlPath = context.Request.Path.ToString().SetLastCharacterAs('/'); + var upstreamUrlPath = context.Request.Path.ToString(); _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index afe5f508..5a6139ba 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -50,11 +50,7 @@ namespace Ocelot.Errors.Middleware private void SetInternalServerErrorOnResponse(HttpContext context) { - context.Response.OnStarting(x => - { - context.Response.StatusCode = 500; - return Task.CompletedTask; - }, context); + context.Response.StatusCode = 500; } private string CreateMessage(HttpContext context, Exception e) diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 817edbd8..6f85df58 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -31,6 +31,7 @@ UnableToFindQoSProviderError, UnableToSetConfigInConsulError, UnmappableRequestError, - RateLimitOptionsError + RateLimitOptionsError, + PathTemplateDoesntStartWithForwardSlash } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index c9b5f536..cef4307b 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using Ocelot.Authentication.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; +using Ocelot.Controllers; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.Errors.Middleware; @@ -51,7 +54,7 @@ namespace Ocelot.Middleware /// /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { await CreateAdministrationArea(builder); @@ -178,25 +181,10 @@ namespace Ocelot.Middleware if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { - var urlFinder = (IBaseUrlFinder)builder.ApplicationServices.GetService(typeof(IBaseUrlFinder)); - - var baseSchemeUrlAndPort = urlFinder.Find(); - builder.Map(configuration.AdministrationPath, app => { - var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}"; - app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions - { - Authority = identityServerUrl, - ApiName = identityServerConfiguration.ApiName, - RequireHttpsMetadata = identityServerConfiguration.RequireHttps, - AllowedScopes = identityServerConfiguration.AllowedScopes, - SupportedTokens = SupportedTokens.Both, - ApiSecret = identityServerConfiguration.ApiSecret - }); - app.UseIdentityServer(); - + app.UseAuthentication(); app.UseMvc(); }); } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index c7626bc7..243953b5 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,21 +1,21 @@  + netcoreapp2.0 + 2.0.0 + 2.0.0 This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. Ocelot 0.0.0-dev - netcoreapp1.1 - 1.6.1 Ocelot Ocelot API Gateway;.NET core https://github.com/TomPallister/Ocelot - https://github.com/TomPallister/Ocelot + https://github.com/TomPallister/Ocelot win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - 1.1.1 false false - True + True false Tom Pallister @@ -26,37 +26,23 @@ - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index f95db4b9..8c3c9218 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -10,9 +10,11 @@ namespace Ocelot.Request.Builder public async Task> Build( HttpRequestMessage httpRequestMessage, bool isQos, - IQoSProvider qosProvider) + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect) { - return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider)); + return new OkResponse(new Request(httpRequestMessage, isQos, qosProvider, useCookieContainer, allowAutoRedirect)); } } } \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 85fbfa8d..d290db4f 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -11,6 +11,8 @@ Task> Build( HttpRequestMessage httpRequestMessage, bool isQos, - IQoSProvider qosProvider); + IQoSProvider qosProvider, + bool useCookieContainer, + bool allowAutoRedirect); } } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index c59af7a6..8e05f2ea 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -46,7 +46,9 @@ namespace Ocelot.Request.Middleware var buildResult = await _requestCreator.Build( DownstreamRequest, DownstreamRoute.ReRoute.IsQos, - qosProvider.Data); + qosProvider.Data, + DownstreamRoute.ReRoute.HttpHandlerOptions.UseCookieContainer, + DownstreamRoute.ReRoute.HttpHandlerOptions.AllowAutoRedirect); if (buildResult.IsError) { diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 3f1c654c..9f0b66e2 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -8,15 +8,21 @@ namespace Ocelot.Request public Request( HttpRequestMessage httpRequestMessage, bool isQos, - IQoSProvider qosProvider) + IQoSProvider qosProvider, + bool allowAutoRedirect, + bool useCookieContainer) { HttpRequestMessage = httpRequestMessage; IsQos = isQos; QosProvider = qosProvider; + AllowAutoRedirect = allowAutoRedirect; + UseCookieContainer = useCookieContainer; } public HttpRequestMessage HttpRequestMessage { get; private set; } public bool IsQos { get; private set; } public IQoSProvider QosProvider { get; private set; } + public bool AllowAutoRedirect { get; private set; } + public bool UseCookieContainer { get; private set; } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 141eab8b..83f475c2 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -20,9 +20,9 @@ namespace Ocelot.Requester return this; } - public IHttpClient Create() + public IHttpClient Create(bool useCookies, bool allowAutoRedirect) { - var httpclientHandler = new HttpClientHandler(); + var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect, UseCookies = useCookies}; var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler)); diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 3f85af0c..a6a28915 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Net.Http; using System.Threading.Tasks; +using Ocelot.Configuration; using Ocelot.Logging; using Ocelot.Responses; using Polly.CircuitBreaker; @@ -14,7 +15,8 @@ namespace Ocelot.Requester private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers) + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, + IHttpClientCache cacheHandlers) { _logger = loggerFactory.CreateLogger(); _cacheHandlers = cacheHandlers; @@ -25,8 +27,8 @@ namespace Ocelot.Requester var builder = new HttpClientBuilder(); var cacheKey = GetCacheKey(request, builder); - - var httpClient = GetHttpClient(cacheKey, builder); + + var httpClient = GetHttpClient(cacheKey, builder, request.UseCookieContainer, request.AllowAutoRedirect); try { @@ -54,13 +56,13 @@ namespace Ocelot.Requester } - private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder) + private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, bool useCookieContainer, bool allowAutoRedirect) { var httpClient = _cacheHandlers.Get(cacheKey); if (httpClient == null) { - httpClient = builder.Create(); + httpClient = builder.Create(useCookieContainer, allowAutoRedirect); } return httpClient; } diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index 832fd8d1..6de5f87a 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -15,11 +15,13 @@ namespace Ocelot.Requester /// /// Sets a PollyCircuitBreakingDelegatingHandler . /// - IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger); - - /// + IHttpClientBuilder WithQos(IQoSProvider qosProvider, IOcelotLogger logger); + + /// /// Creates the - /// - IHttpClient Create(); + /// + /// Defines if http client should use cookie container + /// Defines if http client should allow auto redirect + IHttpClient Create(bool useCookies, bool allowAutoRedirect); } } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 64116034..eb51e889 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -14,7 +15,6 @@ using Xunit; namespace Ocelot.AcceptanceTests { - using IdentityServer4; using IdentityServer4.Test; public class AuthenticationTests : IDisposable @@ -28,10 +28,19 @@ namespace Ocelot.AcceptanceTests private int _downstreamServicePort = 51876; private string _downstreamServiceScheme = "http"; private string _downstreamServiceUrl = "http://localhost:51876"; + private readonly Action _options; public AuthenticationTests() { _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; } [Fact] @@ -51,23 +60,16 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Post" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" } } } }; - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) @@ -91,14 +93,7 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" } } } @@ -108,7 +103,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) @@ -133,14 +128,7 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" } } } @@ -150,7 +138,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) @@ -172,17 +160,9 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamPathTemplate = "/", UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" } } } @@ -192,7 +172,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) @@ -217,14 +197,7 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Post" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = _identityServerRootUrl, - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" } } } @@ -234,7 +207,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) @@ -275,7 +248,7 @@ namespace Ocelot.AcceptanceTests { services.AddLogging(); services.AddIdentityServer() - .AddTemporarySigningCredential() + .AddDeveloperSigningCredential() .AddInMemoryApiResources(new List { new ApiResource diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 3eebd000..a8901573 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -22,10 +23,20 @@ namespace Ocelot.AcceptanceTests private IWebHost _servicebuilder; private IWebHost _identityServerBuilder; private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:51888"; public AuthorisationTests() { _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; } [Fact] @@ -45,14 +56,7 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" }, AddHeadersToRequest = { @@ -79,7 +83,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) @@ -104,14 +108,7 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } + AuthenticationProviderKey = "Test" }, AddHeadersToRequest = { @@ -137,7 +134,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken("http://localhost:51888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) @@ -148,7 +145,7 @@ namespace Ocelot.AcceptanceTests public void should_return_response_200_using_identity_server_with_allowed_scope() { var configuration = new FileConfiguration - { + { ReRoutes = new List { new FileReRoute @@ -161,15 +158,9 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { + AuthenticationProviderKey = "Test", AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } + }, } } }; @@ -178,7 +169,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) @@ -202,15 +193,9 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { + AuthenticationProviderKey = "Test", AllowedScopes = new List{ "api", "openid", "offline_access" }, - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } - } + }, } } }; @@ -219,7 +204,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) .And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) @@ -259,7 +244,7 @@ namespace Ocelot.AcceptanceTests { services.AddLogging(); services.AddIdentityServer() - .AddTemporarySigningCredential() + .AddDeveloperSigningCredential() .AddInMemoryApiResources(new List { new ApiResource diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 740bf512..8c7ef1cd 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -40,7 +40,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -68,7 +68,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -96,7 +96,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -152,7 +152,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -180,7 +180,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/PRODUCTS/1")) @@ -188,16 +188,17 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _builder = new WebHostBuilder() - .UseUrls(url) + .UseUrls(baseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() - .UseUrls(url) + .UseUrls(baseUrl) .Configure(app => { + app.UsePathBase(basePath); app.Run(async context => { context.Response.StatusCode = statusCode; diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index babb520e..abfb002a 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -24,10 +25,20 @@ namespace Ocelot.AcceptanceTests private IWebHost _servicebuilder; private IWebHost _identityServerBuilder; private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:52888"; public ClaimsToHeadersForwardingTests() { _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; } [Fact] @@ -59,17 +70,11 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List + AuthenticationProviderKey = "Test", + AllowedScopes = new List { "openid", "offline_access", "api" }, - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:52888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } }, AddHeadersToRequest = { @@ -86,7 +91,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:52876", 200)) .And(x => _steps.GivenIHaveAToken("http://localhost:52888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) @@ -133,7 +138,7 @@ namespace Ocelot.AcceptanceTests { services.AddLogging(); services.AddIdentityServer() - .AddTemporarySigningCredential() + .AddDeveloperSigningCredential() .AddInMemoryApiResources(new List { new ApiResource diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 744cfab2..d7d1b39c 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Security.Claims; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -24,10 +25,20 @@ namespace Ocelot.AcceptanceTests private IWebHost _servicebuilder; private IWebHost _identityServerBuilder; private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl = "http://localhost:57888"; public ClaimsToQueryStringForwardingTests() { _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; } [Fact] @@ -59,17 +70,11 @@ namespace Ocelot.AcceptanceTests UpstreamHttpMethod = new List { "Get" }, AuthenticationOptions = new FileAuthenticationOptions { - AllowedScopes = new List + AuthenticationProviderKey = "Test", + AllowedScopes = new List { "openid", "offline_access", "api" }, - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig{ - ProviderRootUrl = "http://localhost:57888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } }, AddQueriesToRequest = { @@ -86,7 +91,7 @@ namespace Ocelot.AcceptanceTests .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) @@ -140,7 +145,7 @@ namespace Ocelot.AcceptanceTests { services.AddLogging(); services.AddIdentityServer() - .AddTemporarySigningCredential() + .AddDeveloperSigningCredential() .AddInMemoryApiResources(new List { new ApiResource diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 8416a66d..c970c0c0 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -75,7 +75,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) @@ -128,7 +128,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) @@ -137,16 +137,17 @@ namespace Ocelot.AcceptanceTests } - private void GivenThereIsAServiceRunningOn(string url) + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) { _builder = new WebHostBuilder() - .UseUrls(url) + .UseUrls(baseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() - .UseUrls(url) + .UseUrls(baseUrl) .Configure(app => { + app.UsePathBase(basePath); app.Run(context => { _counterOne++; diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 231f8410..87476533 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -3,15 +3,12 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; -using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; -using Ocelot.Authentication.JsonConverters; using Ocelot.Configuration; using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; using Ocelot.ServiceDiscovery; using TestStack.BDDfy; using Xunit; @@ -105,9 +102,7 @@ namespace Ocelot.AcceptanceTests var json = reader.ReadToEnd(); - var settings = new JsonSerializerSettings(); - settings.Converters.Add(new AuthenticationConfigConverter()); - _config = JsonConvert.DeserializeObject(json, settings); + _config = JsonConvert.DeserializeObject(json); var response = JsonConvert.SerializeObject(true); diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 6b45c6f8..661c1281 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -119,7 +119,7 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamPathTemplate = "41879/", + DownstreamPathTemplate = "/41879/", DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index eea05872..6380b5f9 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -2,13 +2,13 @@ 0.0.0-dev - netcoreapp1.1 + netcoreapp2.0 + 2.0.0 Ocelot.AcceptanceTests Exe Ocelot.AcceptanceTests true osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - 1.1.1 false false false @@ -30,28 +30,23 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index 8825b75b..e8a8e6a1 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -98,7 +98,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51880, - UpstreamPathTemplate = "working", + UpstreamPathTemplate = "/working", UpstreamHttpMethod = new List { "Get" }, } } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index b32c4690..4994aff7 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; +using Shouldly; using TestStack.BDDfy; using Xunit; @@ -15,6 +16,7 @@ namespace Ocelot.AcceptanceTests { private IWebHost _builder; private readonly Steps _steps; + private string _downstreamPath; public RoutingTests() { @@ -50,7 +52,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -59,6 +61,47 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void bug() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/vacancy/", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/vacancy/{vacancyId}", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/vacancy/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_return_response_200_when_path_missing_forward_slash_as_first_char() { @@ -68,7 +111,7 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamPathTemplate = "api/products", + DownstreamPathTemplate = "/api/products", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, @@ -78,7 +121,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -106,7 +149,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -134,7 +177,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products")) @@ -162,7 +205,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) @@ -196,7 +239,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/products", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/")) @@ -223,7 +266,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) @@ -232,6 +275,34 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + + [Fact] + public void should_not_add_trailing_slash_to_downstream_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/products/1")) + .Then(x => ThenTheDownstreamUrlPathShouldBe("/api/products/1")) + .BDDfy(); + } + [Fact] public void should_return_response_201_with_simple_url() { @@ -251,7 +322,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 201, string.Empty)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -279,7 +350,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) @@ -307,7 +378,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/myApp1Name/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/myApp1Name/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) @@ -335,7 +406,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 201, string.Empty)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -363,7 +434,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -372,18 +443,59 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + [Fact] + public void should_return_404_when_calling_upstream_route_with_no_matching_downstream_re_route_github_issue_134() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/vacancy/", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/vacancy/{vacancyId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/vacancy/{vacancyId}", + UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, + ServiceName = "botCore", + LoadBalancer = "LeastConnection" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/v1/vacancy/1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("api/vacancy/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _builder = new WebHostBuilder() - .UseUrls(url) + .UseUrls(baseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() - .UseUrls(url) .Configure(app => { + app.UsePathBase(basePath); app.Run(async context => - { + { + _downstreamPath = context.Request.PathBase.Value; context.Response.StatusCode = statusCode; await context.Response.WriteAsync(responseBody); }); @@ -393,6 +505,11 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } + public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index cb408037..fb853fbb 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -8,6 +8,8 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; @@ -17,7 +19,6 @@ using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.DependencyInjection; -using Ocelot.ManualTest; using Ocelot.Middleware; using Ocelot.ServiceDiscovery; using Shouldly; @@ -86,6 +87,26 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + /// + /// 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. + /// + public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + public void GivenOcelotIsRunningUsingConsulToStoreConfig(ConsulRegistryConfiguration consulConfig) { _webHostBuilder = new WebHostBuilder(); @@ -135,9 +156,7 @@ namespace Ocelot.AcceptanceTests .AddEnvironmentVariables(); var configuration = builder.Build(); - _webHostBuilder = new WebHostBuilder(); - _webHostBuilder.ConfigureServices(s => { s.AddSingleton(_webHostBuilder); @@ -160,7 +179,7 @@ namespace Ocelot.AcceptanceTests }) .ConfigureLogging(l => { - l.AddConsole(configuration.GetSection("Logging")); + l.AddConsole(); l.AddDebug(); }) .Configure(a => @@ -362,4 +381,42 @@ namespace Ocelot.AcceptanceTests _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); } } + + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + services.AddOcelot(Configuration, settings); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + + app.UseOcelot().Wait(); + } + } } diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index c2928a5c..232305d4 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -2,7 +2,8 @@ 0.0.0-dev - netcoreapp1.1 + netcoreapp2.0 + 2.0.0 Ocelot.Benchmarks Exe Ocelot.Benchmarks @@ -17,7 +18,7 @@ - + diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 39721df6..7cc88ed8 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Ocelot.Cache; using Ocelot.Configuration.File; -using Ocelot.ManualTest; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -59,7 +58,7 @@ namespace Ocelot.IntegrationTests public void should_return_response_200_with_call_re_routes_controller() { var configuration = new FileConfiguration - { + { GlobalConfiguration = new FileGlobalConfiguration { AdministrationPath = "/administration" diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 1556111a..5d1d4841 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -2,13 +2,13 @@ 0.0.0-dev - netcoreapp1.1 + netcoreapp2.0 + 2.0.0 Ocelot.IntegrationTests Exe Ocelot.IntegrationTests true win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - 1.1.1 false false false @@ -30,27 +30,22 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + diff --git a/test/Ocelot.IntegrationTests/Startup.cs b/test/Ocelot.IntegrationTests/Startup.cs new file mode 100644 index 00000000..1b67ff96 --- /dev/null +++ b/test/Ocelot.IntegrationTests/Startup.cs @@ -0,0 +1,51 @@ +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + +namespace Ocelot.IntegrationTests +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + services.AddOcelot(Configuration, settings); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + + app.UseOcelot().Wait(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 2922d92c..301d21d6 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Ocelot.Configuration.File; -using Ocelot.ManualTest; using Shouldly; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 57f23ad0..5d875410 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -2,13 +2,13 @@ 0.0.0-dev - netcoreapp1.1 + netcoreapp2.0 + 2.0.0 true Ocelot.ManualTest Exe Ocelot.ManualTest osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - 1.1.1 @@ -17,24 +17,27 @@ + + + PreserveNewest + + + - - - - - - - - - - - - - + + + + + + + + + + diff --git a/test/Ocelot.ManualTest/Ocelot.postman_collection.json b/test/Ocelot.ManualTest/Ocelot.postman_collection.json index eef5f5e8..d8598539 100644 --- a/test/Ocelot.ManualTest/Ocelot.postman_collection.json +++ b/test/Ocelot.ManualTest/Ocelot.postman_collection.json @@ -10,175 +10,362 @@ "e8825dc3-4137-99a7-0000-ef5786610dc3", "fddfc4fa-5114-69e3-4744-203ed71a526b", "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "4684c2fa-f38c-c193-5f55-bf563a1978c6" + "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "37bfa9f1-fe29-6a68-e558-66d125d2c96f", + "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "178f16da-c61b-c881-1c33-9d64a56851a4" ], "folders": [], - "timestamp": 1477767328599, + "folders_order": [], + "timestamp": 0, "owner": "212120", "public": false, "requests": [ { + "folder": null, "id": "09af8dda-a9cb-20d2-5ee3-0a3023773a1a", - "headers": "", - "url": "http://localhost:5000/comments?postId=1", - "pathVariables": {}, - "preRequestScript": null, - "method": "GET", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": null, - "dataMode": "params", "name": "GET http://localhost:5000/comments?postId=1", - "description": "", - "descriptionFormat": "html", - "time": 1477768105592, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {} - }, - { - "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", - "headers": "", - "url": "http://localhost:5000/posts/1", - "pathVariables": {}, - "preRequestScript": null, - "method": "DELETE", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": null, "dataMode": "params", - "name": "DELETE http://localhost:5000/posts/1", - "description": "", + "data": null, + "rawModeData": null, "descriptionFormat": "html", - "time": 1477768404376, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {} - }, - { - "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "description": "", "headers": "", - "url": "http://localhost:5000/posts", - "pathVariables": {}, - "preRequestScript": null, "method": "GET", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": null, - "dataMode": "params", - "name": "GET http://localhost:5000/posts", - "description": "", - "descriptionFormat": "html", - "time": 1477768007806, - "version": 2, - "responses": [], + "pathVariables": {}, + "url": "http://localhost:5000/comments?postId=1", + "preRequestScript": null, "tests": null, "currentHelper": "normal", - "helperAttributes": {} + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" }, { - "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", - "headers": "", - "url": "http://localhost:5000/posts/1/comments", - "pathVariables": {}, + "id": "178f16da-c61b-c881-1c33-9d64a56851a4", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "enabled": true, + "description": "" + } + ], + "url": "http://localhost:5000/administration/configuration", + "folder": null, + "queryParams": [], "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], "method": "GET", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", "data": null, "dataMode": "params", - "name": "GET http://localhost:5000/posts/1/comments", - "description": "", - "descriptionFormat": "html", - "time": 1477768043524, - "version": 2, - "responses": [], "tests": null, "currentHelper": "normal", - "helperAttributes": {} - }, - { - "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", - "headers": "", - "url": "http://localhost:5000/posts/1", - "pathVariables": {}, - "preRequestScript": null, - "method": "PATCH", + "helperAttributes": "{}", + "time": 1508849878025, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": [], - "dataMode": "raw", - "name": "PATCH http://localhost:5000/posts/1", - "description": "", - "descriptionFormat": "html", - "time": 1477768379775, - "version": 2, "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {}, - "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}" + "isFromCollection": true, + "collectionRequestId": "178f16da-c61b-c881-1c33-9d64a56851a4", + "rawModeData": null, + "descriptionFormat": null }, { - "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", + "id": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", "headers": "", - "url": "http://localhost:5000/posts", - "pathVariables": {}, + "headerData": [], + "url": "http://localhost:5000/administration/connect/token", + "folder": null, + "queryParams": [], "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], "method": "POST", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": [], - "dataMode": "raw", - "name": "POST http://localhost:5000/posts/1", - "description": "", - "descriptionFormat": "html", - "time": 1477768186023, - "version": 2, - "responses": [], - "tests": null, + "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", + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", "currentHelper": "normal", - "helperAttributes": {}, - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + "helperAttributes": "{}", + "time": 1506359585080, + "name": "POST http://localhost:5000/admin/connect/token copy", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "responses": [], + "isFromCollection": true, + "collectionRequestId": "37bfa9f1-fe29-6a68-e558-66d125d2c96f", + "rawModeData": null, + "descriptionFormat": null }, { - "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "folder": null, + "id": "4684c2fa-f38c-c193-5f55-bf563a1978c6", + "name": "DELETE http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", "headers": "", - "url": "http://localhost:5000/posts/1", + "method": "DELETE", "pathVariables": {}, + "url": "http://localhost:5000/posts/1", "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "id": "5f308240-79e3-cf74-7a6b-fe462f0d54f1", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{AccessToken}}", + "description": "", + "enabled": true + } + ], + "url": "http://localhost:5000/administration/.well-known/openid-configuration", + "folder": null, + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], "method": "GET", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", "data": null, "dataMode": "params", - "name": "GET http://localhost:5000/posts/1", - "description": "", - "descriptionFormat": "html", - "time": 1477768023989, - "version": 2, - "responses": [], - "tests": null, - "currentHelper": "normal", - "helperAttributes": {} - }, - { - "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", - "headers": "", - "url": "http://localhost:5000/posts/1", - "pathVariables": {}, - "preRequestScript": null, - "method": "PUT", - "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", - "data": [], - "dataMode": "raw", - "name": "PUT http://localhost:5000/posts/1", - "description": "", - "descriptionFormat": "html", - "time": 1477768307036, - "version": 2, - "responses": [], "tests": null, "currentHelper": "normal", "helperAttributes": {}, - "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}" + "time": 1508849923518, + "name": "GET http://localhost:5000/admin/.well-known/openid-configuration", + "description": "", + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375", + "responses": [] + }, + { + "folder": null, + "id": "a1c95935-ed18-d5dc-bcb8-a3db8ba1934f", + "name": "GET http://localhost:5000/posts", + "dataMode": "params", + "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": "admin", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c4494401-3985-a5bf-71fb-6e4171384ac6", + "name": "GET http://localhost:5000/posts/1/comments", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1/comments", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "c45d30d7-d9c4-fa05-8110-d6e769bb6ff9", + "name": "PATCH http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"title\": \"gfdgsgsdgsdfgsdfgdfg\",\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PATCH", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "e8825dc3-4137-99a7-0000-ef5786610dc3", + "name": "POST http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "POST", + "pathVariables": {}, + "url": "http://localhost:5000/posts", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "ea0ed57a-2cb9-8acc-47dd-006b8db2f1b2", + "name": "GET http://localhost:5000/posts/1", + "dataMode": "params", + "data": null, + "rawModeData": null, + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "GET", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" + }, + { + "folder": null, + "id": "fddfc4fa-5114-69e3-4744-203ed71a526b", + "name": "PUT http://localhost:5000/posts/1", + "dataMode": "raw", + "data": [], + "rawModeData": "{\n \"userId\": 1,\n \"title\": \"test\",\n \"body\": \"test\"\n}", + "descriptionFormat": "html", + "description": "", + "headers": "", + "method": "PUT", + "pathVariables": {}, + "url": "http://localhost:5000/posts/1", + "preRequestScript": null, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "queryParams": [], + "headerData": [], + "pathVariableData": [], + "responses": [], + "collectionId": "4dbde9fe-89f5-be35-bb9f-d3b438e16375" } ] } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index a545819a..98b1f927 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -9,17 +9,14 @@ namespace Ocelot.ManualTest public static void Main(string[] args) { IWebHostBuilder builder = new WebHostBuilder(); - builder.ConfigureServices(s => { s.AddSingleton(builder); }); - builder.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); - + .UseIISIntegration() + .UseStartup(); var host = builder.Build(); - host.Run(); } } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index aa0661b6..cd5be1c4 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -38,6 +38,13 @@ namespace Ocelot.ManualTest .WithDictionaryHandle(); }; + services.AddAuthentication() + .AddJwtBearer("TestKey", x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + services.AddOcelot(Configuration, settings); } diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 6d2ee544..a063fe76 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -13,17 +13,11 @@ "TimeoutValue": 5000 }, "AuthenticationOptions": { - "Provider": "IdentityServer", + "AuthenticationProviderKey": "TestKey", "AllowedScopes": [ "openid", "offline_access" - ], - "IdentityServerConfig": { - "ProviderRootUrl": "http://localhost:52888", - "ApiName": "api", - "ApiSecret": "secret", - "RequireHttps": false - } + ] }, "AddHeadersToRequest": { "CustomerId": "Claims[CustomerId] > value", @@ -312,6 +306,6 @@ "GlobalConfiguration": { "RequestIdKey": "OcRequestId", - "AdministrationPath": "/admin" + "AdministrationPath": "/administration" } } \ No newline at end of file diff --git a/test/Ocelot.ManualTest/idsrv3test.pfx b/test/Ocelot.ManualTest/idsrv3test.pfx new file mode 100644 index 00000000..0247dea0 Binary files /dev/null and b/test/Ocelot.ManualTest/idsrv3test.pfx differ diff --git a/test/Ocelot.ManualTest/tempkey.rsa b/test/Ocelot.ManualTest/tempkey.rsa new file mode 100644 index 00000000..f1c5d01d --- /dev/null +++ b/test/Ocelot.ManualTest/tempkey.rsa @@ -0,0 +1 @@ +{"KeyId":"6aad821b3e366a0189ea6c9741eae6ba","Parameters":{"D":"RcfIF/iJ8qnKpHlaJCa9Qz+iN9Z655mfW0B/CycZx0WDQwQtjYNF+ijEkqfpGC3TJ9n19vXHdDEGfONxHwTtgS6PP/VIYYmql7OfCn+tkZUvMeIXykfEXFoNoWJXlT3eMI1JWyZpT/dZJLtmdeY09csDU/LjXTlyFrljW361T0NR/azAHqEfeuoKhqaJ2klzTzjif4xO5kMcTBHVyxrZm0+cbowsKPjI1QRh5xXsst8EDrM7rXStz4enneNaNbvP1nmWx++F7zn+5/WBDcPJVnL1HiyAzMAHj+oXG2JwDizO4RJxLbvQa2Y2jzoDp/qc++s2HWFo1PmuUnOzNIQjyQ==","DP":"x3VwsILF1yo8puLB6TOcb4hMWngz8rqjBl3dty4E3Un7UexVh+NkqTiSXWZNern6Ka6gE8CpdDXhQiYCFbcnBiSF6FVSpjpQ+Qf9PeRj5HipJF+DzGyEOTzwOiBjb6s5CvPUQWvJiqQoP1D+1V+X/+C0zug8+Df73UyHA7uieKc=","DQ":"aaZu9GfgKqUiy0uwPkmnLcwIEDqRlLG14c23OOoEqNRHK9T3OwUKEJ1qK0mbMKIVTwklyiC7uZHqMijIqk0LovTL4nI3LRlDkRjhUsIuubZZOAw7sYYemBUl+wEB/VZofaJ7H/CYtCUXyJhND2DFbTjzgeg3uWoMpyMDHuH/9Pc=","Exponent":"AQAB","InverseQ":"YRV4QA6rwB9BEHjzh5Wk3TcSS1CrwJWVopj/qC1amXxhtM3aMb4ZfKk7XoynLqHyQ86rB2p7dPNP2GL+fIWbt4h/ESO9JqOlM/bVXpdyCvIwchAL82hfHb4FRv+V+J2cksaIA+bHt7ye9n/XSmSr8v0WsjxN2qHzdla3t+J0c1I=","Modulus":"xLJZzQyYbJAqymvvJeig9H4cfXEy3t0KVnRuUumdSBzU/3F7q1vCDBkXLqs8icEv9ZL4MUgDzzSjjYJpVXzvC24L1My3NLhSSZOCGrhSHCx98+cAgrb71tirXXsiBMXKeGhnJ05KopHtPRJVxBvd3d3Kee957y86g1Sbho1XxwWsrzVu5E7YZS+NkJycHkiUseMKeQ+tMLbPoFZPu5EqrrsSWuDjb7XNUjJViyGaOtvL2SQ9QtvDu006fe4m0VVw71ycSt2ReAmlA+EgCFsyLYBIoAhlk7k+lKyYO3a/8E0bzltB6MGaRaGJMB0C9pSxAfGSmSpIi2OUy0YIpIoDoQ==","P":"ydx6DVoXf3DgS6WZtrR82xNf12kLD5cGUToPwwIjjX5oQywOhGXOV4GCrqISDff2bosrPvleBfuJ5KH9KRVAaEjh1At554Bq+Nw8cc/1mTXEOSKENDtA9GjkpthR0QW1FDFRR5Tc8sRuoBpulN1rJIDIkfEkqwlpugFmk2UrDk8=","Q":"+XNIV8qMorQ11C1fVj4L91wufF4NqVqCdm/PN3f+xZ5UWoiCOil+njRuIL09ZifEwy3fgqD06Fu/SvaqMODyKAzA+RMUJU0sk92aOzAhKiGBk38sEvEuDUKZYNJm5NLjo9XXBG8DQzSUPvmIFLaMCloA95Ozie0mJcrXcimCww8="}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs deleted file mode 100644 index 55e1a05c..00000000 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Authentication.Handler; -using Ocelot.Authentication.Handler.Creator; -using Ocelot.Authentication.Handler.Factory; -using Ocelot.Configuration.Builder; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using AuthenticationOptions = Ocelot.Configuration.AuthenticationOptions; - -namespace Ocelot.UnitTests.Authentication -{ - public class AuthenticationHandlerFactoryTests - { - private readonly IAuthenticationHandlerFactory _authenticationHandlerFactory; - private readonly Mock _app; - private readonly Mock _creator; - private AuthenticationOptions _authenticationOptions; - private Response _result; - - public AuthenticationHandlerFactoryTests() - { - _app = new Mock(); - _creator = new Mock(); - _authenticationHandlerFactory = new AuthenticationHandlerFactory(_creator.Object); - } - - [Theory] - [InlineData("IdentityServer")] - [InlineData("Jwt")] - public void should_return_access_token_handler(string provider) - { - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider(provider) - .Build(); - - this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) - .And(x => x.GivenTheCreatorReturns()) - .When(x => x.WhenIGetFromTheFactory()) - .Then(x => x.ThenTheHandlerIsReturned(provider)) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_cannot_create_handler() - { - var authenticationOptions = new AuthenticationOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) - .And(x => x.GivenTheCreatorReturnsAnError()) - .When(x => x.WhenIGetFromTheFactory()) - .Then(x => x.ThenAnErrorResponseIsReturned()) - .BDDfy(); - } - - private void GivenTheAuthenticationOptionsAre(AuthenticationOptions authenticationOptions) - { - _authenticationOptions = authenticationOptions; - } - - private void GivenTheCreatorReturnsAnError() - { - _creator - .Setup(x => x.Create(It.IsAny(), It.IsAny())) - .Returns(new ErrorResponse(new List - { - new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx") - })); - } - - private void GivenTheCreatorReturns() - { - _creator - .Setup(x => x.Create(It.IsAny(), It.IsAny())) - .Returns(new OkResponse(x => Task.CompletedTask)); - } - - private void WhenIGetFromTheFactory() - { - _result = _authenticationHandlerFactory.Get(_app.Object, _authenticationOptions); - } - - private void ThenTheHandlerIsReturned(string expected) - { - _result.Data.Provider.ShouldBe(expected); - } - - private void ThenAnErrorResponseIsReturned() - { - _result.IsError.ShouldBeTrue(); - } - } -} diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index 819c48d7..e6bcc500 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -2,11 +2,9 @@ { using System.Collections.Generic; using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; - using Ocelot.Authentication.Handler.Factory; using Ocelot.Authentication.Middleware; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; @@ -19,13 +17,10 @@ public class AuthenticationMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock _authFactory; private OkResponse _downstreamRoute; public AuthenticationMiddlewareTests() { - _authFactory = new Mock(); - GivenTheTestServerIsConfigured(); } @@ -45,7 +40,6 @@ { services.AddSingleton(); services.AddLogging(); - services.AddSingleton(_authFactory.Object); services.AddSingleton(ScopedRepository.Object); } diff --git a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs index 7e2108ee..a4fe8a16 100644 --- a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs @@ -1,133 +1,133 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; +// using System.Collections.Generic; +// using Ocelot.Configuration; +// using Ocelot.Configuration.Builder; +// using Ocelot.Configuration.Creator; +// using Ocelot.Configuration.File; +// using Shouldly; +// using TestStack.BDDfy; +// using Xunit; -namespace Ocelot.UnitTests.Configuration -{ - public class AuthenticationOptionsCreatorTests - { - private readonly AuthenticationOptionsCreator _authOptionsCreator; - private FileReRoute _fileReRoute; - private AuthenticationOptions _result; +// namespace Ocelot.UnitTests.Configuration +// { +// public class AuthenticationOptionsCreatorTests +// { +// private readonly AuthenticationOptionsCreator _authOptionsCreator; +// private FileReRoute _fileReRoute; +// private AuthenticationOptions _result; - public AuthenticationOptionsCreatorTests() - { - _authOptionsCreator = new AuthenticationOptionsCreator(new AuthenticationProviderConfigCreator()); - } +// public AuthenticationOptionsCreatorTests() +// { +// _authOptionsCreator = new AuthenticationOptionsCreator(new AuthenticationProviderConfigCreator()); +// } - [Fact] - public void should_return_auth_options() - { - var fileReRoute = new FileReRoute() - { - AuthenticationOptions = new FileAuthenticationOptions - { - Provider = "Geoff", - IdentityServerConfig = new FileIdentityServerConfig() - { - ProviderRootUrl = "http://www.bbc.co.uk/", - ApiName = "Laura", - RequireHttps = true, - ApiSecret = "secret" - }, - AllowedScopes = new List { "cheese" }, +// [Fact] +// public void should_return_auth_options() +// { +// var fileReRoute = new FileReRoute() +// { +// AuthenticationOptions = new FileAuthenticationOptions +// { +// Provider = "Geoff", +// IdentityServerConfig = new FileIdentityServerConfig() +// { +// ProviderRootUrl = "http://www.bbc.co.uk/", +// ApiName = "Laura", +// RequireHttps = true, +// ApiSecret = "secret" +// }, +// AllowedScopes = new List { "cheese" }, - } - }; +// } +// }; - var authenticationConfig = new IdentityServerConfigBuilder() - .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ProviderRootUrl) - .WithApiName(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiName) - .WithRequireHttps(fileReRoute.AuthenticationOptions.IdentityServerConfig.RequireHttps) - .WithApiSecret(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiSecret) - .Build(); +// var authenticationConfig = new IdentityServerConfigBuilder() +// .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ProviderRootUrl) +// .WithApiName(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiName) +// .WithRequireHttps(fileReRoute.AuthenticationOptions.IdentityServerConfig.RequireHttps) +// .WithApiSecret(fileReRoute.AuthenticationOptions?.IdentityServerConfig?.ApiSecret) +// .Build(); - var expected = new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithConfig(authenticationConfig) - .Build(); +// var expected = new AuthenticationOptionsBuilder() +// .WithProvider(fileReRoute.AuthenticationOptions?.Provider) +// .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) +// .WithConfig(authenticationConfig) +// .Build(); - this.Given(x => x.GivenTheFollowing(fileReRoute)) - .When(x => x.WhenICreateTheAuthenticationOptions()) - .Then(x => x.ThenTheFollowingIdentityServerConfigIsReturned(expected)) - .BDDfy(); - } +// this.Given(x => x.GivenTheFollowing(fileReRoute)) +// .When(x => x.WhenICreateTheAuthenticationOptions()) +// .Then(x => x.ThenTheFollowingIdentityServerConfigIsReturned(expected)) +// .BDDfy(); +// } - [Fact] - public void should_return_Jwt_auth_options() - { - var fileReRoute = new FileReRoute() - { - AuthenticationOptions = new FileAuthenticationOptions - { - Provider = "Jwt", - JwtConfig = new FileJwtConfig() - { - Audience = "Audience", - Authority = "Authority" - }, - AllowedScopes = new List { "cheese" } - } - }; +// [Fact] +// public void should_return_Jwt_auth_options() +// { +// var fileReRoute = new FileReRoute() +// { +// AuthenticationOptions = new FileAuthenticationOptions +// { +// Provider = "Jwt", +// JwtConfig = new FileJwtConfig() +// { +// Audience = "Audience", +// Authority = "Authority" +// }, +// AllowedScopes = new List { "cheese" } +// } +// }; - var authenticationConfig = new JwtConfigBuilder() - .WithAudience(fileReRoute.AuthenticationOptions?.JwtConfig?.Audience) - .WithAuthority(fileReRoute.AuthenticationOptions?.JwtConfig?.Authority) - .Build(); +// var authenticationConfig = new JwtConfigBuilder() +// .WithAudience(fileReRoute.AuthenticationOptions?.JwtConfig?.Audience) +// .WithAuthority(fileReRoute.AuthenticationOptions?.JwtConfig?.Authority) +// .Build(); - var expected = new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) - .WithConfig(authenticationConfig) - .Build(); +// var expected = new AuthenticationOptionsBuilder() +// .WithProvider(fileReRoute.AuthenticationOptions?.Provider) +// .WithAllowedScopes(fileReRoute.AuthenticationOptions?.AllowedScopes) +// .WithConfig(authenticationConfig) +// .Build(); - this.Given(x => x.GivenTheFollowing(fileReRoute)) - .When(x => x.WhenICreateTheAuthenticationOptions()) - .Then(x => x.ThenTheFollowingJwtConfigIsReturned(expected)) - .BDDfy(); - } +// this.Given(x => x.GivenTheFollowing(fileReRoute)) +// .When(x => x.WhenICreateTheAuthenticationOptions()) +// .Then(x => x.ThenTheFollowingJwtConfigIsReturned(expected)) +// .BDDfy(); +// } - private void GivenTheFollowing(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } +// private void GivenTheFollowing(FileReRoute fileReRoute) +// { +// _fileReRoute = fileReRoute; +// } - private void WhenICreateTheAuthenticationOptions() - { - _result = _authOptionsCreator.Create(_fileReRoute); - } +// private void WhenICreateTheAuthenticationOptions() +// { +// _result = _authOptionsCreator.Create(_fileReRoute); +// } - private void ThenTheFollowingJwtConfigIsReturned(AuthenticationOptions expected) - { - _result.AllowedScopes.ShouldBe(expected.AllowedScopes); - _result.Provider.ShouldBe(expected.Provider); +// private void ThenTheFollowingJwtConfigIsReturned(AuthenticationOptions expected) +// { +// _result.AllowedScopes.ShouldBe(expected.AllowedScopes); +// _result.Provider.ShouldBe(expected.Provider); - var _resultSettings = _result.Config as JwtConfig; - var expectedSettngs = expected.Config as JwtConfig; +// var _resultSettings = _result.Config as JwtConfig; +// var expectedSettngs = expected.Config as JwtConfig; - _resultSettings.Audience.ShouldBe(expectedSettngs.Audience); - _resultSettings.Authority.ShouldBe(expectedSettngs.Authority); +// _resultSettings.Audience.ShouldBe(expectedSettngs.Audience); +// _resultSettings.Authority.ShouldBe(expectedSettngs.Authority); - } +// } - private void ThenTheFollowingIdentityServerConfigIsReturned(AuthenticationOptions expected) - { - _result.AllowedScopes.ShouldBe(expected.AllowedScopes); - _result.Provider.ShouldBe(expected.Provider); +// private void ThenTheFollowingIdentityServerConfigIsReturned(AuthenticationOptions expected) +// { +// _result.AllowedScopes.ShouldBe(expected.AllowedScopes); +// _result.Provider.ShouldBe(expected.Provider); - var _resultSettings = _result.Config as IdentityServerConfig; - var expectedSettngs = expected.Config as IdentityServerConfig; +// var _resultSettings = _result.Config as IdentityServerConfig; +// var expectedSettngs = expected.Config as IdentityServerConfig; - _resultSettings.ProviderRootUrl.ShouldBe(expectedSettngs.ProviderRootUrl); - _resultSettings.RequireHttps.ShouldBe(expectedSettngs.RequireHttps); - _resultSettings.ApiName.ShouldBe(expectedSettngs.ApiName); - _resultSettings.ApiSecret.ShouldBe(expectedSettngs.ApiSecret); - } - } -} \ No newline at end of file +// _resultSettings.ProviderRootUrl.ShouldBe(expectedSettngs.ProviderRootUrl); +// _resultSettings.RequireHttps.ShouldBe(expectedSettngs.RequireHttps); +// _resultSettings.ApiName.ShouldBe(expectedSettngs.ApiName); +// _resultSettings.ApiSecret.ShouldBe(expectedSettngs.ApiSecret); +// } +// } +// } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 382471c6..77d1e278 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -1,4 +1,11 @@ using System.Collections.Generic; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; using Ocelot.Configuration.File; using Ocelot.Configuration.Validator; using Ocelot.Responses; @@ -13,10 +20,12 @@ namespace Ocelot.UnitTests.Configuration private readonly IConfigurationValidator _configurationValidator; private FileConfiguration _fileConfiguration; private Response _result; + private Mock _provider; public ConfigurationValidationTests() { - _configurationValidator = new FileConfigurationValidator(); + _provider = new Mock(); + _configurationValidator = new FileConfigurationValidator(_provider.Object); } [Fact] @@ -48,7 +57,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "http://asdf.com" + UpstreamPathTemplate = "/asdf/" } } })) @@ -57,6 +66,44 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] + public void configuration_is_invalid_without_slash_prefix_downstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products/", + UpstreamPathTemplate = "/asdf/" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_without_slash_prefix_upstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "api/prod/", + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .BDDfy(); + } + [Fact] public void configuration_is_valid_with_valid_authentication_provider() { @@ -67,14 +114,15 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "http://asdf.com", - AuthenticationOptions = new FileAuthenticationOptions + UpstreamPathTemplate = "/asdf/", + AuthenticationOptions = new FileAuthenticationOptions() { - Provider = "IdentityServer" + AuthenticationProviderKey = "Test" } } } })) + .And(x => x.GivenTheAuthSchemeExists("Test")) .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsValid()) .BDDfy(); @@ -90,12 +138,11 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "http://asdf.com", - AuthenticationOptions = new FileAuthenticationOptions + UpstreamPathTemplate = "/asdf/", + AuthenticationOptions = new FileAuthenticationOptions() { - Provider = "BootyBootyBottyRockinEverywhere" - } - } + AuthenticationProviderKey = "Test" + } } } })) .When(x => x.WhenIValidateTheConfiguration()) @@ -114,12 +161,12 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "http://asdf.com" + UpstreamPathTemplate = "/asdf/" }, new FileReRoute { DownstreamPathTemplate = "http://www.bbc.co.uk", - UpstreamPathTemplate = "http://asdf.com" + UpstreamPathTemplate = "/asdf/" } } })) @@ -136,7 +183,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIValidateTheConfiguration() { - _result = _configurationValidator.IsValid(_fileConfiguration); + _result = _configurationValidator.IsValid(_fileConfiguration).Result; } private void ThenTheResultIsValid() @@ -153,5 +200,31 @@ namespace Ocelot.UnitTests.Configuration { _result.Data.Errors[0].ShouldBeOfType(); } + + private void GivenTheAuthSchemeExists(string name) + { + _provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List + { + new AuthenticationScheme(name, name, typeof(TestHandler)) + }); + } + + private class TestOptions : AuthenticationSchemeOptions + { + } + + private class TestHandler : AuthenticationHandler + { + public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected override Task HandleAuthenticateAsync() + { + var principal = new ClaimsPrincipal(); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); + } + } + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4cb53118..d897bf64 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -1,714 +1,730 @@ -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - using System.Collections; - - using Ocelot.UnitTests.TestData; - - public class FileConfigurationCreatorTests - { - private readonly Mock> _fileConfig; - private readonly Mock _validator; - private Response _config; - private FileConfiguration _fileConfiguration; - private readonly Mock _logger; - private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; - private readonly Mock _loadBalancerFactory; - private readonly Mock _loadBalancerHouse; - private readonly Mock _loadBalancer; - private readonly Mock _qosProviderFactory; - private readonly Mock _qosProviderHouse; - private readonly Mock _qosProvider; - private Mock _claimsToThingCreator; - private Mock _authOptionsCreator; - private Mock _upstreamTemplatePatternCreator; - private Mock _requestIdKeyCreator; - private Mock _serviceProviderConfigCreator; - private Mock _qosOptionsCreator; - private Mock _fileReRouteOptionsCreator; - private Mock _rateLimitOptions; - private Mock _regionCreator; - - public FileConfigurationCreatorTests() - { - _qosProviderFactory = new Mock(); - _qosProviderHouse = new Mock(); - _qosProvider = new Mock(); - _logger = new Mock(); - _validator = new Mock(); - _fileConfig = new Mock>(); - _loadBalancerFactory = new Mock(); - _loadBalancerHouse = new Mock(); - _loadBalancer = new Mock(); - _claimsToThingCreator = new Mock(); - _authOptionsCreator = new Mock(); - _upstreamTemplatePatternCreator = new Mock(); - _requestIdKeyCreator = new Mock(); - _serviceProviderConfigCreator = new Mock(); - _qosOptionsCreator = new Mock(); - _fileReRouteOptionsCreator = new Mock(); - _rateLimitOptions = new Mock(); - _regionCreator = new Mock(); - - _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( - _fileConfig.Object, _validator.Object, _logger.Object, - _loadBalancerFactory.Object, _loadBalancerHouse.Object, - _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, - _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, - _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, - _rateLimitOptions.Object, _regionCreator.Object); - } - - [Fact] - public void should_call_region_creator() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - Region = "region" - } - } - }, - })) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingRegionIsReturned("region")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) - .BDDfy(); - } - - private void GivenTheFollowingRegionIsReturned(string region) - { - _regionCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(region); - } - - private void ThenTheRegionCreatorIsCalledCorrectly(string expected) - { - _regionCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); - } - - [Fact] - public void should_call_rate_limit_options_creator() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_call_qos_options_creator() - { - var expected = new QoSOptionsBuilder() - .WithDurationOfBreak(1) - .WithExceptionsAllowedBeforeBreaking(1) - .WithTimeoutValue(1) - .Build(); - - var serviceOptions = new ReRouteOptionsBuilder() - .WithIsQos(true) - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 1, - DurationOfBreak = 1, - ExceptionsAllowedBeforeBreaking = 1 - } - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) - .And(x => x.GivenTheQosProviderFactoryReturns()) - .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheQosOptionsAre(expected)) - .And(x => x.TheQosProviderFactoryIsCalledCorrectly()) - .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_create_load_balancer() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheLoadBalancerFactoryReturns()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) - .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_use_downstream_host() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamHost("127.0.0.1") - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_use_downstream_scheme() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamScheme = "https", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamScheme("https") - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_use_service_discovery_for_downstream_service_host() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false, - ServiceName = "ProductService" - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Provider = "consul", - Host = "127.0.0.1" - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() - .WithUseServiceDiscovery(true) - .WithServiceDiscoveryProvider("consul") - .WithServiceDiscoveryProviderHost("127.0.0.1") - .WithServiceName("ProductService") - .Build()) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false, - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() - .WithUseServiceDiscovery(false) - .Build()) - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_call_template_pattern_creator_correctly() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = false - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_call_request_id_creator() - { - var reRouteOptions = new ReRouteOptionsBuilder() - .Build(); - - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - ReRouteIsCaseSensitive = true - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "blahhhh" - } - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithRequestIdKey("blahhhh") - .Build() - })) - .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Theory] - [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] - public void should_create_with_headers_to_extract(string provider, IAuthenticationConfig config, FileConfiguration fileConfig) - { - var reRouteOptions = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .Build(); - - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider(provider) - .WithAllowedScopes(new List()) - .WithConfig(config) - .Build(); - - var expected = new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithAuthenticationOptions(authenticationOptions) - .WithClaimsToHeaders(new List - { - new ClaimToThing("CustomerId", "CustomerId", "", 0), - }) - .Build() - }; - - this.Given(x => x.GivenTheConfigIs(fileConfig)) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheClaimsToThingCreatorReturns(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0) })) - .And(x => x.GivenTheLoadBalancerFactoryReturns()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(provider, expected)) - .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - [Theory] - [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] - public void should_create_with_authentication_properties(string provider, IAuthenticationConfig config, FileConfiguration fileConfig) - { - var reRouteOptions = new ReRouteOptionsBuilder() - .WithIsAuthenticated(true) - .Build(); - - var authenticationOptions = new AuthenticationOptionsBuilder() - .WithProvider(provider) - .WithAllowedScopes(new List()) - .WithConfig(config) - .Build(); - - var expected = new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithAuthenticationOptions(authenticationOptions) - .Build() - }; - - this.Given(x => x.GivenTheConfigIs(fileConfig)) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) - .And(x => x.GivenTheLoadBalancerFactoryReturns()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(expected)) - .And(x => x.ThenTheAuthenticationOptionsAre(provider, expected)) - .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions) - { - _fileReRouteOptionsCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(fileReRouteOptions); - } - - private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() - { - _rateLimitOptions - .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - private void GivenTheConfigIsValid() - { - _validator - .Setup(x => x.IsValid(It.IsAny())) - .Returns(new OkResponse(new ConfigurationValidationResult(false))); - } - - private void GivenTheConfigIs(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - _fileConfig - .Setup(x => x.Value) - .Returns(_fileConfiguration); - } - - private void WhenICreateTheConfig() - { - _config = _ocelotConfigurationCreator.Create().Result; - } - - private void ThenTheReRoutesAre(List expectedReRoutes) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i]; - var expected = expectedReRoutes[i]; - - result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); - result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); - result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); - result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); - result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); - result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); - result.RequestIdKey.ShouldBe(expected.RequestIdKey); - - } - } - - private void ThenTheServiceConfigurationIs(ServiceProviderConfiguration expected) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i]; - result.ServiceProviderConfiguraion.DownstreamHost.ShouldBe(expected.DownstreamHost); - result.ServiceProviderConfiguraion.DownstreamPort.ShouldBe(expected.DownstreamPort); - result.ServiceProviderConfiguraion.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider); - result.ServiceProviderConfiguraion.ServiceName.ShouldBe(expected.ServiceName); - result.ServiceProviderConfiguraion.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); - result.ServiceProviderConfiguraion.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); - } - } - - private void ThenTheAuthenticationOptionsAre(string provider, List expectedReRoutes) - { - for (int i = 0; i < _config.Data.ReRoutes.Count; i++) - { - var result = _config.Data.ReRoutes[i].AuthenticationOptions; - var expected = expectedReRoutes[i].AuthenticationOptions; - - result.AllowedScopes.ShouldBe(expected.AllowedScopes); - result.Provider.ShouldBe(expected.Provider); - - if (provider.ToLower() == "identityserver") - { - var config = result.Config as IdentityServerConfig; - var expectedConfig = expected.Config as IdentityServerConfig; - - config.ProviderRootUrl.ShouldBe(expectedConfig.ProviderRootUrl); - config.RequireHttps.ShouldBe(expectedConfig.RequireHttps); - config.ApiName.ShouldBe(expectedConfig.ApiName); - config.ApiSecret.ShouldBe(expectedConfig.ApiSecret); - } - else - { - var config = result.Config as JwtConfig; - var expectedConfig = expected.Config as JwtConfig; - - config.Audience.ShouldBe(expectedConfig.Audience); - config.Authority.ShouldBe(expectedConfig.Authority); - } - } - } - - private void GivenTheLoadBalancerFactoryReturns() - { - _loadBalancerFactory - .Setup(x => x.Get(It.IsAny())) - .ReturnsAsync(_loadBalancer.Object); - } - - private void TheLoadBalancerFactoryIsCalledCorrectly() - { - _loadBalancerFactory - .Verify(x => x.Get(It.IsAny()), Times.Once); - } - - private void ThenTheLoadBalancerHouseIsCalledCorrectly() - { - _loadBalancerHouse - .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); - } - - private void GivenTheQosProviderFactoryReturns() - { - _qosProviderFactory - .Setup(x => x.Get(It.IsAny())) - .Returns(_qosProvider.Object); - } - - private void TheQosProviderFactoryIsCalledCorrectly() - { - _qosProviderFactory - .Verify(x => x.Get(It.IsAny()), Times.Once); - } - - private void ThenTheQosProviderHouseIsCalledCorrectly() - { - _qosProviderHouse - .Verify(x => x.Add(It.IsAny(), _qosProvider.Object), Times.Once); - } - - private void GivenTheClaimsToThingCreatorReturns(List claimsToThing) - { - _claimsToThingCreator - .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest)) - .Returns(claimsToThing); - } - - private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions) - { - _authOptionsCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(authOptions); - } - - private void ThenTheAuthOptionsCreatorIsCalledCorrectly() - { - _authOptionsCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); - } - - private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern) - { - _upstreamTemplatePatternCreator - .Setup(x => x.Create(It.IsAny())) - .Returns(pattern); - } - - private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() - { - _requestIdKeyCreator - .Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once); - } - - private void GivenTheRequestIdCreatorReturns(string requestId) - { - _requestIdKeyCreator - .Setup(x => x.Create(It.IsAny(), It.IsAny())) - .Returns(requestId); - } - - private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions) - { - _qosOptionsCreator - .Setup(x => x.Create(_fileConfiguration.ReRoutes[0])) - .Returns(qosOptions); - } - - private void ThenTheQosOptionsAre(QoSOptions qosOptions) - { - _config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); - - _config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); - _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); - } - } -} +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Ocelot.Cache; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Validator; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections; + + using Ocelot.UnitTests.TestData; + + public class FileConfigurationCreatorTests + { + private readonly Mock> _fileConfig; + private readonly Mock _validator; + private Response _config; + private FileConfiguration _fileConfiguration; + private readonly Mock _logger; + private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; + private readonly Mock _loadBalancerFactory; + private readonly Mock _loadBalancerHouse; + private readonly Mock _loadBalancer; + private readonly Mock _qosProviderFactory; + private readonly Mock _qosProviderHouse; + private readonly Mock _qosProvider; + private Mock _claimsToThingCreator; + private Mock _authOptionsCreator; + private Mock _upstreamTemplatePatternCreator; + private Mock _requestIdKeyCreator; + private Mock _serviceProviderConfigCreator; + private Mock _qosOptionsCreator; + private Mock _fileReRouteOptionsCreator; + private Mock _rateLimitOptions; + private Mock _regionCreator; + private Mock _httpHandlerOptionsCreator; + + public FileConfigurationCreatorTests() + { + _qosProviderFactory = new Mock(); + _qosProviderHouse = new Mock(); + _qosProvider = new Mock(); + _logger = new Mock(); + _validator = new Mock(); + _fileConfig = new Mock>(); + _loadBalancerFactory = new Mock(); + _loadBalancerHouse = new Mock(); + _loadBalancer = new Mock(); + _claimsToThingCreator = new Mock(); + _authOptionsCreator = new Mock(); + _upstreamTemplatePatternCreator = new Mock(); + _requestIdKeyCreator = new Mock(); + _serviceProviderConfigCreator = new Mock(); + _qosOptionsCreator = new Mock(); + _fileReRouteOptionsCreator = new Mock(); + _rateLimitOptions = new Mock(); + _regionCreator = new Mock(); + _httpHandlerOptionsCreator = new Mock(); + + _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( + _fileConfig.Object, _validator.Object, _logger.Object, + _loadBalancerFactory.Object, _loadBalancerHouse.Object, + _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, + _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, + _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, + _rateLimitOptions.Object, _regionCreator.Object, _httpHandlerOptionsCreator.Object); + } + + [Fact] + public void should_call_region_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + Region = "region" + } + } + }, + })) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingRegionIsReturned("region")) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) + .BDDfy(); + } + + private void GivenTheFollowingRegionIsReturned(string region) + { + _regionCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(region); + } + + private void ThenTheRegionCreatorIsCalledCorrectly(string expected) + { + _regionCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); + } + + [Fact] + public void should_call_rate_limit_options_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_call_qos_options_creator() + { + var expected = new QoSOptionsBuilder() + .WithDurationOfBreak(1) + .WithExceptionsAllowedBeforeBreaking(1) + .WithTimeoutValue(1) + .Build(); + + var serviceOptions = new ReRouteOptionsBuilder() + .WithIsQos(true) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + DurationOfBreak = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) + .And(x => x.GivenTheQosProviderFactoryReturns()) + .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheQosOptionsAre(expected)) + .And(x => x.TheQosProviderFactoryIsCalledCorrectly()) + .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_create_load_balancer() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) + .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_use_downstream_host() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_use_downstream_scheme() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "https", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamScheme("https") + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_use_service_discovery_for_downstream_service_host() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = false, + ServiceName = "ProductService" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Host = "127.0.0.1" + } + } + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("consul") + .WithServiceDiscoveryProviderHost("127.0.0.1") + .WithServiceName("ProductService") + .Build()) + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = false, + } + } + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() + .WithUseServiceDiscovery(false) + .Build()) + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_call_template_pattern_creator_correctly() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = false + } + } + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_call_request_id_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" }, + ReRouteIsCaseSensitive = true + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "blahhhh" + } + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithRequestIdKey("blahhhh") + .Build() + })) + .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_call_httpHandler_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + var httpHandlerOptions = new HttpHandlerOptions(true, true); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = new List { "Get" } + } + }, + })) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingHttpHandlerOptionsAreReturned(httpHandlerOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheHttpHandlerOptionsCreatorIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheFollowingHttpHandlerOptionsAreReturned(HttpHandlerOptions httpHandlerOptions) + { + _httpHandlerOptionsCreator.Setup(x => x.Create(It.IsAny())) + .Returns(httpHandlerOptions); + } + + private void ThenTheHttpHandlerOptionsCreatorIsCalledCorrectly() + { + _httpHandlerOptionsCreator.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once()); + } + + [Theory] + [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] + public void should_create_with_headers_to_extract(FileConfiguration fileConfig) + { + var reRouteOptions = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .Build(); + + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithAllowedScopes(new List()) + .Build(); + + var expected = new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithAuthenticationOptions(authenticationOptions) + .WithClaimsToHeaders(new List + { + new ClaimToThing("CustomerId", "CustomerId", "", 0), + }) + .Build() + }; + + this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheClaimsToThingCreatorReturns(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0) })) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(expected)) + .And(x => x.ThenTheAuthenticationOptionsAre(expected)) + .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) + .BDDfy(); + } + + [Theory] + [MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))] + public void should_create_with_authentication_properties(FileConfiguration fileConfig) + { + var reRouteOptions = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .Build(); + + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithAllowedScopes(new List()) + .Build(); + + var expected = new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithAuthenticationOptions(authenticationOptions) + .Build() + }; + + this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(expected)) + .And(x => x.ThenTheAuthenticationOptionsAre(expected)) + .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions) + { + _fileReRouteOptionsCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(fileReRouteOptions); + } + + private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() + { + _rateLimitOptions + .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + private void GivenTheConfigIsValid() + { + _validator + .Setup(x => x.IsValid(It.IsAny())) + .ReturnsAsync(new OkResponse(new ConfigurationValidationResult(false))); + } + + private void GivenTheConfigIs(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + _fileConfig + .Setup(x => x.Value) + .Returns(_fileConfiguration); + } + + private void WhenICreateTheConfig() + { + _config = _ocelotConfigurationCreator.Create().Result; + } + + private void ThenTheReRoutesAre(List expectedReRoutes) + { + for (int i = 0; i < _config.Data.ReRoutes.Count; i++) + { + var result = _config.Data.ReRoutes[i]; + var expected = expectedReRoutes[i]; + + result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); + result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); + result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); + result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); + result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); + result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); + result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); + result.RequestIdKey.ShouldBe(expected.RequestIdKey); + + } + } + + private void ThenTheServiceConfigurationIs(ServiceProviderConfiguration expected) + { + for (int i = 0; i < _config.Data.ReRoutes.Count; i++) + { + var result = _config.Data.ReRoutes[i]; + result.ServiceProviderConfiguraion.DownstreamHost.ShouldBe(expected.DownstreamHost); + result.ServiceProviderConfiguraion.DownstreamPort.ShouldBe(expected.DownstreamPort); + result.ServiceProviderConfiguraion.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider); + result.ServiceProviderConfiguraion.ServiceName.ShouldBe(expected.ServiceName); + result.ServiceProviderConfiguraion.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); + result.ServiceProviderConfiguraion.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); + } + } + + private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) + { + for (int i = 0; i < _config.Data.ReRoutes.Count; i++) + { + var result = _config.Data.ReRoutes[i].AuthenticationOptions; + var expected = expectedReRoutes[i].AuthenticationOptions; + result.AllowedScopes.ShouldBe(expected.AllowedScopes); + } + } + + private void GivenTheLoadBalancerFactoryReturns() + { + _loadBalancerFactory + .Setup(x => x.Get(It.IsAny())) + .ReturnsAsync(_loadBalancer.Object); + } + + private void TheLoadBalancerFactoryIsCalledCorrectly() + { + _loadBalancerFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheLoadBalancerHouseIsCalledCorrectly() + { + _loadBalancerHouse + .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); + } + + private void GivenTheQosProviderFactoryReturns() + { + _qosProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_qosProvider.Object); + } + + private void TheQosProviderFactoryIsCalledCorrectly() + { + _qosProviderFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheQosProviderHouseIsCalledCorrectly() + { + _qosProviderHouse + .Verify(x => x.Add(It.IsAny(), _qosProvider.Object), Times.Once); + } + + private void GivenTheClaimsToThingCreatorReturns(List claimsToThing) + { + _claimsToThingCreator + .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest)) + .Returns(claimsToThing); + } + + private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions) + { + _authOptionsCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(authOptions); + } + + private void ThenTheAuthOptionsCreatorIsCalledCorrectly() + { + _authOptionsCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); + } + + private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern) + { + _upstreamTemplatePatternCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(pattern); + } + + private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() + { + _requestIdKeyCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once); + } + + private void GivenTheRequestIdCreatorReturns(string requestId) + { + _requestIdKeyCreator + .Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(requestId); + } + + private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions) + { + _qosOptionsCreator + .Setup(x => x.Create(_fileConfiguration.ReRoutes[0])) + .Returns(qosOptions); + } + + private void ThenTheQosOptionsAre(QoSOptions qosOptions) + { + _config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); + + _config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); + _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs new file mode 100644 index 00000000..69f414e5 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -0,0 +1,71 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + + public class HttpHandlerOptionsCreatorTests + { + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private FileReRoute _fileReRoute; + private HttpHandlerOptions _httpHandlerOptions; + + public HttpHandlerOptionsCreatorTests() + { + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(); + } + + [Fact] + public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default() + { + var fileReRoute = new FileReRoute(); + var expectedOptions = new HttpHandlerOptions(true, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_specified_useCookie_and_allowAutoRedirect() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false, + UseCookieContainer = false + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateHttpHandlerOptions() + { + _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute); + } + + private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options) + { + _httpHandlerOptions.ShouldNotBeNull(); + _httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect); + _httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs index 79872ea1..3c1b7561 100644 --- a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs @@ -34,9 +34,9 @@ namespace Ocelot.UnitTests.Configuration ExceptionsAllowedBeforeBreaking = 1, TimeoutValue = 1 }, - AuthenticationOptions = new FileAuthenticationOptions + AuthenticationOptions = new FileAuthenticationOptions() { - Provider = "IdentityServer" + AuthenticationProviderKey = "Test" }, RouteClaimsRequirement = new Dictionary() { diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index 34db1232..21c5be38 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -28,7 +28,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("(?i)/PRODUCTS/.*/$")) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.*/$")) .BDDfy(); } @@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Configuration }; this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("/PRODUCTS/.*/$")) + .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.*/$")) .BDDfy(); } @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/$")) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.*/$")) .BDDfy(); } @@ -72,7 +72,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$")) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.*/variants/.*/$")) .BDDfy(); } [Fact] @@ -86,7 +86,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$")) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.*/variants/.*/$")) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 8514786d..8dbe6ef4 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_return_route() { - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( new OkResponse>( new List()))) @@ -65,6 +65,39 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + + [Fact] + public void should_append_slash_to_upstream_url_path() + { + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) + .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern("someUpstreamPath") + .Build() + }, string.Empty + )) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher/")) + .BDDfy(); + } + [Fact] public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() { @@ -137,7 +170,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_not_return_route() { - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath")) + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath/")) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -269,6 +302,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); } + private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) + { + _mockMatcher + .Verify(x => x.Match(expectedUpstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + } + private void ThenTheUrlMatcherIsNotCalled() { _mockMatcher diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 70dd747d..9f76228b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -28,6 +28,16 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher .BDDfy(); } + [Fact] + public void should_not_match_issue_134() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.*/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + [Fact] public void should_match_forward_slash_only_regex() { @@ -42,7 +52,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void should_find_match_when_template_smaller_than_valid_path() { this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("/api/products/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.*$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -52,7 +62,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void should_not_find_match() { this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsFalse()) .BDDfy(); @@ -62,7 +72,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url() { this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -72,7 +82,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_no_slash() { this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -82,7 +92,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_one_slash() { this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -92,7 +102,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -102,7 +112,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -112,7 +122,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/.*$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -122,7 +132,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*/categories/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -132,7 +142,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*/categories/.*/variant/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/.*$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -142,7 +152,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() { this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -152,7 +162,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void should_ignore_case_sensitivity() { this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("(?i)api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.*/categories/.*/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -162,7 +172,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void should_respect_case_sensitivity() { this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("api/product/products/.*/categories/.*/variant/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.*/categories/.*/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsFalse()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs new file mode 100644 index 00000000..eef56982 --- /dev/null +++ b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs @@ -0,0 +1,124 @@ +using Xunit; +using Shouldly; +using Ocelot.Authorisation; +using Ocelot.Infrastructure.Claims.Parser; +using Moq; +using System.Collections.Generic; +using System.Security.Claims; +using Ocelot.Responses; +using TestStack.BDDfy; +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class ScopesAuthoriserTests + { + private ScopesAuthoriser _authoriser; + public Mock _parser; + private ClaimsPrincipal _principal; + private List _allowedScopes; + private Response _result; + + public ScopesAuthoriserTests() + { + _parser = new Mock(); + _authoriser = new ScopesAuthoriser(_parser.Object); + } + + [Fact] + public void should_return_ok_if_no_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing(new List())) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + + [Fact] + public void should_return_ok_if_null_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing((List)null)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_claims_parser_returns_error() + { + var fakeError = new FakeError(); + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) + .And(_ => GivenTheFollowing(new List(){"doesntmatter"})) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + [Fact] + public void should_match_scopes_and_return_ok_result() + { + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List(){"someScope"}; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_not_match_scopes_and_return_error_result() + { + var fakeError = new FakeError(); + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List(){"someScope"}; + var userScopes = new List(){"anotherScope"}; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + private void GivenTheParserReturns(Response> response) + { + _parser.Setup(x => x.GetValuesByClaimType(It.IsAny>(), It.IsAny())).Returns(response); + } + + private void GivenTheFollowing(ClaimsPrincipal principal) + { + _principal = principal; + } + + private void GivenTheFollowing(List allowedScopes) + { + _allowedScopes = allowedScopes; + } + + private void WhenIAuthorise() + { + _result = _authoriser.Authorise(_principal, _allowedScopes); + } + + private void ThenTheFollowingIsReturned(Response expected) + { + _result.Data.ShouldBe(expected.Data); + _result.IsError.ShouldBe(expected.IsError); + } + } + + public class FakeError : Error + { + public FakeError() : base("fake error", OcelotErrorCode.CannotAddDataError) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 201ecccf..415fb07b 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -2,13 +2,13 @@ 0.0.0-dev - netcoreapp1.1 + netcoreapp2.0 + 2.0.0 Ocelot.UnitTests Ocelot.UnitTests Exe true osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - 1.1.1 false false false @@ -32,27 +32,22 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index 02f2b208..807c35a5 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -15,8 +15,9 @@ using TestStack.BDDfy; using Xunit; using Ocelot.Requester.QoS; - using Microsoft.AspNetCore.Builder; - + using Ocelot.Configuration; + using Microsoft.AspNetCore.Builder; + public class HttpRequestBuilderMiddlewareTests : ServerHostedMiddlewareTest { private readonly Mock _requestBuilder; @@ -50,12 +51,13 @@ new ReRouteBuilder() .WithRequestIdKey("LSRequestId") .WithUpstreamHttpMethod(new List { "Get" }) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true)) .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse(new NoQoSProvider()))) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider()))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider(), false, false))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); @@ -103,7 +105,11 @@ _request = new OkResponse(request); _requestBuilder - .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Build(It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) .ReturnsAsync(_request); } diff --git a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs index d831aed9..f4f67ca0 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestCreatorTests.cs @@ -15,6 +15,9 @@ private readonly bool _isQos; private readonly IQoSProvider _qoSProvider; private readonly HttpRequestMessage _requestMessage; + private readonly bool _useCookieContainer; + private readonly bool _allowAutoRedirect; + private Response _response; public HttpRequestCreatorTests() @@ -22,6 +25,9 @@ _requestCreator = new HttpRequestCreator(); _isQos = true; _qoSProvider = new NoQoSProvider(); + _useCookieContainer = false; + _allowAutoRedirect = false; + _requestMessage = new HttpRequestMessage(); } @@ -30,12 +36,19 @@ { this.When(x => x.WhenIBuildARequest()) .Then(x => x.ThenTheRequestContainsTheRequestMessage()) + .Then(x => x.ThenTheRequestContainsTheIsQos()) + .Then(x => x.ThenTheRequestContainsTheQosProvider()) + .Then(x => x.ThenTheRequestContainsUseCookieContainer()) + .Then(x => x.ThenTheRequestContainsAllowAutoRedirect()) .BDDfy(); } private void WhenIBuildARequest() { - _response = _requestCreator.Build(_requestMessage, _isQos, _qoSProvider).GetAwaiter().GetResult(); + _response = _requestCreator.Build(_requestMessage, + _isQos, _qoSProvider, _useCookieContainer, _allowAutoRedirect) + .GetAwaiter() + .GetResult(); } private void ThenTheRequestContainsTheRequestMessage() @@ -52,5 +65,15 @@ { _response.Data.QosProvider.ShouldBe(_qoSProvider); } + + private void ThenTheRequestContainsUseCookieContainer() + { + _response.Data.UseCookieContainer.ShouldBe(_useCookieContainer); + } + + private void ThenTheRequestContainsAllowAutoRedirect() + { + _response.Data.AllowAutoRedirect.ShouldBe(_allowAutoRedirect); + } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index f5570f15..45aafecd 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -28,7 +28,7 @@ [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider()))) + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider(), false, false))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index b0455e38..c1165bc4 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Responder [InlineData(OcelotErrorCode.RequestTimedOutError)] public void should_return_service_unavailable(OcelotErrorCode errorCode) { - ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.ServiceUnavailable); + ShouldMapErrorToStatusCode(OcelotErrorCode.RequestTimedOutError, HttpStatusCode.ServiceUnavailable); } @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(30, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(31, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) diff --git a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs index 5392a58d..ef9a454a 100644 --- a/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs +++ b/test/Ocelot.UnitTests/TestData/AuthenticationConfigTestData.cs @@ -1,8 +1,6 @@ namespace Ocelot.UnitTests.TestData { using System.Collections.Generic; - - using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; public class AuthenticationConfigTestData @@ -11,13 +9,6 @@ { yield return new object[] { - "IdentityServer", - new IdentityServerConfigBuilder() - .WithRequireHttps(true) - .WithApiName("test") - .WithApiSecret("test") - .WithProviderRootUrl("test") - .Build(), new FileConfiguration { ReRoutes = new List @@ -30,20 +21,13 @@ ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions { + AuthenticationProviderKey = "Test", AllowedScopes = new List(), - Provider = "IdentityServer", - IdentityServerConfig = new FileIdentityServerConfig - { - ProviderRootUrl = "http://localhost:51888", - RequireHttps = false, - ApiName = "api", - ApiSecret = "secret" - } }, AddHeadersToRequest = - { - { "CustomerId", "Claims[CustomerId] > value" }, - } + { + { "CustomerId", "Claims[CustomerId] > value" }, + } } } } @@ -51,11 +35,6 @@ yield return new object[] { - "Jwt", - new JwtConfigBuilder() - .WithAudience("a") - .WithAuthority("au") - .Build(), new FileConfiguration { ReRoutes = new List @@ -68,13 +47,8 @@ ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions { + AuthenticationProviderKey = "Test", AllowedScopes = new List(), - Provider = "IdentityServer", - JwtConfig = new FileJwtConfig - { - Audience = "a", - Authority = "au" - } }, AddHeadersToRequest = { diff --git a/tools/packages.config b/tools/packages.config index 70a1e599..747e13e6 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - +