From b74a1197a28879b1e02fa52b96a95003d06becb6 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 11 Dec 2020 09:54:08 +0000 Subject: [PATCH 1/4] +semver: upgrade to net5.0 (#1390) * breaking upgrade base build image to net5.0 * add make and build tools to image * fix code broken after net5.0 upgrade * fix warnings * fix tests and line endings * upgrade dotnet test and coverages packages * update circle build image * removed rafty and updated more packages * bring back develop * rename authorisation to authorization --- .circleci/config.yml | 17 +- .dockerignore | 6 +- Makefile | 45 +- Ocelot.sln | 7 - README.md | 2 +- build.cake | 1092 +++++----- docker/Dockerfile.base | 25 +- docker/Dockerfile.build | 30 +- docker/Dockerfile.release | 40 +- docker/docker-build.sh | 13 +- docs/building/releaseprocess.rst | 10 +- docs/features/administration.rst | 15 +- docs/features/authentication.rst | 10 +- .../{authorisation.rst => authorization.rst} | 6 +- docs/features/claimstransformation.rst | 2 +- docs/features/errorcodes.rst | 2 +- docs/features/middlewareinjection.rst | 4 +- docs/features/raft.rst | 49 - docs/features/requestid.rst | 4 +- docs/features/websockets.rst | 2 +- docs/index.rst | 3 +- .../AdministrationApi.csproj | 4 +- samples/Docker/Dockerfile | 1 - samples/OcelotBasic/OcelotBasic.csproj | 2 +- .../OcelotEureka/ApiGateway/ApiGateway.csproj | 2 +- .../DownstreamService.csproj | 2 +- samples/OcelotGraphQL/OcelotGraphQL.csproj | 6 +- samples/OcelotGraphQL/Program.cs | 263 +-- samples/OcelotGraphQL/README.md | 2 +- .../OcelotKube/ApiGateway/ApiGateway.csproj | 4 +- .../DownstreamService.csproj | 4 +- .../OcelotOpenTracing.csproj | 6 +- .../OcelotApplicationApiGateway.csproj | 8 +- .../OcelotApplicationService.csproj | 10 +- .../Ocelot.Administration.csproj | 78 +- .../OcelotBuilderExtensions.cs | 45 +- .../Ocelot.Cache.CacheManager.csproj | 78 +- .../Ocelot.Provider.Consul.csproj | 76 +- src/Ocelot.Provider.Eureka/Eureka.cs | 70 +- .../EurekaProviderFactory.cs | 42 +- .../Ocelot.Provider.Eureka.csproj | 5 +- .../Ocelot.Provider.Kubernetes.csproj | 86 +- .../Ocelot.Provider.Polly.csproj | 76 +- src/Ocelot.Provider.Rafty/BearerToken.cs | 16 - src/Ocelot.Provider.Rafty/FakeCommand.cs | 14 - src/Ocelot.Provider.Rafty/FilePeer.cs | 7 - src/Ocelot.Provider.Rafty/FilePeers.cs | 14 - .../FilePeersProvider.cs | 44 - src/Ocelot.Provider.Rafty/HttpPeer.cs | 130 -- .../Ocelot.Provider.Rafty.csproj | 40 - .../OcelotAdministrationBuilderExtensions.cs | 28 - .../OcelotFiniteStateMachine.cs | 25 - .../Properties/AssemblyInfo.cs | 18 - src/Ocelot.Provider.Rafty/RaftController.cs | 96 - .../RaftyFileConfigurationSetter.cs | 30 - .../RaftyMiddlewareConfigurationProvider.cs | 49 - src/Ocelot.Provider.Rafty/SqlLiteLog.cs | 334 --- .../UnableToSaveAcceptCommand.cs | 12 - .../UpdateFileConfiguration.cs | 15 - .../Ocelot.Tracing.Butterfly.csproj | 2 +- .../Ocelot.Tracing.OpenTracing.csproj | 2 +- .../ClaimValueNotAuthorisedError.cs | 13 - ...orisationMiddlewareMiddlewareExtensions.cs | 12 - .../Authorisation/ScopeNotAuthorisedError.cs | 12 - .../ClaimValueNotAuthorizedError.cs | 13 + .../ClaimsAuthorizer.cs} | 24 +- .../IClaimsAuthorizer.cs} | 10 +- .../IScopesAuthorizer.cs} | 8 +- .../Middleware/AuthorizationMiddleware.cs} | 66 +- ...orizationMiddlewareMiddlewareExtensions.cs | 12 + .../Authorization/ScopeNotAuthorizedError.cs | 12 + .../ScopesAuthorizer.cs} | 94 +- .../UnauthorizedError.cs} | 6 +- .../UserDoesNotHaveClaimError.cs | 8 +- .../Builder/DownstreamReRouteBuilder.cs | 8 +- .../Builder/RouteOptionsBuilder.cs | 8 +- .../Creator/RouteOptionsCreator.cs | 10 +- .../Configuration/Creator/RoutesCreator.cs | 2 +- src/Ocelot/Configuration/DownstreamRoute.cs | 6 +- src/Ocelot/Configuration/RouteOptions.cs | 6 +- .../DependencyInjection/OcelotBuilder.cs | 6 +- src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- .../Middleware/OcelotPipelineConfiguration.cs | 12 +- .../Middleware/OcelotPipelineExtensions.cs | 16 +- src/Ocelot/Ocelot.csproj | 10 +- .../Responder/ErrorsToHttpStatusCodeMapper.cs | 4 +- src/Ocelot/Responses/Response.cs | 38 +- .../AuthenticationTests.cs | 641 +++--- ...risationTests.cs => AuthorizationTests.cs} | 821 ++++---- .../ButterflyTracingTests.cs | 3 +- .../ClaimsToDownstreamPathTests.cs | 422 ++-- .../ClaimsToHeadersForwardingTests.cs | 363 ++-- .../ClaimsToQueryStringForwardingTests.cs | 495 ++--- test/Ocelot.AcceptanceTests/ContentTests.cs | 2 +- .../CustomMiddlewareTests.cs | 56 +- .../EurekaServiceDiscoveryTests.cs | 100 +- test/Ocelot.AcceptanceTests/HeaderTests.cs | 49 +- .../HttpClientCachingTests.cs | 9 +- .../Ocelot.AcceptanceTests.csproj | 153 +- .../OpenTracingTests.cs | 69 +- .../ServiceDiscoveryTests.cs | 7 +- test/Ocelot.AcceptanceTests/Steps.cs | 4 +- test/Ocelot.AcceptanceTests/WebSocketTests.cs | 8 +- .../Ocelot.Benchmarks.csproj | 6 +- .../AdministrationTests.cs | 1829 +++++++++-------- .../Ocelot.IntegrationTests.csproj | 121 +- test/Ocelot.IntegrationTests/RaftTests.cs | 513 ----- .../Ocelot.ManualTest.csproj | 18 +- .../OcelotAdministrationBuilderTests.cs | 201 +- ...sts.cs => AuthorizationMiddlewareTests.cs} | 30 +- ...riserTests.cs => ClaimsAuthorizerTests.cs} | 48 +- .../Configuration/RouteOptionsCreatorTests.cs | 4 +- .../Configuration/RoutesCreatorTests.cs | 2 +- .../ConsulServiceDiscoveryProviderTests.cs | 9 +- ...ekaMiddlewareConfigurationProviderTests.cs | 10 +- .../Eureka/EurekaProviderFactoryTests.cs | 2 +- .../EurekaServiceDiscoveryProviderTests.cs | 233 +-- .../Headers/AddHeadersToRequestPlainTests.cs | 255 +-- ...ttpHeadersTransformationMiddlewareTests.cs | 4 +- .../Infrastructure/HttpDataRepositoryTests.cs | 174 +- ...riserTests.cs => ScopesAuthorizerTests.cs} | 24 +- .../KubeServiceDiscoveryProviderTests.cs | 297 +-- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 192 +- ...lotAdministrationBuilderExtensionsTests.cs | 89 - .../Rafty/OcelotFiniteStateMachineTests.cs | 45 - .../RaftyFileConfigurationSetterTests.cs | 52 - .../Request/Mapper/RequestMapperTests.cs | 2 +- .../ErrorsToHttpStatusCodeMapperTests.cs | 14 +- test/Ocelot.UnitTests/UnitTests.runsettings | 44 +- tools/packages.config | 4 - 130 files changed, 4765 insertions(+), 6209 deletions(-) rename docs/features/{authorisation.rst => authorization.rst} (51%) delete mode 100644 docs/features/raft.rst delete mode 100644 src/Ocelot.Provider.Rafty/BearerToken.cs delete mode 100644 src/Ocelot.Provider.Rafty/FakeCommand.cs delete mode 100644 src/Ocelot.Provider.Rafty/FilePeer.cs delete mode 100644 src/Ocelot.Provider.Rafty/FilePeers.cs delete mode 100644 src/Ocelot.Provider.Rafty/FilePeersProvider.cs delete mode 100644 src/Ocelot.Provider.Rafty/HttpPeer.cs delete mode 100644 src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj delete mode 100644 src/Ocelot.Provider.Rafty/OcelotAdministrationBuilderExtensions.cs delete mode 100644 src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs delete mode 100644 src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs delete mode 100644 src/Ocelot.Provider.Rafty/RaftController.cs delete mode 100644 src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs delete mode 100644 src/Ocelot.Provider.Rafty/RaftyMiddlewareConfigurationProvider.cs delete mode 100644 src/Ocelot.Provider.Rafty/SqlLiteLog.cs delete mode 100644 src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs delete mode 100644 src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs delete mode 100644 src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs delete mode 100644 src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs delete mode 100644 src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs create mode 100644 src/Ocelot/Authorization/ClaimValueNotAuthorizedError.cs rename src/Ocelot/{Authorisation/ClaimsAuthoriser.cs => Authorization/ClaimsAuthorizer.cs} (86%) rename src/Ocelot/{Authorisation/IClaimsAuthoriser.cs => Authorization/IClaimsAuthorizer.cs} (74%) rename src/Ocelot/{Authorisation/IScopesAuthoriser.cs => Authorization/IScopesAuthorizer.cs} (50%) rename src/Ocelot/{Authorisation/Middleware/AuthorisationMiddleware.cs => Authorization/Middleware/AuthorizationMiddleware.cs} (58%) create mode 100644 src/Ocelot/Authorization/Middleware/AuthorizationMiddlewareMiddlewareExtensions.cs create mode 100644 src/Ocelot/Authorization/ScopeNotAuthorizedError.cs rename src/Ocelot/{Authorisation/ScopesAuthoriser.cs => Authorization/ScopesAuthorizer.cs} (78%) rename src/Ocelot/{Authorisation/UnauthorisedError.cs => Authorization/UnauthorizedError.cs} (50%) rename src/Ocelot/{Authorisation => Authorization}/UserDoesNotHaveClaimError.cs (84%) rename test/Ocelot.AcceptanceTests/{AuthorisationTests.cs => AuthorizationTests.cs} (60%) delete mode 100644 test/Ocelot.IntegrationTests/RaftTests.cs rename test/Ocelot.UnitTests/Authorization/{AuthorisationMiddlewareTests.cs => AuthorizationMiddlewareTests.cs} (78%) rename test/Ocelot.UnitTests/Authorization/{ClaimsAuthoriserTests.cs => ClaimsAuthorizerTests.cs} (75%) rename test/Ocelot.UnitTests/Infrastructure/{ScopesAuthoriserTests.cs => ScopesAuthorizerTests.cs} (87%) delete mode 100644 test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs delete mode 100644 test/Ocelot.UnitTests/Rafty/OcelotFiniteStateMachineTests.cs delete mode 100644 test/Ocelot.UnitTests/Rafty/RaftyFileConfigurationSetterTests.cs delete mode 100644 tools/packages.config diff --git a/.circleci/config.yml b/.circleci/config.yml index 3325b02df..65fe36918 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,32 +1,38 @@ -version: 2.1 +version: 2.1 orbs: queue: eddiewebb/queue@1.5.0 jobs: build: docker: - - image: mijitt0m/ocelot-build:0.0.1 + - image: mijitt0m/ocelot-build:0.0.3 steps: - checkout - run: make build release: docker: - - image: mijitt0m/ocelot-build:0.0.1 + - image: mijitt0m/ocelot-build:0.0.3 steps: - checkout - run: make release workflows: version: 2 master: - jobs: + jobs: - queue/block_workflow: time: "20" only-on-branch: master - - release: + - release: requires: - queue/block_workflow filters: branches: only: master + develop: + jobs: + - build: + filters: + branches: + only: develop pr: jobs: - build: @@ -34,3 +40,4 @@ workflows: branches: ignore: - master + - develop diff --git a/.dockerignore b/.dockerignore index 54fec4f0a..43f01f6dc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,4 @@ -*/*/bin -*/*/obj +*/*/bin +*/*/obj +artifacts/ +TestResults/ \ No newline at end of file diff --git a/Makefile b/Makefile index 834ba760b..a83aa708e 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,24 @@ -NAME ?= ocelot - -build: - ./build.sh - -build_and_run_tests: - ./build.sh --target=RunTests - -release: - ./build.sh --target=Release - -run_acceptance_tests: - ./build.sh --target=RunAcceptanceTests - -run_benchmarks: - ./build.sh --target=RunBenchmarkTests - -run_unit_tests: - ./build.sh --target=RunUnitTests - -release_notes: - ./build.sh --target=ReleaseNotes +NAME ?= ocelot + +build: + ./build.sh + +build_and_run_tests: + ./build.sh --target=RunTests + +release: + ./build.sh --target=Release + +run_acceptance_tests: + ./build.sh --target=RunAcceptanceTests + +run_benchmarks: + ./build.sh --target=RunBenchmarkTests + +run_unit_tests: + ./build.sh --target=RunUnitTests + +release_notes: + ./build.sh --target=ReleaseNotes + \ No newline at end of file diff --git a/Ocelot.sln b/Ocelot.sln index 511c79839..ade57b25f 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -44,8 +44,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Eureka", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Polly", "src\Ocelot.Provider.Polly\Ocelot.Provider.Polly.csproj", "{1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Rafty", "src\Ocelot.Provider.Rafty\Ocelot.Provider.Rafty.csproj", "{AC153C67-EF18-47E6-A230-F0D3CF5F0A98}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Tracing.Butterfly", "src\Ocelot.Tracing.Butterfly\Ocelot.Tracing.Butterfly.csproj", "{6045E23D-669C-4F27-AF8E-8EEE6DB3557F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Kubernetes", "src\Ocelot.Provider.Kubernetes\Ocelot.Provider.Kubernetes.csproj", "{72C8E528-B4F5-45CE-8A06-CD3787364856}" @@ -138,10 +136,6 @@ Global {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E}.Release|Any CPU.Build.0 = Release|Any CPU - {AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AC153C67-EF18-47E6-A230-F0D3CF5F0A98}.Release|Any CPU.Build.0 = Release|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Debug|Any CPU.Build.0 = Debug|Any CPU {6045E23D-669C-4F27-AF8E-8EEE6DB3557F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -210,7 +204,6 @@ Global {02F5AE4D-9C36-4E58-B7C6-012CBBDEFDE0} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {9BBD3586-145C-4FA0-91C5-9ED58287D753} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {1F6E5DCF-8A2E-4E24-A25D-064362DE8D0E} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} - {AC153C67-EF18-47E6-A230-F0D3CF5F0A98} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {6045E23D-669C-4F27-AF8E-8EEE6DB3557F} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {72C8E528-B4F5-45CE-8A06-CD3787364856} = {5CFB79B7-C9DC-45A4-9A75-625D92471702} {ED0B3A09-112B-4BA4-82D6-11569BC7A99B} = {ED066001-BAF7-4117-9884-DF591A56347D} diff --git a/README.md b/README.md index a18d15fea..c9ff5a61f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Kubernetes * WebSockets * Authentication -* Authorisation +* Authorization * Rate Limiting * Caching * Retry policies / QoS diff --git a/build.cake b/build.cake index 063de67dc..efd9e76b1 100644 --- a/build.cake +++ b/build.cake @@ -1,547 +1,547 @@ -#tool "nuget:?package=GitVersion.CommandLine&version=5.0.1" -#addin nuget:?package=Cake.Json -#addin nuget:?package=Newtonsoft.Json -#addin nuget:?package=System.Net.Http -#addin nuget:?package=System.Text.Encodings.Web -#tool "nuget:?package=ReportGenerator" -#tool "nuget:?package=coveralls.net&version=0.7.0" -#addin Cake.Coveralls&version=0.10.1 - -// compile -var compileConfig = Argument("configuration", "Release"); - -var slnFile = "./Ocelot.sln"; - -// build artifacts -var artifactsDir = Directory("artifacts"); - -// unit testing -var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); -var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; -var minCodeCoverage = 80d; -var coverallsRepoToken = "OCELOT_COVERALLS_TOKEN"; -var coverallsRepo = "https://coveralls.io/github/ThreeMammals/Ocelot"; - -// acceptance testing -var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); -var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj"; - -// integration testing -var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); -var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj"; - -// benchmark testing -var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); -var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; - -// packaging -var packagesDir = artifactsDir + Directory("Packages"); -var releaseNotesFile = packagesDir + File("releasenotes.md"); -var artifactsFile = packagesDir + File("artifacts.txt"); - -// stable releases -var tagsUrl = "https://api.github.com/repos/ThreeMammals/ocelot/releases/tags/"; -var nugetFeedStableKey = EnvironmentVariable("OCELOT_NUTGET_API_KEY"); -var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; -var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; - -// internal build variables - don't change these. -string committedVersion = "0.0.0-dev"; -GitVersion versioning = null; -int releaseId = 0; -string gitHubUsername = "TomPallister"; -string gitHubPassword = Environment.GetEnvironmentVariable("OCELOT_GITHUB_API_KEY"); - -var target = Argument("target", "Default"); - -Information("target is " + target); -Information("Build configuration is " + compileConfig); - -Task("Default") - .IsDependentOn("Build"); - -Task("Build") - .IsDependentOn("RunTests"); - -Task("ReleaseNotes") - .IsDependentOn("CreateReleaseNotes"); - -Task("RunTests") - .IsDependentOn("RunUnitTests") - .IsDependentOn("RunAcceptanceTests") - .IsDependentOn("RunIntegrationTests"); - -Task("Release") - .IsDependentOn("Build") - .IsDependentOn("CreateArtifacts") - .IsDependentOn("PublishGitHubRelease") - .IsDependentOn("PublishToNuget"); - -Task("Compile") - .IsDependentOn("Clean") - .IsDependentOn("Version") - .Does(() => - { - var settings = new DotNetCoreBuildSettings - { - Configuration = compileConfig, - }; - - DotNetCoreBuild(slnFile, settings); - }); - -Task("Clean") - .Does(() => - { - if (DirectoryExists(artifactsDir)) - { - DeleteDirectory(artifactsDir, recursive:true); - } - CreateDirectory(artifactsDir); - }); - -Task("CreateReleaseNotes") - .Does(() => - { - Information("Generating release notes at " + releaseNotesFile); - - IEnumerable lastReleaseTag; - - var lastReleaseTagExitCode = StartProcess( - "git", - new ProcessSettings { - Arguments = "describe --tags --abbrev=0", - RedirectStandardOutput = true - }, - out lastReleaseTag - ); - - if (lastReleaseTagExitCode != 0) - { - throw new Exception("Failed to get latest release tag"); - } - - var lastRelease = lastReleaseTag.First(); - - Information("Last release tag is " + lastRelease); - - IEnumerable releaseNotes; - - var releaseNotesExitCode = StartProcess( - "git", - new ProcessSettings { - Arguments = $"log --pretty=format:\"%h - %an - %s\" {lastRelease}..HEAD", - RedirectStandardOutput = true - }, - out releaseNotes - ); - - if (releaseNotesExitCode != 0) - { - throw new Exception("Failed to generate release notes"); - } - - EnsureDirectoryExists(packagesDir); - - System.IO.File.WriteAllLines(releaseNotesFile, releaseNotes); - - if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) - { - System.IO.File.WriteAllText(releaseNotesFile, "No commits since last release"); - } - - Information("Release notes are\r\n" + System.IO.File.ReadAllText(releaseNotesFile)); - }); - -Task("Version") - .IsDependentOn("CreateReleaseNotes") - .Does(() => - { - versioning = GetNuGetVersionForCommit(); - var nugetVersion = versioning.NuGetVersion; - Information("SemVer version number: " + nugetVersion); - - if (IsRunningOnCircleCI()) - { - Information("Persisting version number..."); - PersistVersion(committedVersion, nugetVersion); - } - else - { - Information("We are not running on build server, so we won't persist the version number."); - } - }); - -Task("RunUnitTests") - .IsDependentOn("Compile") - .Does(() => - { - var testSettings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - ResultsDirectory = artifactsForUnitTestsDir, - ArgumentCustomization = args => args - // this create the code coverage report - .Append("--settings test/Ocelot.UnitTests/UnitTests.runsettings") - }; - - EnsureDirectoryExists(artifactsForUnitTestsDir); - DotNetCoreTest(unitTestAssemblies, testSettings); - - var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.opencover.xml")); - Information(coverageSummaryFile); - Information(artifactsForUnitTestsDir); - - // todo bring back report generator to get a friendly report - // ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); - // https://github.com/danielpalme/ReportGenerator - - if (IsRunningOnCircleCI() && IsMaster()) - { - var repoToken = EnvironmentVariable(coverallsRepoToken); - if (string.IsNullOrEmpty(repoToken)) - { - throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken)); - } - - Information(string.Format("Uploading test coverage to {0}", coverallsRepo)); - CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings() - { - RepoToken = repoToken - }); - } - else - { - Information("We are not running on the build server so we won't publish the coverage report to coveralls.io"); - } - - var sequenceCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@sequenceCoverage"); - var branchCoverage = XmlPeek(coverageSummaryFile, "//CoverageSession/Summary/@branchCoverage"); - - Information("Sequence Coverage: " + sequenceCoverage); - - if(double.Parse(sequenceCoverage) < minCodeCoverage) - { - var whereToCheck = !IsRunningOnCircleCI() ? coverallsRepo : artifactsForUnitTestsDir; - throw new Exception(string.Format("Code coverage fell below the threshold of {0}%. You can find the code coverage report at {1}", minCodeCoverage, whereToCheck)); - }; - }); - -Task("RunAcceptanceTests") - .IsDependentOn("Compile") - .Does(() => - { - var settings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - ArgumentCustomization = args => args - .Append("--no-restore") - .Append("--no-build") - }; - - EnsureDirectoryExists(artifactsForAcceptanceTestsDir); - DotNetCoreTest(acceptanceTestAssemblies, settings); - }); - -Task("RunIntegrationTests") - .IsDependentOn("Compile") - .Does(() => - { - var settings = new DotNetCoreTestSettings - { - Configuration = compileConfig, - ArgumentCustomization = args => args - .Append("--no-restore") - .Append("--no-build") - }; - - EnsureDirectoryExists(artifactsForIntegrationTestsDir); - DotNetCoreTest(integrationTestAssemblies, settings); - }); - -Task("CreateArtifacts") - .IsDependentOn("Compile") - .Does(() => - { - EnsureDirectoryExists(packagesDir); - - CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir); - - var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg"); - - foreach(var projectFile in projectFiles) - { - System.IO.File.AppendAllLines(artifactsFile, new[]{ - projectFile.GetFilename().FullPath, - "releasenotes.md" - }); - } - - var artifacts = System.IO.File - .ReadAllLines(artifactsFile) - .Distinct(); - - foreach(var artifact in artifacts) - { - var codePackage = packagesDir + File(artifact); - - Information("Created package " + codePackage); - } - }); - -Task("PublishGitHubRelease") - .IsDependentOn("CreateArtifacts") - .Does(() => - { - if (IsRunningOnCircleCI()) - { - var path = packagesDir.ToString() + @"/**/*"; - - CreateGitHubRelease(); - - foreach (var file in GetFiles(path)) - { - UploadFileToGitHubRelease(file); - } - - CompleteGitHubRelease(); - } - }); - -Task("EnsureStableReleaseRequirements") - .Does(() => - { - Information("Check if stable release..."); - - if (!IsRunningOnCircleCI()) - { - throw new Exception("Stable release should happen via circleci"); - } - - Information("Release is stable..."); - }); - -Task("DownloadGitHubReleaseArtifacts") - .Does(() => - { - - try - { - // hack to let GitHub catch up, todo - refactor to poll - System.Threading.Thread.Sleep(5000); - - EnsureDirectoryExists(packagesDir); - - var releaseUrl = tagsUrl + versioning.NuGetVersion; - - var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) - .Value("assets_url"); - - var assets = GetResource(assets_url); - - foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject(assets)) - { - var file = packagesDir + File(asset.Value("name")); - DownloadFile(asset.Value("browser_download_url"), file); - } - } - catch(Exception exception) - { - Information("There was an exception " + exception); - throw; - } - }); - -Task("PublishToNuget") - .IsDependentOn("DownloadGitHubReleaseArtifacts") - .Does(() => - { - if (IsRunningOnCircleCI()) - { - PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); - } - }); - -RunTarget(target); - -/// Gets unique nuget version for this commit -private GitVersion GetNuGetVersionForCommit() -{ - GitVersion(new GitVersionSettings{ - UpdateAssemblyInfo = false, - OutputType = GitVersionOutput.BuildServer - }); - - return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); -} - -/// Updates project version in all of our projects -private void PersistVersion(string committedVersion, string newVersion) -{ - Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion)); - - var projectFiles = GetFiles("./**/*.csproj"); - - foreach(var projectFile in projectFiles) - { - var file = projectFile.ToString(); - - Information(string.Format("Updating {0}...", file)); - - var updatedProjectFile = System.IO.File.ReadAllText(file) - .Replace(committedVersion, newVersion); - - System.IO.File.WriteAllText(file, updatedProjectFile); - } -} - -/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) -{ - Information("PublishPackages"); - var artifacts = System.IO.File - .ReadAllLines(artifactsFile) - .Distinct(); - - foreach(var artifact in artifacts) - { - if (artifact == "releasenotes.md") - { - continue; - } - - var codePackage = packagesDir + File(artifact); - - Information("Pushing package " + codePackage); - - Information("Calling NuGetPush"); - - NuGetPush( - codePackage, - new NuGetPushSettings { - ApiKey = feedApiKey, - Source = codeFeedUrl - }); - } -} - -private void CreateGitHubRelease() -{ - var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"{ReleaseNotesAsJson()}\", \"draft\": true, \"prerelease\": true }}"; - - var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); - - using(var client = new System.Net.Http.HttpClient()) - { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - - client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); - - var result = client.PostAsync("https://api.github.com/repos/ThreeMammals/Ocelot/releases", content).Result; - if(result.StatusCode != System.Net.HttpStatusCode.Created) - { - throw new Exception("CreateGitHubRelease result.StatusCode = " + result.StatusCode); - } - var returnValue = result.Content.ReadAsStringAsync().Result; - dynamic test = Newtonsoft.Json.JsonConvert.DeserializeObject(returnValue); - releaseId = test.id; - } -} - -private string ReleaseNotesAsJson() -{ - return System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(System.IO.File.ReadAllText(releaseNotesFile)); -} - -private void UploadFileToGitHubRelease(FilePath file) -{ - var data = System.IO.File.ReadAllBytes(file.FullPath); - var content = new System.Net.Http.ByteArrayContent(data); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); - - using(var client = new System.Net.Http.HttpClient()) - { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - - client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); - - var result = client.PostAsync($"https://uploads.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}/assets?name={file.GetFilename()}", content).Result; - if(result.StatusCode != System.Net.HttpStatusCode.Created) - { - throw new Exception("UploadFileToGitHubRelease result.StatusCode = " + result.StatusCode); - } - } -} - -private void CompleteGitHubRelease() -{ - var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"{ReleaseNotesAsJson()}\", \"draft\": false, \"prerelease\": false }}"; - var request = new System.Net.Http.HttpRequestMessage(new System.Net.Http.HttpMethod("Patch"), $"https://api.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}"); - request.Content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); - - using(var client = new System.Net.Http.HttpClient()) - { - client.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", Convert.ToBase64String( - System.Text.ASCIIEncoding.ASCII.GetBytes( - $"{gitHubUsername}:{gitHubPassword}"))); - - client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); - - var result = client.SendAsync(request).Result; - if(result.StatusCode != System.Net.HttpStatusCode.OK) - { - throw new Exception("CompleteGitHubRelease result.StatusCode = " + result.StatusCode); - } - } -} - - -/// gets the resource from the specified url -private string GetResource(string 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"; - - 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 IsRunningOnCircleCI() -{ - return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI")); -} - -private bool IsMaster() -{ - return Environment.GetEnvironmentVariable("CIRCLE_BRANCH").ToLower() == "master"; +#tool "nuget:?package=GitVersion.CommandLine&version=5.0.1" +#addin nuget:?package=Cake.Json +#addin nuget:?package=Newtonsoft.Json +#addin nuget:?package=System.Net.Http&version=4.3.4 +#addin nuget:?package=System.Text.Encodings.Web&version=4.7.1 +#tool "nuget:?package=ReportGenerator" +#tool "nuget:?package=coveralls.net&version=0.7.0" +#addin Cake.Coveralls&version=0.10.1 + +// compile +var compileConfig = Argument("configuration", "Release"); + +var slnFile = "./Ocelot.sln"; + +// build artifacts +var artifactsDir = Directory("artifacts"); + +// unit testing +var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); +var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; +var minCodeCoverage = 0.80d; +var coverallsRepoToken = "OCELOT_COVERALLS_TOKEN"; +var coverallsRepo = "https://coveralls.io/github/ThreeMammals/Ocelot"; + +// acceptance testing +var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); +var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj"; + +// integration testing +var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); +var integrationTestAssemblies = @"./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj"; + +// benchmark testing +var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); +var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; + +// packaging +var packagesDir = artifactsDir + Directory("Packages"); +var releaseNotesFile = packagesDir + File("releasenotes.md"); +var artifactsFile = packagesDir + File("artifacts.txt"); + +// stable releases +var tagsUrl = "https://api.github.com/repos/ThreeMammals/ocelot/releases/tags/"; +var nugetFeedStableKey = EnvironmentVariable("OCELOT_NUTGET_API_KEY"); +var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; + +// internal build variables - don't change these. +string committedVersion = "0.0.0-dev"; +GitVersion versioning = null; +int releaseId = 0; +string gitHubUsername = "TomPallister"; +string gitHubPassword = Environment.GetEnvironmentVariable("OCELOT_GITHUB_API_KEY"); + +var target = Argument("target", "Default"); + +Information("target is " + target); +Information("Build configuration is " + compileConfig); + +Task("Default") + .IsDependentOn("Build"); + +Task("Build") + .IsDependentOn("RunTests"); + +Task("ReleaseNotes") + .IsDependentOn("CreateReleaseNotes"); + +Task("RunTests") + .IsDependentOn("RunUnitTests") + .IsDependentOn("RunAcceptanceTests") + .IsDependentOn("RunIntegrationTests"); + +Task("Release") + .IsDependentOn("Build") + .IsDependentOn("CreateArtifacts") + .IsDependentOn("PublishGitHubRelease") + .IsDependentOn("PublishToNuget"); + +Task("Compile") + .IsDependentOn("Clean") + .IsDependentOn("Version") + .Does(() => + { + var settings = new DotNetCoreBuildSettings + { + Configuration = compileConfig, + }; + + DotNetCoreBuild(slnFile, settings); + }); + +Task("Clean") + .Does(() => + { + if (DirectoryExists(artifactsDir)) + { + DeleteDirectory(artifactsDir, recursive:true); + } + CreateDirectory(artifactsDir); + }); + +Task("CreateReleaseNotes") + .Does(() => + { + Information("Generating release notes at " + releaseNotesFile); + + IEnumerable lastReleaseTag; + + var lastReleaseTagExitCode = StartProcess( + "git", + new ProcessSettings { + Arguments = "describe --tags --abbrev=0", + RedirectStandardOutput = true + }, + out lastReleaseTag + ); + + if (lastReleaseTagExitCode != 0) + { + throw new Exception("Failed to get latest release tag"); + } + + var lastRelease = lastReleaseTag.First(); + + Information("Last release tag is " + lastRelease); + + IEnumerable releaseNotes; + + var releaseNotesExitCode = StartProcess( + "git", + new ProcessSettings { + Arguments = $"log --pretty=format:\"%h - %an - %s\" {lastRelease}..HEAD", + RedirectStandardOutput = true + }, + out releaseNotes + ); + + if (releaseNotesExitCode != 0) + { + throw new Exception("Failed to generate release notes"); + } + + EnsureDirectoryExists(packagesDir); + + System.IO.File.WriteAllLines(releaseNotesFile, releaseNotes); + + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + { + System.IO.File.WriteAllText(releaseNotesFile, "No commits since last release"); + } + + Information("Release notes are\r\n" + System.IO.File.ReadAllText(releaseNotesFile)); + }); + +Task("Version") + .IsDependentOn("CreateReleaseNotes") + .Does(() => + { + versioning = GetNuGetVersionForCommit(); + var nugetVersion = versioning.NuGetVersion; + Information("SemVer version number: " + nugetVersion); + + if (IsRunningOnCircleCI()) + { + Information("Persisting version number..."); + PersistVersion(committedVersion, nugetVersion); + } + else + { + Information("We are not running on build server, so we won't persist the version number."); + } + }); + +Task("RunUnitTests") + .IsDependentOn("Compile") + .Does(() => + { + var testSettings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ResultsDirectory = artifactsForUnitTestsDir, + ArgumentCustomization = args => args + // this create the code coverage report + .Append("--collect:\"XPlat Code Coverage\"") + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, testSettings); + + var coverageSummaryFile = GetSubDirectories(artifactsForUnitTestsDir).First().CombineWithFilePath(File("coverage.cobertura.xml")); + Information(coverageSummaryFile); + Information(artifactsForUnitTestsDir); + + // todo bring back report generator to get a friendly report + // ReportGenerator(coverageSummaryFile, artifactsForUnitTestsDir); + // https://github.com/danielpalme/ReportGenerator + + if (IsRunningOnCircleCI() && IsMaster()) + { + var repoToken = EnvironmentVariable(coverallsRepoToken); + if (string.IsNullOrEmpty(repoToken)) + { + throw new Exception(string.Format("Coveralls repo token not found. Set environment variable '{0}'", coverallsRepoToken)); + } + + Information(string.Format("Uploading test coverage to {0}", coverallsRepo)); + CoverallsNet(coverageSummaryFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings() + { + RepoToken = repoToken + }); + } + else + { + Information("We are not running on the build server so we won't publish the coverage report to coveralls.io"); + } + + var sequenceCoverage = XmlPeek(coverageSummaryFile, "//coverage/@line-rate"); + var branchCoverage = XmlPeek(coverageSummaryFile, "//coverage/@line-rate"); + + Information("Sequence Coverage: " + sequenceCoverage); + + if(double.Parse(sequenceCoverage) < minCodeCoverage) + { + var whereToCheck = !IsRunningOnCircleCI() ? coverallsRepo : artifactsForUnitTestsDir; + throw new Exception(string.Format("Code coverage fell below the threshold of {0}%. You can find the code coverage report at {1}", minCodeCoverage, whereToCheck)); + }; + }); + +Task("RunAcceptanceTests") + .IsDependentOn("Compile") + .Does(() => + { + var settings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ArgumentCustomization = args => args + .Append("--no-restore") + .Append("--no-build") + }; + + EnsureDirectoryExists(artifactsForAcceptanceTestsDir); + DotNetCoreTest(acceptanceTestAssemblies, settings); + }); + +Task("RunIntegrationTests") + .IsDependentOn("Compile") + .Does(() => + { + var settings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + ArgumentCustomization = args => args + .Append("--no-restore") + .Append("--no-build") + }; + + EnsureDirectoryExists(artifactsForIntegrationTestsDir); + DotNetCoreTest(integrationTestAssemblies, settings); + }); + +Task("CreateArtifacts") + .IsDependentOn("Compile") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + CopyFiles("./src/**/Release/Ocelot.*.nupkg", packagesDir); + + var projectFiles = GetFiles("./src/**/Release/Ocelot.*.nupkg"); + + foreach(var projectFile in projectFiles) + { + System.IO.File.AppendAllLines(artifactsFile, new[]{ + projectFile.GetFilename().FullPath, + "releasenotes.md" + }); + } + + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Distinct(); + + foreach(var artifact in artifacts) + { + var codePackage = packagesDir + File(artifact); + + Information("Created package " + codePackage); + } + }); + +Task("PublishGitHubRelease") + .IsDependentOn("CreateArtifacts") + .Does(() => + { + if (IsRunningOnCircleCI()) + { + var path = packagesDir.ToString() + @"/**/*"; + + CreateGitHubRelease(); + + foreach (var file in GetFiles(path)) + { + UploadFileToGitHubRelease(file); + } + + CompleteGitHubRelease(); + } + }); + +Task("EnsureStableReleaseRequirements") + .Does(() => + { + Information("Check if stable release..."); + + if (!IsRunningOnCircleCI()) + { + throw new Exception("Stable release should happen via circleci"); + } + + Information("Release is stable..."); + }); + +Task("DownloadGitHubReleaseArtifacts") + .Does(() => + { + + try + { + // hack to let GitHub catch up, todo - refactor to poll + System.Threading.Thread.Sleep(5000); + + EnsureDirectoryExists(packagesDir); + + var releaseUrl = tagsUrl + versioning.NuGetVersion; + + var assets_url = Newtonsoft.Json.Linq.JObject.Parse(GetResource(releaseUrl)) + .Value("assets_url"); + + var assets = GetResource(assets_url); + + foreach(var asset in Newtonsoft.Json.JsonConvert.DeserializeObject(assets)) + { + var file = packagesDir + File(asset.Value("name")); + DownloadFile(asset.Value("browser_download_url"), file); + } + } + catch(Exception exception) + { + Information("There was an exception " + exception); + throw; + } + }); + +Task("PublishToNuget") + .IsDependentOn("DownloadGitHubReleaseArtifacts") + .Does(() => + { + if (IsRunningOnCircleCI()) + { + PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + } + }); + +RunTarget(target); + +/// Gets unique nuget version for this commit +private GitVersion GetNuGetVersionForCommit() +{ + GitVersion(new GitVersionSettings{ + UpdateAssemblyInfo = false, + OutputType = GitVersionOutput.BuildServer + }); + + return GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); +} + +/// Updates project version in all of our projects +private void PersistVersion(string committedVersion, string newVersion) +{ + Information(string.Format("We'll search all csproj files for {0} and replace with {1}...", committedVersion, newVersion)); + + var projectFiles = GetFiles("./**/*.csproj"); + + foreach(var projectFile in projectFiles) + { + var file = projectFile.ToString(); + + Information(string.Format("Updating {0}...", file)); + + var updatedProjectFile = System.IO.File.ReadAllText(file) + .Replace(committedVersion, newVersion); + + System.IO.File.WriteAllText(file, updatedProjectFile); + } +} + +/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file +private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +{ + Information("PublishPackages"); + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Distinct(); + + foreach(var artifact in artifacts) + { + if (artifact == "releasenotes.md") + { + continue; + } + + var codePackage = packagesDir + File(artifact); + + Information("Pushing package " + codePackage); + + Information("Calling NuGetPush"); + + NuGetPush( + codePackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = codeFeedUrl + }); + } +} + +private void CreateGitHubRelease() +{ + var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"{ReleaseNotesAsJson()}\", \"draft\": true, \"prerelease\": true }}"; + + var content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + using(var client = new System.Net.Http.HttpClient()) + { + client.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", Convert.ToBase64String( + System.Text.ASCIIEncoding.ASCII.GetBytes( + $"{gitHubUsername}:{gitHubPassword}"))); + + client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); + + var result = client.PostAsync("https://api.github.com/repos/ThreeMammals/Ocelot/releases", content).Result; + if(result.StatusCode != System.Net.HttpStatusCode.Created) + { + throw new Exception("CreateGitHubRelease result.StatusCode = " + result.StatusCode); + } + var returnValue = result.Content.ReadAsStringAsync().Result; + dynamic test = Newtonsoft.Json.JsonConvert.DeserializeObject(returnValue); + releaseId = test.id; + } +} + +private string ReleaseNotesAsJson() +{ + return System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(System.IO.File.ReadAllText(releaseNotesFile)); +} + +private void UploadFileToGitHubRelease(FilePath file) +{ + var data = System.IO.File.ReadAllBytes(file.FullPath); + var content = new System.Net.Http.ByteArrayContent(data); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + + using(var client = new System.Net.Http.HttpClient()) + { + client.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", Convert.ToBase64String( + System.Text.ASCIIEncoding.ASCII.GetBytes( + $"{gitHubUsername}:{gitHubPassword}"))); + + client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); + + var result = client.PostAsync($"https://uploads.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}/assets?name={file.GetFilename()}", content).Result; + if(result.StatusCode != System.Net.HttpStatusCode.Created) + { + throw new Exception("UploadFileToGitHubRelease result.StatusCode = " + result.StatusCode); + } + } +} + +private void CompleteGitHubRelease() +{ + var json = $"{{ \"tag_name\": \"{versioning.NuGetVersion}\", \"target_commitish\": \"master\", \"name\": \"{versioning.NuGetVersion}\", \"body\": \"{ReleaseNotesAsJson()}\", \"draft\": false, \"prerelease\": false }}"; + var request = new System.Net.Http.HttpRequestMessage(new System.Net.Http.HttpMethod("Patch"), $"https://api.github.com/repos/ThreeMammals/Ocelot/releases/{releaseId}"); + request.Content = new System.Net.Http.StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + using(var client = new System.Net.Http.HttpClient()) + { + client.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", Convert.ToBase64String( + System.Text.ASCIIEncoding.ASCII.GetBytes( + $"{gitHubUsername}:{gitHubPassword}"))); + + client.DefaultRequestHeaders.Add("User-Agent", "Ocelot Release"); + + var result = client.SendAsync(request).Result; + if(result.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new Exception("CompleteGitHubRelease result.StatusCode = " + result.StatusCode); + } + } +} + + +/// gets the resource from the specified url +private string GetResource(string 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"; + + 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 IsRunningOnCircleCI() +{ + return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CIRCLECI")); +} + +private bool IsMaster() +{ + return Environment.GetEnvironmentVariable("CIRCLE_BRANCH").ToLower() == "master"; } \ No newline at end of file diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index 02d91f53a..cc6798c37 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -1,9 +1,16 @@ -# this is the dockerfile that create the ocelot build container -# build with the docker-build.sh file in this folder -FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build - -RUN apt install gnupg ca-certificates -RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF -RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list -RUN apt update -RUN apt-get -y install mono-devel \ No newline at end of file +# this is the dockerfile that create the ocelot build container +# build with the docker-build.sh file in this folder +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build + +RUN apt install gnupg ca-certificates +RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF +RUN echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list +RUN apt update +RUN apt-get -y install mono-devel + +RUN wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \ + && dpkg -i packages-microsoft-prod.deb \ + && apt-get update; \ + apt-get install -y apt-transport-https && \ + apt-get update && \ + apt-get install -y dotnet-sdk-5.0 \ No newline at end of file diff --git a/docker/Dockerfile.build b/docker/Dockerfile.build index 0478da417..4b99c053c 100644 --- a/docker/Dockerfile.build +++ b/docker/Dockerfile.build @@ -1,15 +1,15 @@ -# call from ocelot repo root with -# docker build --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build . -FROM mijitt0m/ocelot-build:0.0.1 - -ARG OCELOT_COVERALLS_TOKEN - -ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN - -WORKDIR /src - -COPY ./. . - -RUN chmod u+x build.sh - -RUN make build \ No newline at end of file +# call from ocelot repo root with +# docker build --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.build . +FROM mijitt0m/ocelot-build:0.0.3 + +ARG OCELOT_COVERALLS_TOKEN + +ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN + +WORKDIR /src + +COPY ./. . + +RUN chmod u+x build.sh + +RUN make build diff --git a/docker/Dockerfile.release b/docker/Dockerfile.release index 5d63816d2..0ce0e95ea 100644 --- a/docker/Dockerfile.release +++ b/docker/Dockerfile.release @@ -1,20 +1,20 @@ -# call from ocelot repo root with -# docker build --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.release . - -FROM mijitt0m/ocelot-build:0.0.1 - -ARG OCELOT_COVERALLS_TOKEN -ARG OCELOT_NUTGET_API_KEY -ARG OCELOT_GITHUB_API_KEY - -ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -ENV OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY -ENV OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY - -WORKDIR /src - -COPY ./. . - -RUN chmod u+x build.sh - -RUN make release \ No newline at end of file +# call from ocelot repo root with +# docker build --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN --build-arg OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY --build-arg OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN -f ./docker/Dockerfile.release . + +FROM mijitt0m/ocelot-build:0.0.3 + +ARG OCELOT_COVERALLS_TOKEN +ARG OCELOT_NUTGET_API_KEY +ARG OCELOT_GITHUB_API_KEY + +ENV OCELOT_COVERALLS_TOKEN=$OCELOT_COVERALLS_TOKEN +ENV OCELOT_NUTGET_API_KEY=$OCELOT_NUTGET_API_KEY +ENV OCELOT_GITHUB_API_KEY=$OCELOT_GITHUB_API_KEY + +WORKDIR /src + +COPY ./. . + +RUN chmod u+x build.sh + +RUN make release diff --git a/docker/docker-build.sh b/docker/docker-build.sh index 8bb4e2065..d2d639347 100755 --- a/docker/docker-build.sh +++ b/docker/docker-build.sh @@ -1,6 +1,7 @@ -# this script build the ocelot docker file -docker build -t mijitt0m/ocelot-build -f Dockerfile.base . -echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin -docker tag mijitt0m/ocelot-build mijitt0m/ocelot-build:0.0.1 -docker push mijitt0m/ocelot-build:latest -docker push mijitt0m/ocelot-build:0.0.1 +# this script build the ocelot docker file +version=0.0.3 +docker build -t mijitt0m/ocelot-build -f Dockerfile.base . +echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin +docker tag mijitt0m/ocelot-build mijitt0m/ocelot-build:$version +docker push mijitt0m/ocelot-build:latest +docker push mijitt0m/ocelot-build:$version diff --git a/docs/building/releaseprocess.rst b/docs/building/releaseprocess.rst index 43cb5f61a..7a89a1f07 100644 --- a/docs/building/releaseprocess.rst +++ b/docs/building/releaseprocess.rst @@ -1,7 +1,7 @@ Release process =============== -* The release process works best with GitHubFlow branching. +* The release process works best with Git Flow branching. * Contributors can do whatever they want on PRs and merges to master will result in packages being released to GitHub and NuGet. Ocelot uses the following process to accept work into the NuGet packages. @@ -10,7 +10,7 @@ Ocelot uses the following process to accept work into the NuGet packages. 2. User creates a fork and branches from this (unless a member of core team, they can just create a branch on the main repo) e.g. feat/xxx, fix/xxx etc. It doesn't really matter what the xxx is. It might make sense to use the issue number and maybe a short description. I don't care as long as it has (feat, fix, refactor)/xxx :) -3. When the user is happy with their work they can create a pull request against master in GitHub with their changes. The user must follow the `SemVer `_ support for this is provided by `GitVersion `_. So if you need to make breaking changes please make sure you use the correct commit message so GitVersion uses the correct semver tags. Do not manually tag the Ocelot repo this will break things. +3. When the user is happy with their work they can create a pull request against develop in GitHub with their changes. The user must follow the `SemVer `_ support for this is provided by `GitVersion `_. So if you need to make breaking changes please make sure you use the correct commit message so GitVersion uses the correct semver tags. Do not manually tag the Ocelot repo this will break things. 4. The Ocelot team will review the PR and if all is good merge it, else they will suggest feedback that the user will need to act on. In order to speed up getting a PR the user should think about the following. - Have I covered all my changes with tests at unit and acceptance level? @@ -23,9 +23,11 @@ In order for a PR to be merged the following must have occured. - Build must not have slowed down dramatically. - The main Ocelot package must not have taken on any non MS dependencies. -5. After the PR is merged to master the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet. +5. After the PR is merged to develop the Ocelot NuGet packages will not be updated until a release is created. -6. The final step is to go back to GitHub and close any issues that are now fixed. You should see something like this in`GitHub `_ and this in `NuGet `_. +6. When enough work has been completed to justify a new release. Develop will be merged into master the release process will begin which builds the code, versions it, pushes artifacts to GitHub and NuGet packages to NuGet. + +7. The final step is to go back to GitHub and close any issues that are now fixed. You should see something like this in`GitHub `_ and this in `NuGet `_. Notes ----- diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 274e7299f..6449f583a 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -12,19 +12,22 @@ This will bring down everything needed by the admin API. Providing your own IdentityServer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method. .. code-block:: csharp public virtual void ConfigureServices(IServiceCollection services) { - Action options = o => { - // o.Authority = ; - // o.ApiName = ; - // etc.... + Action options = o => + { + o.Authority = identityServerRootUrl; + o.RequireHttpsMetadata = false; + o.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, }; + // etc.... + }; services .AddOcelot() diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index f935cbacd..e41c0033a 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -1,7 +1,7 @@ Authentication ============== -In order to authenticate Routes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. +In order to authenticate Routes and subsequently use any of Ocelot's claims based features such as authorization or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. .. code-block:: csharp @@ -97,16 +97,14 @@ In order to use IdentityServer bearer tokens, register your IdentityServer servi public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; - Action options = o => + Action options = o => { o.Authority = "https://whereyouridentityserverlives.com"; - o.ApiName = "api"; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; + // etc }; services.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); + .AddJwtBearer(authenticationProviderKey, options); services.AddOcelot(); } diff --git a/docs/features/authorisation.rst b/docs/features/authorization.rst similarity index 51% rename from docs/features/authorisation.rst rename to docs/features/authorization.rst index ab1bc1b50..c890c14c0 100644 --- a/docs/features/authorisation.rst +++ b/docs/features/authorization.rst @@ -1,7 +1,7 @@ -Authorisation +Authorization ============= -Ocelot supports claims based authorisation which is run post authentication. This means if you have a route you want to authorise you can add the following to you Route configuration. +Ocelot supports claims based authorization which is run post authentication. This means if you have a route you want to authorize you can add the following to you Route configuration. .. code-block:: json @@ -9,7 +9,7 @@ Ocelot supports claims based authorisation which is run post authentication. Thi "UserType": "registered" } -In this example when the authorisation middleware is called Ocelot will check to seeif the user has the claim type UserType and if the value of that claim is registered. If it isn't then the user will not be authorised and the response will be 403 forbidden. +In this example when the authorization middleware is called Ocelot will check to seeif the user has the claim type UserType and if the value of that claim is registered. If it isn't then the user will not be authorized and the response will be 403 forbidden. diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index d58801e58..c82657a4c 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -3,7 +3,7 @@ Claims Transformation Ocelot allows the user to access claims and transform them into headers, query string parameters, other claims and change downstream paths. This is only available once a user has been authenticated. -After the user is authenticated we run the claims to claims transformation middleware. This allows the user to transform claims before the authorisation middleware is called. After the user is authorised first we call the claims to headers middleware, thenthe claims to query string parameters middleware, and Finally the claims to downstream pathmiddleware. +After the user is authenticated we run the claims to claims transformation middleware. This allows the user to transform claims before the authorization middleware is called. After the user is authorized first we call the claims to headers middleware, thenthe claims to query string parameters middleware, and Finally the claims to downstream pathmiddleware. The syntax for performing the transforms is the same for each process. In the Route configuration a json dictionary is added with a specific name either AddClaimsToRequest, AddHeadersToRequest, AddQueriesToRequest, or ChangeDownstreamPathTemplate. diff --git a/docs/features/errorcodes.rst b/docs/features/errorcodes.rst index aeea7b633..d07eb3a49 100644 --- a/docs/features/errorcodes.rst +++ b/docs/features/errorcodes.rst @@ -3,7 +3,7 @@ Http Error Status Codes Ocelot will return HTTP status error codes based on internal logic in certain siturations: - 401 if the authentication middleware runs and the user is not authenticated. -- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim. +- 403 if the authorization middleware runs and the user is unauthenticated, claim value not authroised, scope not authorized, user doesnt have required claim or cannot find claim. - 503 if the downstream request times out. - 499 if the request is cancelled by the client. - 404 if unable to find a downstream route. diff --git a/docs/features/middlewareinjection.rst b/docs/features/middlewareinjection.rst index 4b6636582..7fccc599b 100644 --- a/docs/features/middlewareinjection.rst +++ b/docs/features/middlewareinjection.rst @@ -31,9 +31,9 @@ The user can set functions against the following. * AuthenticationMiddleware - This overrides Ocelots authentication middleware. -* PreAuthorisationMiddleware - This allows the user to run pre authorisation logic and then call Ocelot's authorisation middleware. +* PreAuthorizationMiddleware - This allows the user to run pre authorization logic and then call Ocelot's authorization middleware. -* AuthorisationMiddleware - This overrides Ocelots authorisation middleware. +* AuthorizationMiddleware - This overrides Ocelots authorization middleware. * PreQueryStringBuilderMiddleware - This allows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. diff --git a/docs/features/raft.rst b/docs/features/raft.rst deleted file mode 100644 index 69b804043..000000000 --- a/docs/features/raft.rst +++ /dev/null @@ -1,49 +0,0 @@ -Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) -============================================ - -Ocelot has recently integrated `Rafty `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. - -Raft is a distributed concensus algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). - -To get Raft support you must first install the Ocelot Rafty package. - -``Install-Package Ocelot.Provider.Rafty`` - -Then you must make the following changes to your Startup.cs / Program.cs. - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot() - .AddAdministration("/administration", "secret") - .AddRafty(); - } - -In addition to this you must add a file called peers.json to your main project and it will look as follows - -.. code-block:: json - - { - "Peers": [{ - "HostAndPort": "http://localhost:5000" - }, - { - "HostAndPort": "http://localhost:5002" - }, - { - "HostAndPort": "http://localhost:5003" - }, - { - "HostAndPort": "http://localhost:5004" - }, - { - "HostAndPort": "http://localhost:5001" - } - ] - } - -Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. - -Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration. diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index 519cc21aa..d36734026 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -44,8 +44,8 @@ Below is an example of the logging when set at Debug level for a normal request. requestId: asdf, previousRequestId: no previous request id, message: downstream template is {downstreamRoute.Data.Route.DownstreamPath}, dbug: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0] requestId: asdf, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for Ocelot.Values.PathTemplate, - dbug: Ocelot.Authorisation.Middleware.AuthorisationMiddleware[0] - requestId: 1234, previousRequestId: asdf, message: /posts/{postId} route does not require user to be authorised, + dbug: Ocelot.Authorization.Middleware.AuthorizationMiddleware[0] + requestId: 1234, previousRequestId: asdf, message: /posts/{postId} route does not require user to be authorized, dbug: Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware[0] requestId: 1234, previousRequestId: asdf, message: downstream url is {downstreamUrl.Data.Value}, dbug: Ocelot.Request.Middleware.HttpRequestBuilderMiddleware[0] diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 8a1acfdb4..ad185f297 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -102,7 +102,7 @@ Unfortunately a lot of Ocelot's features are non websocket specific such as head 9. Claims Transformation 10. Caching 11. Authentication - If anyone requests it we might be able to do something with basic authentication. -12. Authorisation +12. Authorization I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly! diff --git a/docs/index.rst b/docs/index.rst index 1acbd7124..983d24715 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,7 +26,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/servicefabric features/kubernetes features/authentication - features/authorisation + features/authorization features/websockets features/administration features/ratelimiting @@ -41,7 +41,6 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/middlewareinjection features/loadbalancer features/delegatinghandlers - features/raft features/errorcodes .. toctree:: diff --git a/samples/AdministrationApi/AdministrationApi.csproj b/samples/AdministrationApi/AdministrationApi.csproj index 810fa13c8..7c5f80ecd 100644 --- a/samples/AdministrationApi/AdministrationApi.csproj +++ b/samples/AdministrationApi/AdministrationApi.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1 + net5.0 @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/samples/Docker/Dockerfile b/samples/Docker/Dockerfile index 86b12ab01..dad54b20f 100644 --- a/samples/Docker/Dockerfile +++ b/samples/Docker/Dockerfile @@ -17,7 +17,6 @@ COPY src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj src/Ocelot.C COPY src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj COPY src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj COPY src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj -COPY src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj COPY src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj COPY src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj COPY test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj diff --git a/samples/OcelotBasic/OcelotBasic.csproj b/samples/OcelotBasic/OcelotBasic.csproj index 26b7a4412..be6fd6dab 100644 --- a/samples/OcelotBasic/OcelotBasic.csproj +++ b/samples/OcelotBasic/OcelotBasic.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 diff --git a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj index bc5318be1..f7f4c81e4 100644 --- a/samples/OcelotEureka/ApiGateway/ApiGateway.csproj +++ b/samples/OcelotEureka/ApiGateway/ApiGateway.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 diff --git a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj index 209fa6d21..67a861a8a 100644 --- a/samples/OcelotEureka/DownstreamService/DownstreamService.csproj +++ b/samples/OcelotEureka/DownstreamService/DownstreamService.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/OcelotGraphQL/OcelotGraphQL.csproj index df8f3ec71..f205c25e3 100644 --- a/samples/OcelotGraphQL/OcelotGraphQL.csproj +++ b/samples/OcelotGraphQL/OcelotGraphQL.csproj @@ -1,6 +1,6 @@ - netcoreapp3.1 + net5.0 @@ -12,6 +12,6 @@ - + - \ No newline at end of file + diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs index a18240572..ef23d98d4 100644 --- a/samples/OcelotGraphQL/Program.cs +++ b/samples/OcelotGraphQL/Program.cs @@ -1,133 +1,138 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Ocelot.Middleware; -using Ocelot.DependencyInjection; -using GraphQL.Types; -using GraphQL; -using Ocelot.Requester; -using Ocelot.Responses; -using System.Net.Http; -using System.Net; -using Microsoft.Extensions.DependencyInjection; -using System.Threading; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using GraphQL.Types; +using GraphQL; +using Ocelot.Requester; +using Ocelot.Responses; +using System.Net.Http; +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using System.Threading; + +namespace OcelotGraphQL +{ + public class Hero + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class Query + { + private readonly List _heroes = new List + { + new Hero { Id = 1, Name = "R2-D2" }, + new Hero { Id = 2, Name = "Batman" }, + new Hero { Id = 3, Name = "Wonder Woman" }, + new Hero { Id = 4, Name = "Tom Pallister" } + }; + + [GraphQLMetadata("hero")] + public Hero GetHero(int id) + { + return _heroes.FirstOrDefault(x => x.Id == id); + } + } + + public class GraphQlDelegatingHandler : DelegatingHandler + { + //private readonly ISchema _schema; + private readonly IDocumentExecuter _executer; + private readonly IDocumentWriter _writer; + + public GraphQlDelegatingHandler(IDocumentExecuter executer, IDocumentWriter writer) + { + _executer = executer; + _writer = writer; + } + + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + //try get query from body, could check http method :) + var query = await request.Content.ReadAsStringAsync(); + + //if not body try query string, dont hack like this in real world.. + if (query.Length == 0) + { + var decoded = WebUtility.UrlDecode(request.RequestUri.Query); + query = decoded.Replace("?query=", ""); + } -namespace OcelotGraphQL -{ - public class Hero - { - public int Id { get; set; } - public string Name { get; set; } - } - - public class Query - { - private readonly List _heroes = new List - { - new Hero { Id = 1, Name = "R2-D2" }, - new Hero { Id = 2, Name = "Batman" }, - new Hero { Id = 3, Name = "Wonder Woman" }, - new Hero { Id = 4, Name = "Tom Pallister" } - }; - - [GraphQLMetadata("hero")] - public Hero GetHero(int id) - { - return _heroes.FirstOrDefault(x => x.Id == id); - } - } - - public class GraphQlDelegatingHandler : DelegatingHandler - { - private readonly ISchema _schema; - - public GraphQlDelegatingHandler(ISchema schema) - { - _schema = schema; - } - - protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - //try get query from body, could check http method :) - var query = await request.Content.ReadAsStringAsync(); - - //if not body try query string, dont hack like this in real world.. - if (query.Length == 0) - { - var decoded = WebUtility.UrlDecode(request.RequestUri.Query); - query = decoded.Replace("?query=", ""); - } - - var result = _schema.Execute(_ => - { - _.Query = query; + var result = await _executer.ExecuteAsync(_ => + { + _.Query = query; }); - //maybe check for errors and headers etc in real world? - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(result) - }; - - //ocelot will treat this like any other http request... - return response; - } - } - - public class Program - { - public static void Main() - { - var schema = Schema.For(@" - type Hero { - id: Int - name: String - } - - type Query { - hero(id: Int): Hero - } - ", _ => - { - _.Types.Include(); - }); + var responseBody = await _writer.WriteToStringAsync(result); - new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json", false, false) - .AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(schema); - s.AddOcelot() - .AddDelegatingHandler(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .UseIISIntegration() - .Configure(app => - { - app.UseOcelot().Wait(); - }) - .Build() - .Run(); - } - } -} + //maybe check for errors and headers etc in real world? + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(responseBody) + }; + + //ocelot will treat this like any other http request... + return response; + } + } + + public class Program + { + public static void Main() + { + var schema = Schema.For(@" + type Hero { + id: Int + name: String + } + + type Query { + hero(id: Int): Hero + } + ", _ => + { + _.Types.Include(); + }); + + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json", false, false) + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(schema); + s.AddOcelot() + .AddDelegatingHandler(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build() + .Run(); + } + } +} diff --git a/samples/OcelotGraphQL/README.md b/samples/OcelotGraphQL/README.md index ed6c16600..7f16ed985 100644 --- a/samples/OcelotGraphQL/README.md +++ b/samples/OcelotGraphQL/README.md @@ -1,7 +1,7 @@ # Ocelot using GraphQL example Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together. -I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorisation / authentication or I would +I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorization / authentication or I would bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that. ## Example diff --git a/samples/OcelotKube/ApiGateway/ApiGateway.csproj b/samples/OcelotKube/ApiGateway/ApiGateway.csproj index 8cf74f8d1..a96c220ee 100644 --- a/samples/OcelotKube/ApiGateway/ApiGateway.csproj +++ b/samples/OcelotKube/ApiGateway/ApiGateway.csproj @@ -1,13 +1,13 @@  - netcoreapp3.1 + net5.0 InProcess Linux - + diff --git a/samples/OcelotKube/DownstreamService/DownstreamService.csproj b/samples/OcelotKube/DownstreamService/DownstreamService.csproj index 3947a3de9..732c6594b 100644 --- a/samples/OcelotKube/DownstreamService/DownstreamService.csproj +++ b/samples/OcelotKube/DownstreamService/DownstreamService.csproj @@ -1,13 +1,13 @@ - netcoreapp3.1 + net5.0 InProcess Linux - + diff --git a/samples/OcelotOpenTracing/OcelotOpenTracing.csproj b/samples/OcelotOpenTracing/OcelotOpenTracing.csproj index 295fc461c..fad9470ea 100644 --- a/samples/OcelotOpenTracing/OcelotOpenTracing.csproj +++ b/samples/OcelotOpenTracing/OcelotOpenTracing.csproj @@ -2,12 +2,12 @@ Exe - netcoreapp3.1 + net5.0 - - + + diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj index 186b43f36..f0fd14d40 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj @@ -2,7 +2,7 @@ Stateless Web Service for Stateful OcelotApplicationApiGateway App - netcoreapp3.1 + net5.0 OcelotApplicationApiGateway Exe OcelotApplicationApiGateway @@ -13,10 +13,10 @@ - - + + - \ No newline at end of file + diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj index 236cb1790..b7df220e4 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj +++ b/samples/OcelotServiceFabric/src/OcelotApplicationService/OcelotApplicationService.csproj @@ -3,16 +3,16 @@ Stateless Service Application Exe - netcoreapp3.1 + net5.0 OcelotApplicationService OcelotApplicationService $(PackageTargetFallback) - - + + - + - \ No newline at end of file + diff --git a/src/Ocelot.Administration/Ocelot.Administration.csproj b/src/Ocelot.Administration/Ocelot.Administration.csproj index 88375a183..e449ce9f3 100644 --- a/src/Ocelot.Administration/Ocelot.Administration.csproj +++ b/src/Ocelot.Administration/Ocelot.Administration.csproj @@ -1,39 +1,39 @@ - - - netcoreapp3.1 - true - Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it - Ocelot.Administration - 0.0.0-dev - Ocelot.Administration - Ocelot.Administration - API Gateway;.NET core - https://github.com/ThreeMammals/Ocelot.Administration - https://github.com/ThreeMammals/Ocelot.Administration - http://threemammals.com/images/ocelot_logo.png - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - ..\..\codeanalysis.ruleset - - - full - True - - - - - - - all - - - - - - - - + + + net5.0 + true + Provides Ocelot extensions to use the administration API and IdentityService dependencies that come with it + Ocelot.Administration + 0.0.0-dev + Ocelot.Administration + Ocelot.Administration + API Gateway;.NET core + https://github.com/ThreeMammals/Ocelot.Administration + https://github.com/ThreeMammals/Ocelot.Administration + http://threemammals.com/images/ocelot_logo.png + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + ..\..\codeanalysis.ruleset + + + full + True + + + + + + + all + + + + + + + + diff --git a/src/Ocelot.Administration/OcelotBuilderExtensions.cs b/src/Ocelot.Administration/OcelotBuilderExtensions.cs index da25beaa0..cf14fc1e4 100644 --- a/src/Ocelot.Administration/OcelotBuilderExtensions.cs +++ b/src/Ocelot.Administration/OcelotBuilderExtensions.cs @@ -1,7 +1,6 @@ using Ocelot.DependencyInjection; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -10,6 +9,9 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Cryptography.X509Certificates; +using System.Linq; +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Authentication.JwtBearer; namespace Ocelot.Administration { @@ -18,6 +20,7 @@ public static class OcelotBuilderExtensions public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, string secret) { var administrationPath = new AdministrationPath(path); + builder.Services.AddSingleton(IdentityServerMiddlewareConfigurationProvider.Get); //add identity server for admin area @@ -32,7 +35,7 @@ public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder return new OcelotAdministrationBuilder(builder.Services, builder.Configuration); } - public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, Action configureOptions) + public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder builder, string path, Action configureOptions) { var administrationPath = new AdministrationPath(path); builder.Services.AddSingleton(IdentityServerMiddlewareConfigurationProvider.Get); @@ -46,11 +49,11 @@ public static IOcelotAdministrationBuilder AddAdministration(this IOcelotBuilder return new OcelotAdministrationBuilder(builder.Services, builder.Configuration); } - private static void AddIdentityServer(Action configOptions, IOcelotBuilder builder) + private static void AddIdentityServer(Action configOptions, IOcelotBuilder builder) { builder.Services .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(configOptions); + .AddJwtBearer("Bearer", configOptions); } private static void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath, IOcelotBuilder builder, IConfiguration configuration) @@ -60,7 +63,9 @@ private static void AddIdentityServer(IIdentityServerConfiguration identityServe .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; + o.EmitStaticAudienceClaim = true; }) + .AddInMemoryApiScopes(ApiScopes(identityServerConfiguration)) .AddInMemoryApiResources(Resources(identityServerConfiguration)) .AddInMemoryClients(Client(identityServerConfiguration)); @@ -68,14 +73,17 @@ private static void AddIdentityServer(IIdentityServerConfiguration identityServe var baseSchemeUrlAndPort = urlFinder.Find(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - builder.Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(o => + builder.Services + .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) + .AddJwtBearer("Bearer", options => { - o.Authority = baseSchemeUrlAndPort + adminPath.Path; - o.ApiName = identityServerConfiguration.ApiName; - o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = identityServerConfiguration.ApiSecret; + options.Authority = baseSchemeUrlAndPort + adminPath.Path; + options.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + }; }); //todo - refactor naming.. @@ -91,6 +99,11 @@ private static void AddIdentityServer(IIdentityServerConfiguration identityServe } } + private static IEnumerable ApiScopes(IIdentityServerConfiguration identityServerConfiguration) + { + return identityServerConfiguration.AllowedScopes.Select(s => new ApiScope(s)); + } + private static List Resources(IIdentityServerConfiguration identityServerConfiguration) { return new List @@ -101,9 +114,9 @@ private static List Resources(IIdentityServerConfiguration identity { new Secret { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } + Value = identityServerConfiguration.ApiSecret.Sha256(), + }, + }, }, }; } @@ -117,8 +130,8 @@ private static List Client(IIdentityServerConfiguration identityServerCo ClientId = identityServerConfiguration.ApiName, AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, - AllowedScopes = { identityServerConfiguration.ApiName } - } + AllowedScopes = identityServerConfiguration.AllowedScopes, + }, }; } } diff --git a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj index dcc97dd6f..43baa3329 100644 --- a/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj +++ b/src/Ocelot.Cache.CacheManager/Ocelot.Cache.CacheManager.csproj @@ -1,39 +1,39 @@ - - - netcoreapp3.1 - true - Provides Ocelot extensions to use CacheManager.Net - Ocelot.Cache.CacheManager - 0.0.0-dev - Ocelot.Cache.CacheManager - Ocelot.Cache.CacheManager - API Gateway;.NET core - https://github.com/ThreeMammals/Ocelot.Cache.CacheManager - https://github.com/ThreeMammals/Ocelot.Cache.CacheManager - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - ..\..\codeanalysis.ruleset - - - full - True - - - - - - - all - - - - - - - - - + + + net5.0 + true + Provides Ocelot extensions to use CacheManager.Net + Ocelot.Cache.CacheManager + 0.0.0-dev + Ocelot.Cache.CacheManager + Ocelot.Cache.CacheManager + API Gateway;.NET core + https://github.com/ThreeMammals/Ocelot.Cache.CacheManager + https://github.com/ThreeMammals/Ocelot.Cache.CacheManager + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + ..\..\codeanalysis.ruleset + + + full + True + + + + + + + all + + + + + + + + + diff --git a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj index eaae221fe..13472b026 100644 --- a/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj +++ b/src/Ocelot.Provider.Consul/Ocelot.Provider.Consul.csproj @@ -1,38 +1,38 @@ - - - netcoreapp3.1 - true - Provides Ocelot extensions to use Consul - Ocelot.Provider.Consul - 0.0.0-dev - Ocelot.Provider.Consul - Ocelot.Provider.Consul - API Gateway;.NET core - https://github.com/ThreeMammals/Ocelot.Provider.Consul - https://github.com/ThreeMammals/Ocelot.Provider.Consul - http://threemammals.com/images/ocelot_logo.png - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - ..\..\codeanalysis.ruleset - - - full - True - - - - - - - - all - - - - - - + + + net5.0 + true + Provides Ocelot extensions to use Consul + Ocelot.Provider.Consul + 0.0.0-dev + Ocelot.Provider.Consul + Ocelot.Provider.Consul + API Gateway;.NET core + https://github.com/ThreeMammals/Ocelot.Provider.Consul + https://github.com/ThreeMammals/Ocelot.Provider.Consul + http://threemammals.com/images/ocelot_logo.png + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + ..\..\codeanalysis.ruleset + + + full + True + + + + + + + + all + + + + + + diff --git a/src/Ocelot.Provider.Eureka/Eureka.cs b/src/Ocelot.Provider.Eureka/Eureka.cs index 8a064549e..9da8decd1 100644 --- a/src/Ocelot.Provider.Eureka/Eureka.cs +++ b/src/Ocelot.Provider.Eureka/Eureka.cs @@ -1,35 +1,35 @@ -namespace Ocelot.Provider.Eureka -{ - using ServiceDiscovery.Providers; - using Steeltoe.Common.Discovery; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Values; - - public class Eureka : IServiceDiscoveryProvider - { - private readonly IDiscoveryClient _client; - private readonly string _serviceName; - - public Eureka(string serviceName, IDiscoveryClient client) - { - _client = client; - _serviceName = serviceName; - } - - public Task> Get() - { - var services = new List(); - - var instances = _client.GetInstances(_serviceName); - - if (instances != null && instances.Any()) - { - services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port, i.Uri.Scheme), "", "", new List()))); - } - - return Task.FromResult(services); - } - } -} +namespace Ocelot.Provider.Eureka +{ + using Ocelot.ServiceDiscovery.Providers; + using Steeltoe.Discovery; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Ocelot.Values; + + public class Eureka : IServiceDiscoveryProvider + { + private readonly IDiscoveryClient _client; + private readonly string _serviceName; + + public Eureka(string serviceName, IDiscoveryClient client) + { + _client = client; + _serviceName = serviceName; + } + + public Task> Get() + { + var services = new List(); + + var instances = _client.GetInstances(_serviceName); + + if (instances != null && instances.Any()) + { + services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port, i.Uri.Scheme), "", "", new List()))); + } + + return Task.FromResult(services); + } + } +} diff --git a/src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs b/src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs index 550a83a3b..13e6fc8f8 100644 --- a/src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs +++ b/src/Ocelot.Provider.Eureka/EurekaProviderFactory.cs @@ -1,21 +1,21 @@ -namespace Ocelot.Provider.Eureka -{ - using Microsoft.Extensions.DependencyInjection; - using ServiceDiscovery; - using Steeltoe.Common.Discovery; - - public static class EurekaProviderFactory - { - public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) => - { - var client = provider.GetService(); - - if (config.Type?.ToLower() == "eureka" && client != null) - { - return new Eureka(route.ServiceName, client); - } - - return null; - }; - } -} +namespace Ocelot.Provider.Eureka +{ + using Microsoft.Extensions.DependencyInjection; + using Ocelot.ServiceDiscovery; + using Steeltoe.Discovery; + + public static class EurekaProviderFactory + { + public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) => + { + var client = provider.GetService(); + + if (config.Type?.ToLower() == "eureka" && client != null) + { + return new Eureka(route.ServiceName, client); + } + + return null; + }; + } +} diff --git a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj index e808bf931..3581464d9 100644 --- a/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj +++ b/src/Ocelot.Provider.Eureka/Ocelot.Provider.Eureka.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1 + net5.0 true Provides Ocelot extensions to use Eureka Ocelot.Provider.Eureka @@ -27,7 +27,8 @@ - + + all diff --git a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj index 8f3428656..c8d1ebde1 100644 --- a/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj +++ b/src/Ocelot.Provider.Kubernetes/Ocelot.Provider.Kubernetes.csproj @@ -1,43 +1,43 @@ - - - - netcoreapp3.1 - true - Ocelot - Provides Ocelot extensions to use kubernetes - https://github.com/ThreeMammals/Ocelot - http://threemammals.com/images/ocelot_logo.png - - Ocelot.Provider.Kubernetes - Ocelot.Provider.Kubernetes - API Gateway;.NET core - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - true - false - 0.0.0-dev - geffzhang - - ..\..\codeanalysis.ruleset - - - - - - - - - - - - - - - - - - - - - + + + + net5.0 + true + Ocelot + Provides Ocelot extensions to use kubernetes + https://github.com/ThreeMammals/Ocelot + http://threemammals.com/images/ocelot_logo.png + + Ocelot.Provider.Kubernetes + Ocelot.Provider.Kubernetes + API Gateway;.NET core + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + true + false + 0.0.0-dev + geffzhang + + ..\..\codeanalysis.ruleset + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj index e082c6066..8764b0122 100644 --- a/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj +++ b/src/Ocelot.Provider.Polly/Ocelot.Provider.Polly.csproj @@ -1,38 +1,38 @@ - - - netcoreapp3.1 - true - Provides Ocelot extensions to use Polly.NET - Ocelot.Provider.Polly - 0.0.0-dev - Ocelot.Provider.Polly - Ocelot.Provider.Polly - API Gateway;.NET core - https://github.com/ThreeMammals/Ocelot.Provider.Polly - https://github.com/ThreeMammals/Ocelot.Provider.Polly - http://threemammals.com/images/ocelot_logo.png - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - ..\..\codeanalysis.ruleset - - - full - True - - - - - - - all - - - - - - - + + + net5.0 + true + Provides Ocelot extensions to use Polly.NET + Ocelot.Provider.Polly + 0.0.0-dev + Ocelot.Provider.Polly + Ocelot.Provider.Polly + API Gateway;.NET core + https://github.com/ThreeMammals/Ocelot.Provider.Polly + https://github.com/ThreeMammals/Ocelot.Provider.Polly + http://threemammals.com/images/ocelot_logo.png + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + True + false + Tom Pallister + ..\..\codeanalysis.ruleset + + + full + True + + + + + + + all + + + + + + + diff --git a/src/Ocelot.Provider.Rafty/BearerToken.cs b/src/Ocelot.Provider.Rafty/BearerToken.cs deleted file mode 100644 index c006aaf3a..000000000 --- a/src/Ocelot.Provider.Rafty/BearerToken.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Newtonsoft.Json; - - internal class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } -} diff --git a/src/Ocelot.Provider.Rafty/FakeCommand.cs b/src/Ocelot.Provider.Rafty/FakeCommand.cs deleted file mode 100644 index de611da74..000000000 --- a/src/Ocelot.Provider.Rafty/FakeCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using global::Rafty.FiniteStateMachine; - - public class FakeCommand : ICommand - { - public FakeCommand(string value) - { - this.Value = value; - } - - public string Value { get; private set; } - } -} diff --git a/src/Ocelot.Provider.Rafty/FilePeer.cs b/src/Ocelot.Provider.Rafty/FilePeer.cs deleted file mode 100644 index 4bb57548c..000000000 --- a/src/Ocelot.Provider.Rafty/FilePeer.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - public class FilePeer - { - public string HostAndPort { get; set; } - } -} diff --git a/src/Ocelot.Provider.Rafty/FilePeers.cs b/src/Ocelot.Provider.Rafty/FilePeers.cs deleted file mode 100644 index 4d5f9e39e..000000000 --- a/src/Ocelot.Provider.Rafty/FilePeers.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using System.Collections.Generic; - - public class FilePeers - { - public FilePeers() - { - Peers = new List(); - } - - public List Peers { get; set; } - } -} diff --git a/src/Ocelot.Provider.Rafty/FilePeersProvider.cs b/src/Ocelot.Provider.Rafty/FilePeersProvider.cs deleted file mode 100644 index ddca1ac2c..000000000 --- a/src/Ocelot.Provider.Rafty/FilePeersProvider.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Administration; - using Configuration.Repository; - using global::Rafty.Concensus.Peers; - using global::Rafty.Infrastructure; - using Microsoft.Extensions.Options; - using Middleware; - using System.Collections.Generic; - using System.Net.Http; - - public class FilePeersProvider : IPeersProvider - { - private readonly IOptions _options; - private readonly List _peers; - private IBaseUrlFinder _finder; - private IInternalConfigurationRepository _repo; - private IIdentityServerConfiguration _identityServerConfig; - - public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig) - { - _identityServerConfig = identityServerConfig; - _repo = repo; - _finder = finder; - _options = options; - _peers = new List(); - - var config = _repo.Get(); - foreach (var item in _options.Value.Peers) - { - var httpClient = new HttpClient(); - - //todo what if this errors? - var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig); - _peers.Add(httpPeer); - } - } - - public List Get() - { - return _peers; - } - } -} diff --git a/src/Ocelot.Provider.Rafty/HttpPeer.cs b/src/Ocelot.Provider.Rafty/HttpPeer.cs deleted file mode 100644 index c3ed9e658..000000000 --- a/src/Ocelot.Provider.Rafty/HttpPeer.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Administration; - using Configuration; - using global::Rafty.Concensus.Messages; - using global::Rafty.Concensus.Peers; - using global::Rafty.FiniteStateMachine; - using global::Rafty.Infrastructure; - using Middleware; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - - public class HttpPeer : IPeer - { - private readonly string _hostAndPort; - private readonly HttpClient _httpClient; - private readonly JsonSerializerSettings _jsonSerializerSettings; - private readonly string _baseSchemeUrlAndPort; - private BearerToken _token; - private readonly IInternalConfiguration _config; - private readonly IIdentityServerConfiguration _identityServerConfiguration; - - public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration) - { - _identityServerConfiguration = identityServerConfiguration; - _config = config; - Id = hostAndPort; - _hostAndPort = hostAndPort; - _httpClient = httpClient; - _jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = finder.Find(); - } - - public string Id { get; } - - public async Task Request(RequestVote requestVote) - { - if (_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content); - if (response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - } - - return new RequestVoteResponse(false, requestVote.Term); - } - - public async Task Request(AppendEntries appendEntries) - { - try - { - if (_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content); - if (response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - } - - return new AppendEntriesResponse(appendEntries.Term, false); - } - catch (Exception ex) - { - Console.WriteLine(ex); - return new AppendEntriesResponse(appendEntries.Term, false); - } - } - - public async Task> Request(T command) - where T : ICommand - { - Console.WriteLine("SENDING REQUEST...."); - if (_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content); - if (response.IsSuccessStatusCode) - { - Console.WriteLine("REQUEST OK...."); - var okResponse = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - return new OkResponse((T)okResponse.Command); - } - - Console.WriteLine("REQUEST NOT OK...."); - return new ErrorResponse(await response.Content.ReadAsStringAsync(), command); - } - - private async Task SetToken() - { - var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", _identityServerConfiguration.ApiName), - new KeyValuePair("client_secret", _identityServerConfiguration.ApiSecret), - new KeyValuePair("scope", _identityServerConfiguration.ApiName), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - var response = await _httpClient.PostAsync(tokenUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); - } - } -} diff --git a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj b/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj deleted file mode 100644 index 058fb0cfd..000000000 --- a/src/Ocelot.Provider.Rafty/Ocelot.Provider.Rafty.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - netcoreapp3.1 - true - Provides Ocelot extensions to use Rafty - Ocelot.Provider.Rafty - 0.0.0-dev - Ocelot.Provider.Rafty - Ocelot.Provider.Rafty - API Gateway;.NET core - https://github.com/ThreeMammals/Ocelot.Provider.Rafty - https://github.com/ThreeMammals/Ocelot.Provider.Rafty - http://threemammals.com/images/ocelot_logo.png - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - True - false - Tom Pallister - ..\..\codeanalysis.ruleset - - - full - True - - - - - - - - - - all - - - - - - diff --git a/src/Ocelot.Provider.Rafty/OcelotAdministrationBuilderExtensions.cs b/src/Ocelot.Provider.Rafty/OcelotAdministrationBuilderExtensions.cs deleted file mode 100644 index a04876594..000000000 --- a/src/Ocelot.Provider.Rafty/OcelotAdministrationBuilderExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Configuration.Setter; - using DependencyInjection; - using global::Rafty.Concensus.Node; - using global::Rafty.FiniteStateMachine; - using global::Rafty.Infrastructure; - using global::Rafty.Log; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.DependencyInjection.Extensions; - - public static class OcelotAdministrationBuilderExtensions - { - public static IOcelotAdministrationBuilder AddRafty(this IOcelotAdministrationBuilder builder) - { - var settings = new InMemorySettings(4000, 6000, 100, 10000); - builder.Services.RemoveAll(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(settings); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.Configure(builder.ConfigurationRoot); - return builder; - } - } -} diff --git a/src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs b/src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs deleted file mode 100644 index b054852ee..000000000 --- a/src/Ocelot.Provider.Rafty/OcelotFiniteStateMachine.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Configuration.Setter; - using global::Rafty.FiniteStateMachine; - using global::Rafty.Log; - using System.Threading.Tasks; - - public class OcelotFiniteStateMachine : IFiniteStateMachine - { - private readonly IFileConfigurationSetter _setter; - - public OcelotFiniteStateMachine(IFileConfigurationSetter setter) - { - _setter = setter; - } - - public async Task Handle(LogEntry log) - { - //todo - handle an error - //hack it to just cast as at the moment we know this is the only command :P - var hack = (UpdateFileConfiguration)log.CommandData; - await _setter.Set(hack.Configuration); - } - } -} diff --git a/src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs b/src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs deleted file mode 100644 index dd8b7610c..000000000 --- a/src/Ocelot.Provider.Rafty/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Ocelot")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d6df4206-0dba-41d8-884d-c3e08290fdbb")] diff --git a/src/Ocelot.Provider.Rafty/RaftController.cs b/src/Ocelot.Provider.Rafty/RaftController.cs deleted file mode 100644 index a8006cdce..000000000 --- a/src/Ocelot.Provider.Rafty/RaftController.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using global::Rafty.Concensus.Messages; - using global::Rafty.Concensus.Node; - using global::Rafty.FiniteStateMachine; - using Logging; - using Microsoft.AspNetCore.Authorization; - using Microsoft.AspNetCore.Mvc; - using Middleware; - using Newtonsoft.Json; - using System; - using System.IO; - using System.Threading.Tasks; - - [Authorize] - [Route("raft")] - public class RaftController : Controller - { - private readonly INode _node; - private readonly IOcelotLogger _logger; - private readonly string _baseSchemeUrlAndPort; - private readonly JsonSerializerSettings _jsonSerialiserSettings; - - public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder) - { - _jsonSerialiserSettings = new JsonSerializerSettings - { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = finder.Find(); - _logger = loggerFactory.CreateLogger(); - _node = node; - } - - [Route("appendentries")] - public async Task AppendEntries() - { - using (var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var appendEntries = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); - - var appendEntriesResponse = await _node.Handle(appendEntries); - - return new OkObjectResult(appendEntriesResponse); - } - } - - [Route("requestvote")] - public async Task RequestVote() - { - using (var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var requestVote = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); - - var requestVoteResponse = await _node.Handle(requestVote); - - return new OkObjectResult(requestVoteResponse); - } - } - - [Route("command")] - public async Task Command() - { - try - { - using (var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var command = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); - - var commandResponse = await _node.Accept(command); - - json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); - - return StatusCode(200, json); - } - } - catch (Exception e) - { - _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e); - throw; - } - } - } -} diff --git a/src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs b/src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs deleted file mode 100644 index 21fc97a81..000000000 --- a/src/Ocelot.Provider.Rafty/RaftyFileConfigurationSetter.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Configuration.File; - using Configuration.Setter; - using global::Rafty.Concensus.Node; - using global::Rafty.Infrastructure; - using System.Threading.Tasks; - - public class RaftyFileConfigurationSetter : IFileConfigurationSetter - { - private readonly INode _node; - - public RaftyFileConfigurationSetter(INode node) - { - _node = node; - } - - public async Task Set(FileConfiguration fileConfiguration) - { - var result = await _node.Accept(new UpdateFileConfiguration(fileConfiguration)); - - if (result.GetType() == typeof(ErrorResponse)) - { - return new Responses.ErrorResponse(new UnableToSaveAcceptCommand($"unable to save file configuration to state machine")); - } - - return new Responses.OkResponse(); - } - } -} diff --git a/src/Ocelot.Provider.Rafty/RaftyMiddlewareConfigurationProvider.cs b/src/Ocelot.Provider.Rafty/RaftyMiddlewareConfigurationProvider.cs deleted file mode 100644 index 62e9711da..000000000 --- a/src/Ocelot.Provider.Rafty/RaftyMiddlewareConfigurationProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using global::Rafty.Concensus.Node; - using global::Rafty.Infrastructure; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Middleware; - using System.Threading.Tasks; - - public static class RaftyMiddlewareConfigurationProvider - { - public static OcelotMiddlewareConfigurationDelegate Get = builder => - { - if (UsingRafty(builder)) - { - SetUpRafty(builder); - } - - return Task.CompletedTask; - }; - - private static bool UsingRafty(IApplicationBuilder builder) - { - var node = builder.ApplicationServices.GetService(); - if (node != null) - { - return true; - } - - return false; - } - - private static void SetUpRafty(IApplicationBuilder builder) - { - var applicationLifetime = builder.ApplicationServices.GetService(); - applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = builder.ApplicationServices.GetService(); - var nodeId = builder.ApplicationServices.GetService(); - node.Start(nodeId); - } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = app.ApplicationServices.GetService(); - node.Stop(); - } - } -} diff --git a/src/Ocelot.Provider.Rafty/SqlLiteLog.cs b/src/Ocelot.Provider.Rafty/SqlLiteLog.cs deleted file mode 100644 index 1be1645e9..000000000 --- a/src/Ocelot.Provider.Rafty/SqlLiteLog.cs +++ /dev/null @@ -1,334 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using global::Rafty.Infrastructure; - using global::Rafty.Log; - using Microsoft.Data.Sqlite; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - - public class SqlLiteLog : ILog - { - private readonly string _path; - private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1); - private readonly ILogger _logger; - private readonly NodeId _nodeId; - - public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _nodeId = nodeId; - _path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db"; - _sempaphore.Wait(); - - if (!File.Exists(_path)) - { - var fs = File.Create(_path); - - fs.Dispose(); - - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - const string sql = @"create table logs ( - id integer primary key, - data text not null - )"; - - using (var command = new SqliteCommand(sql, connection)) - { - var result = command.ExecuteNonQuery(); - - _logger.LogInformation(result == 0 - ? $"id: {_nodeId.Id} create database, result: {result}" - : $"id: {_nodeId.Id} did not create database., result: {result}"); - } - } - } - - _sempaphore.Release(); - } - - public async Task LastLogIndex() - { - _sempaphore.Wait(); - var result = 1; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select id from logs order by id desc limit 1"; - using (var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(await command.ExecuteScalarAsync()); - if (index > result) - { - result = index; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task LastLogTerm() - { - _sempaphore.Wait(); - long result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select data from logs order by id desc limit 1"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (log != null && log.Term > result) - { - result = log.Term; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Count() - { - _sempaphore.Wait(); - var result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using (var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(await command.ExecuteScalarAsync()); - if (index > result) - { - result = index; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Apply(LogEntry log) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var data = JsonConvert.SerializeObject(log, jsonSerializerSettings); - - //todo - sql injection dont copy this.. - var sql = $"insert into logs (data) values ('{data}')"; - _logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}"); - using (var command = new SqliteCommand(sql, connection)) - { - var result = await command.ExecuteNonQueryAsync(); - _logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}"); - } - - sql = "select last_insert_rowid()"; - using (var command = new SqliteCommand(sql, connection)) - { - var result = await command.ExecuteScalarAsync(); - _logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore"); - _sempaphore.Release(); - _logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite"); - return Convert.ToInt32(result); - } - } - } - - public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index};"; - _logger.LogInformation($"id: {_nodeId.Id} sql: {sql}"); - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - - _logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}"); - - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (logEntry != null && log != null && logEntry.Term != log.Term) - { - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {index};"; - _logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}"); - using (var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = await deleteCommand.ExecuteNonQueryAsync(); - } - } - } - } - - _sempaphore.Release(); - } - - public async Task IsDuplicate(int index, LogEntry logEntry) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index};"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - - if (logEntry != null && log != null && logEntry.Term == log.Term) - { - _sempaphore.Release(); - return true; - } - } - } - - _sempaphore.Release(); - return false; - } - - public async Task Get(int index) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - _sempaphore.Release(); - return log; - } - } - } - - public async Task> GetFrom(int index) - { - _sempaphore.Wait(); - var logsToReturn = new List<(int, LogEntry)>(); - - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select id, data from logs where id >= {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - using (var reader = await command.ExecuteReaderAsync()) - { - while (reader.Read()) - { - var id = Convert.ToInt32(reader[0]); - var data = (string)reader[1]; - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - logsToReturn.Add((id, log)); - } - } - } - - _sempaphore.Release(); - return logsToReturn; - } - } - - public async Task GetTermAtIndex(int index) - { - _sempaphore.Wait(); - long result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (log != null && log.Term > result) - { - result = log.Term; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Remove(int indexOfCommand) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {indexOfCommand};"; - _logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}"); - using (var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = await deleteCommand.ExecuteNonQueryAsync(); - } - } - - _sempaphore.Release(); - } - } -} diff --git a/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs b/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs deleted file mode 100644 index 961cd6ed9..000000000 --- a/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Errors; - - public class UnableToSaveAcceptCommand : Error - { - public UnableToSaveAcceptCommand(string message) - : base(message, OcelotErrorCode.UnknownError, 404) - { - } - } -} diff --git a/src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs b/src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs deleted file mode 100644 index 894a758a6..000000000 --- a/src/Ocelot.Provider.Rafty/UpdateFileConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ocelot.Provider.Rafty -{ - using Configuration.File; - using global::Rafty.FiniteStateMachine; - - public class UpdateFileConfiguration : ICommand - { - public UpdateFileConfiguration(FileConfiguration configuration) - { - Configuration = configuration; - } - - public FileConfiguration Configuration { get; private set; } - } -} diff --git a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj index f2401e024..1182a364c 100644 --- a/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj +++ b/src/Ocelot.Tracing.Butterfly/Ocelot.Tracing.Butterfly.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 true This package provides methods to integrate Butterfly tracing with Ocelot. Ocelot.Tracing.Butterfly diff --git a/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj b/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj index 9b6d5abae..e341adba2 100644 --- a/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj +++ b/src/Ocelot.Tracing.OpenTracing/Ocelot.Tracing.OpenTracing.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 0.0.0-dev Kjell-Åke Gafvelin This package provides OpenTracing support to Ocelot. diff --git a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs deleted file mode 100644 index 98f8309f9..000000000 --- a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ocelot.Authorisation -{ - using Ocelot.Errors; - using System.Net; - - public class ClaimValueNotAuthorisedError : Error - { - public ClaimValueNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError, 403) - { - } - } -} diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs deleted file mode 100644 index 0bd1579a6..000000000 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.Authorisation.Middleware -{ - using Microsoft.AspNetCore.Builder; - - public static class AuthorisationMiddlewareMiddlewareExtensions - { - public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - } -} diff --git a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs b/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs deleted file mode 100644 index c7f403ded..000000000 --- a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.Authorisation -{ - using Ocelot.Errors; - - public class ScopeNotAuthorisedError : Error - { - public ScopeNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ScopeNotAuthorisedError, 403) - { - } - } -} diff --git a/src/Ocelot/Authorization/ClaimValueNotAuthorizedError.cs b/src/Ocelot/Authorization/ClaimValueNotAuthorizedError.cs new file mode 100644 index 000000000..c4d4e8144 --- /dev/null +++ b/src/Ocelot/Authorization/ClaimValueNotAuthorizedError.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Authorization +{ + using Ocelot.Errors; + using System.Net; + + public class ClaimValueNotAuthorizedError : Error + { + public ClaimValueNotAuthorizedError(string message) + : base(message, OcelotErrorCode.ClaimValueNotAuthorizedError, 403) + { + } + } +} diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorization/ClaimsAuthorizer.cs similarity index 86% rename from src/Ocelot/Authorisation/ClaimsAuthoriser.cs rename to src/Ocelot/Authorization/ClaimsAuthorizer.cs index d6c994402..15da73d74 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorization/ClaimsAuthorizer.cs @@ -1,4 +1,4 @@ -namespace Ocelot.Authorisation +namespace Ocelot.Authorization { using Ocelot.Infrastructure.Claims.Parser; using Ocelot.DownstreamRouteFinder.UrlMatcher; @@ -8,16 +8,16 @@ using System.Security.Claims; using System.Text.RegularExpressions; - public class ClaimsAuthoriser : IClaimsAuthoriser + public class ClaimsAuthorizer : IClaimsAuthorizer { private readonly IClaimsParser _claimsParser; - public ClaimsAuthoriser(IClaimsParser claimsParser) + public ClaimsAuthorizer(IClaimsParser claimsParser) { _claimsParser = claimsParser; } - public Response Authorise( + public Response Authorize( ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement, List urlPathPlaceholderNameAndValues @@ -45,10 +45,10 @@ List urlPathPlaceholderNameAndValues { // match var actualValue = matchingPlaceholders[0].Value; - var authorised = values.Data.Contains(actualValue); - if (!authorised) + var authorized = values.Data.Contains(actualValue); + if (!authorized) { - return new ErrorResponse(new ClaimValueNotAuthorisedError( + return new ErrorResponse(new ClaimValueNotAuthorizedError( $"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}")); } } @@ -57,12 +57,12 @@ List urlPathPlaceholderNameAndValues // config error if (matchingPlaceholders.Length == 0) { - return new ErrorResponse(new ClaimValueNotAuthorisedError( + return new ErrorResponse(new ClaimValueNotAuthorizedError( $"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}")); } else { - return new ErrorResponse(new ClaimValueNotAuthorisedError( + return new ErrorResponse(new ClaimValueNotAuthorizedError( $"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}")); } } @@ -70,10 +70,10 @@ List urlPathPlaceholderNameAndValues else { // static claim - var authorised = values.Data.Contains(required.Value); - if (!authorised) + var authorized = values.Data.Contains(required.Value); + if (!authorized) { - return new ErrorResponse(new ClaimValueNotAuthorisedError( + return new ErrorResponse(new ClaimValueNotAuthorizedError( $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); } } diff --git a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs b/src/Ocelot/Authorization/IClaimsAuthorizer.cs similarity index 74% rename from src/Ocelot/Authorisation/IClaimsAuthoriser.cs rename to src/Ocelot/Authorization/IClaimsAuthorizer.cs index 5e4b9c59c..dc89f0109 100644 --- a/src/Ocelot/Authorisation/IClaimsAuthoriser.cs +++ b/src/Ocelot/Authorization/IClaimsAuthorizer.cs @@ -2,16 +2,16 @@ using Ocelot.Responses; using System.Security.Claims; -namespace Ocelot.Authorisation +namespace Ocelot.Authorization { using System.Collections.Generic; - public interface IClaimsAuthoriser + public interface IClaimsAuthorizer { - Response Authorise( + Response Authorize( ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement, - List urlPathPlaceholderNameAndValues + List urlPathPlaceholderNameAndValues ); } -} +} diff --git a/src/Ocelot/Authorisation/IScopesAuthoriser.cs b/src/Ocelot/Authorization/IScopesAuthorizer.cs similarity index 50% rename from src/Ocelot/Authorisation/IScopesAuthoriser.cs rename to src/Ocelot/Authorization/IScopesAuthorizer.cs index 57047ce75..e0041cd28 100644 --- a/src/Ocelot/Authorisation/IScopesAuthoriser.cs +++ b/src/Ocelot/Authorization/IScopesAuthorizer.cs @@ -1,12 +1,12 @@ using Ocelot.Responses; using System.Security.Claims; -namespace Ocelot.Authorisation +namespace Ocelot.Authorization { using System.Collections.Generic; - public interface IScopesAuthoriser + public interface IScopesAuthorizer { - Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes); + Response Authorize(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes); } -} +} diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorization/Middleware/AuthorizationMiddleware.cs similarity index 58% rename from src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs rename to src/Ocelot/Authorization/Middleware/AuthorizationMiddleware.cs index 0d02c62ed..3a9c5d802 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorization/Middleware/AuthorizationMiddleware.cs @@ -1,4 +1,4 @@ -namespace Ocelot.Authorisation.Middleware +namespace Ocelot.Authorization.Middleware { using Ocelot.Configuration; using Ocelot.Logging; @@ -8,21 +8,21 @@ using Microsoft.AspNetCore.Http; using Ocelot.DownstreamRouteFinder.Middleware; - public class AuthorisationMiddleware : OcelotMiddleware + public class AuthorizationMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private readonly IClaimsAuthoriser _claimsAuthoriser; - private readonly IScopesAuthoriser _scopesAuthoriser; + private readonly IClaimsAuthorizer _claimsAuthorizer; + private readonly IScopesAuthorizer _scopesAuthorizer; - public AuthorisationMiddleware(RequestDelegate next, - IClaimsAuthoriser claimsAuthoriser, - IScopesAuthoriser scopesAuthoriser, + public AuthorizationMiddleware(RequestDelegate next, + IClaimsAuthorizer claimsAuthorizer, + IScopesAuthorizer scopesAuthorizer, IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) + : base(loggerFactory.CreateLogger()) { _next = next; - _claimsAuthoriser = claimsAuthoriser; - _scopesAuthoriser = scopesAuthoriser; + _claimsAuthorizer = claimsAuthorizer; + _scopesAuthorizer = scopesAuthorizer; } public async Task Invoke(HttpContext httpContext) @@ -33,65 +33,65 @@ public async Task Invoke(HttpContext httpContext) { Logger.LogInformation("route is authenticated scopes must be checked"); - var authorised = _scopesAuthoriser.Authorise(httpContext.User, downstreamRoute.AuthenticationOptions.AllowedScopes); + var authorized = _scopesAuthorizer.Authorize(httpContext.User, downstreamRoute.AuthenticationOptions.AllowedScopes); - if (authorised.IsError) + if (authorized.IsError) { - Logger.LogWarning("error authorising user scopes"); + Logger.LogWarning("error authorizing user scopes"); - httpContext.Items.UpsertErrors(authorised.Errors); + httpContext.Items.UpsertErrors(authorized.Errors); return; } - if (IsAuthorised(authorised)) + if (IsAuthorized(authorized)) { - Logger.LogInformation("user scopes is authorised calling next authorisation checks"); + Logger.LogInformation("user scopes is authorized calling next authorization checks"); } else { - Logger.LogWarning("user scopes is not authorised setting pipeline error"); + Logger.LogWarning("user scopes is not authorized setting pipeline error"); - httpContext.Items.SetError(new UnauthorisedError( + httpContext.Items.SetError(new UnauthorizedError( $"{httpContext.User.Identity.Name} unable to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}")); } } - if (!IsOptionsHttpMethod(httpContext) && IsAuthorisedRoute(downstreamRoute)) + if (!IsOptionsHttpMethod(httpContext) && IsAuthorizedRoute(downstreamRoute)) { - Logger.LogInformation("route is authorised"); + Logger.LogInformation("route is authorized"); - var authorised = _claimsAuthoriser.Authorise(httpContext.User, downstreamRoute.RouteClaimsRequirement, httpContext.Items.TemplatePlaceholderNameAndValues()); + var authorized = _claimsAuthorizer.Authorize(httpContext.User, downstreamRoute.RouteClaimsRequirement, httpContext.Items.TemplatePlaceholderNameAndValues()); - if (authorised.IsError) + if (authorized.IsError) { - Logger.LogWarning($"Error whilst authorising {httpContext.User.Identity.Name}. Setting pipeline error"); + Logger.LogWarning($"Error whilst authorizing {httpContext.User.Identity.Name}. Setting pipeline error"); - httpContext.Items.UpsertErrors(authorised.Errors); + httpContext.Items.UpsertErrors(authorized.Errors); return; } - if (IsAuthorised(authorised)) + if (IsAuthorized(authorized)) { - Logger.LogInformation($"{httpContext.User.Identity.Name} has succesfully been authorised for {downstreamRoute.UpstreamPathTemplate.OriginalValue}."); + Logger.LogInformation($"{httpContext.User.Identity.Name} has succesfully been authorized for {downstreamRoute.UpstreamPathTemplate.OriginalValue}."); await _next.Invoke(httpContext); } else { - Logger.LogWarning($"{httpContext.User.Identity.Name} is not authorised to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error"); + Logger.LogWarning($"{httpContext.User.Identity.Name} is not authorized to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error"); - httpContext.Items.SetError(new UnauthorisedError($"{httpContext.User.Identity.Name} is not authorised to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}")); + httpContext.Items.SetError(new UnauthorizedError($"{httpContext.User.Identity.Name} is not authorized to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}")); } } else { - Logger.LogInformation($"{downstreamRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + Logger.LogInformation($"{downstreamRoute.DownstreamPathTemplate.Value} route does not require user to be authorized"); await _next.Invoke(httpContext); } } - private static bool IsAuthorised(Response authorised) + private static bool IsAuthorized(Response authorized) { - return authorised.Data; + return authorized.Data; } private static bool IsAuthenticatedRoute(DownstreamRoute route) @@ -99,9 +99,9 @@ private static bool IsAuthenticatedRoute(DownstreamRoute route) return route.IsAuthenticated; } - private static bool IsAuthorisedRoute(DownstreamRoute route) + private static bool IsAuthorizedRoute(DownstreamRoute route) { - return route.IsAuthorised; + return route.IsAuthorized; } private static bool IsOptionsHttpMethod(HttpContext httpContext) diff --git a/src/Ocelot/Authorization/Middleware/AuthorizationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorization/Middleware/AuthorizationMiddlewareMiddlewareExtensions.cs new file mode 100644 index 000000000..260cc86e0 --- /dev/null +++ b/src/Ocelot/Authorization/Middleware/AuthorizationMiddlewareMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Authorization.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class AuthorizationMiddlewareMiddlewareExtensions + { + public static IApplicationBuilder UseAuthorizationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Authorization/ScopeNotAuthorizedError.cs b/src/Ocelot/Authorization/ScopeNotAuthorizedError.cs new file mode 100644 index 000000000..dc5823a32 --- /dev/null +++ b/src/Ocelot/Authorization/ScopeNotAuthorizedError.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Authorization +{ + using Ocelot.Errors; + + public class ScopeNotAuthorizedError : Error + { + public ScopeNotAuthorizedError(string message) + : base(message, OcelotErrorCode.ScopeNotAuthorizedError, 403) + { + } + } +} diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorization/ScopesAuthorizer.cs similarity index 78% rename from src/Ocelot/Authorisation/ScopesAuthoriser.cs rename to src/Ocelot/Authorization/ScopesAuthorizer.cs index 8344d80bc..7fd7e2aaf 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorization/ScopesAuthorizer.cs @@ -1,47 +1,47 @@ -using Ocelot.Responses; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ScopesAuthoriser : IScopesAuthoriser - { - private readonly IClaimsParser _claimsParser; - private readonly string _scope = "scope"; - - public ScopesAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) - { - if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) - { - return new OkResponse(true); - } - - var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope); - - if (values.IsError) - { - return new ErrorResponse(values.Errors); - } - - var userScopes = values.Data; - - var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - - if (matchesScopes.Count == 0) - { - return new ErrorResponse( - new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); - } - - return new OkResponse(true); - } - } -} +using Ocelot.Responses; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Ocelot.Authorization +{ + using Infrastructure.Claims.Parser; + + public class ScopesAuthorizer : IScopesAuthorizer + { + private readonly IClaimsParser _claimsParser; + private readonly string _scope = "scope"; + + public ScopesAuthorizer(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response Authorize(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) + { + if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) + { + return new OkResponse(true); + } + + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope); + + if (values.IsError) + { + return new ErrorResponse(values.Errors); + } + + var userScopes = values.Data; + + var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + + if (matchesScopes.Count == 0) + { + return new ErrorResponse( + new ScopeNotAuthorizedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); + } + + return new OkResponse(true); + } + } +} diff --git a/src/Ocelot/Authorisation/UnauthorisedError.cs b/src/Ocelot/Authorization/UnauthorizedError.cs similarity index 50% rename from src/Ocelot/Authorisation/UnauthorisedError.cs rename to src/Ocelot/Authorization/UnauthorizedError.cs index fc32ea831..689f9e2f2 100644 --- a/src/Ocelot/Authorisation/UnauthorisedError.cs +++ b/src/Ocelot/Authorization/UnauthorizedError.cs @@ -1,10 +1,10 @@ -namespace Ocelot.Authorisation +namespace Ocelot.Authorization { using Ocelot.Errors; - public class UnauthorisedError : Error + public class UnauthorizedError : Error { - public UnauthorisedError(string message) + public UnauthorizedError(string message) : base(message, OcelotErrorCode.UnauthorizedError, 403) { } diff --git a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs b/src/Ocelot/Authorization/UserDoesNotHaveClaimError.cs similarity index 84% rename from src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs rename to src/Ocelot/Authorization/UserDoesNotHaveClaimError.cs index 6f9aa3eb2..08c19d9f8 100644 --- a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs +++ b/src/Ocelot/Authorization/UserDoesNotHaveClaimError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Authorisation -{ +namespace Ocelot.Authorization +{ using Ocelot.Errors; public class UserDoesNotHaveClaimError : Error { - public UserDoesNotHaveClaimError(string message) + public UserDoesNotHaveClaimError(string message) : base(message, OcelotErrorCode.UserDoesNotHaveClaimError, 403) { } } -} +} diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index b51c33db8..5ee5ddf73 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -18,7 +18,7 @@ public class DownstreamRouteBuilder private List _claimsToHeaders; private List _claimToClaims; private Dictionary _routeClaimRequirement; - private bool _isAuthorised; + private bool _isAuthorized; private List _claimToQueries; private List _claimToDownstreamPath; private string _requestIdHeaderKey; @@ -101,9 +101,9 @@ public DownstreamRouteBuilder WithIsAuthenticated(bool input) return this; } - public DownstreamRouteBuilder WithIsAuthorised(bool input) + public DownstreamRouteBuilder WithIsAuthorized(bool input) { - _isAuthorised = input; + _isAuthorized = input; return this; } @@ -289,7 +289,7 @@ public DownstreamRoute Build() _claimToClaims, _claimToDownstreamPath, _isAuthenticated, - _isAuthorised, + _isAuthorized, _authenticationOptions, new DownstreamPathTemplate(_downstreamPathTemplate), _loadBalancerKey, diff --git a/src/Ocelot/Configuration/Builder/RouteOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/RouteOptionsBuilder.cs index ef1cdcd32..7906dad39 100644 --- a/src/Ocelot/Configuration/Builder/RouteOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/RouteOptionsBuilder.cs @@ -3,7 +3,7 @@ namespace Ocelot.Configuration.Builder public class RouteOptionsBuilder { private bool _isAuthenticated; - private bool _isAuthorised; + private bool _isAuthorized; private bool _isCached; private bool _enableRateLimiting; private bool _useServiceDiscovery; @@ -20,9 +20,9 @@ public RouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) return this; } - public RouteOptionsBuilder WithIsAuthorised(bool isAuthorised) + public RouteOptionsBuilder WithIsAuthorized(bool isAuthorized) { - _isAuthorised = isAuthorised; + _isAuthorized = isAuthorized; return this; } @@ -40,7 +40,7 @@ public RouteOptionsBuilder WithUseServiceDiscovery(bool useServiceDiscovery) public RouteOptions Build() { - return new RouteOptions(_isAuthenticated, _isAuthorised, _isCached, _enableRateLimiting, _useServiceDiscovery); + return new RouteOptions(_isAuthenticated, _isAuthorized, _isCached, _enableRateLimiting, _useServiceDiscovery); } } } diff --git a/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs index 506bd32aa..703cb71f0 100644 --- a/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Creator -{ - using Ocelot.Configuration.Builder; +{ + using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; public class RouteOptionsCreator : IRouteOptionsCreator @@ -8,14 +8,14 @@ public class RouteOptionsCreator : IRouteOptionsCreator public RouteOptions Create(FileRoute fileRoute) { var isAuthenticated = IsAuthenticated(fileRoute); - var isAuthorised = IsAuthorised(fileRoute); + var isAuthorized = IsAuthorized(fileRoute); var isCached = IsCached(fileRoute); var enableRateLimiting = IsEnableRateLimiting(fileRoute); var useServiceDiscovery = !string.IsNullOrEmpty(fileRoute.ServiceName); var options = new RouteOptionsBuilder() .WithIsAuthenticated(isAuthenticated) - .WithIsAuthorised(isAuthorised) + .WithIsAuthorized(isAuthorized) .WithIsCached(isCached) .WithRateLimiting(enableRateLimiting) .WithUseServiceDiscovery(useServiceDiscovery) @@ -34,7 +34,7 @@ private bool IsAuthenticated(FileRoute fileRoute) return !string.IsNullOrEmpty(fileRoute.AuthenticationOptions?.AuthenticationProviderKey); } - private bool IsAuthorised(FileRoute fileRoute) + private bool IsAuthorized(FileRoute fileRoute) { return fileRoute.RouteClaimsRequirement?.Count > 0; } diff --git a/src/Ocelot/Configuration/Creator/RoutesCreator.cs b/src/Ocelot/Configuration/Creator/RoutesCreator.cs index a5b348624..48845ce06 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -119,7 +119,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf .WithClaimsToHeaders(claimsToHeaders) .WithClaimsToClaims(claimsToClaims) .WithRouteClaimsRequirement(fileRoute.RouteClaimsRequirement) - .WithIsAuthorised(fileRouteOptions.IsAuthorised) + .WithIsAuthorized(fileRouteOptions.IsAuthorized) .WithClaimsToQueries(claimsToQueries) .WithClaimsToDownstreamPath(claimsToDownstreamPath) .WithRequestIdKey(requestIdKey) diff --git a/src/Ocelot/Configuration/DownstreamRoute.cs b/src/Ocelot/Configuration/DownstreamRoute.cs index 8dd9f30dd..b2bde0ed2 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -31,7 +31,7 @@ public DownstreamRoute( List claimsToClaims, List claimsToPath, bool isAuthenticated, - bool isAuthorised, + bool isAuthorized, AuthenticationOptions authenticationOptions, DownstreamPathTemplate downstreamPathTemplate, string loadBalancerKey, @@ -69,7 +69,7 @@ public DownstreamRoute( ClaimsToClaims = claimsToClaims ?? new List(); ClaimsToPath = claimsToPath ?? new List(); IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; + IsAuthorized = isAuthorized; AuthenticationOptions = authenticationOptions; DownstreamPathTemplate = downstreamPathTemplate; LoadBalancerKey = loadBalancerKey; @@ -102,7 +102,7 @@ public DownstreamRoute( public List ClaimsToClaims { get; } public List ClaimsToPath { get; } public bool IsAuthenticated { get; } - public bool IsAuthorised { get; } + public bool IsAuthorized { get; } public AuthenticationOptions AuthenticationOptions { get; } public DownstreamPathTemplate DownstreamPathTemplate { get; } public string LoadBalancerKey { get; } diff --git a/src/Ocelot/Configuration/RouteOptions.cs b/src/Ocelot/Configuration/RouteOptions.cs index 9b5ba8d94..411235989 100644 --- a/src/Ocelot/Configuration/RouteOptions.cs +++ b/src/Ocelot/Configuration/RouteOptions.cs @@ -2,17 +2,17 @@ namespace Ocelot.Configuration { public class RouteOptions { - public RouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting, bool useServiceDiscovery) + public RouteOptions(bool isAuthenticated, bool isAuthorized, bool isCached, bool isEnableRateLimiting, bool useServiceDiscovery) { IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; + IsAuthorized = isAuthorized; IsCached = isCached; EnableRateLimiting = isEnableRateLimiting; UseServiceDiscovery = useServiceDiscovery; } public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } + public bool IsAuthorized { get; private set; } public bool IsCached { get; private set; } public bool EnableRateLimiting { get; private set; } public bool UseServiceDiscovery { get; private set; } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index f4fbf8447..20c4e4bb3 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -5,7 +5,7 @@ namespace Ocelot.DependencyInjection using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; - using Ocelot.Authorisation; + using Ocelot.Authorization; using Ocelot.Cache; using Ocelot.Claims; using Ocelot.Configuration; @@ -96,8 +96,8 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 468641816..9063e7142 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -16,8 +16,8 @@ public enum OcelotErrorCode NoInstructionsError = 11, InstructionNotForClaimsError = 12, UnauthorizedError = 13, - ClaimValueNotAuthorisedError = 14, - ScopeNotAuthorisedError = 15, + ClaimValueNotAuthorizedError = 14, + ScopeNotAuthorizedError = 15, UserDoesNotHaveClaimError = 16, DownstreamPathTemplateContainsSchemeError = 17, DownstreamPathNullOrEmptyError = 18, diff --git a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs index b1f1bf997..f24efa2d6 100644 --- a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -39,22 +39,22 @@ public class OcelotPipelineConfiguration public Func, Task> AuthenticationMiddleware { get; set; } /// - /// This is to allow the user to run any extra authorisation before the Ocelot authentication + /// This is to allow the user to run any extra authorization before the Ocelot authentication /// kicks in /// /// - /// This is to allow the user to run any extra authorisation before the Ocelot authentication + /// This is to allow the user to run any extra authorization before the Ocelot authentication /// kicks in /// - public Func, Task> PreAuthorisationMiddleware { get; set; } + public Func, Task> PreAuthorizationMiddleware { get; set; } /// - /// This allows the user to completely override the ocelot authorisation middleware + /// This allows the user to completely override the ocelot authorization middleware /// /// - /// This allows the user to completely override the ocelot authorisation middleware + /// This allows the user to completely override the ocelot authorization middleware /// - public Func, Task> AuthorisationMiddleware { get; set; } + public Func, Task> AuthorizationMiddleware { get; set; } /// /// This allows the user to implement there own query string manipulation logic diff --git a/src/Ocelot/Middleware/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/OcelotPipelineExtensions.cs index 6158c72b3..58ab855df 100644 --- a/src/Ocelot/Middleware/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/OcelotPipelineExtensions.cs @@ -8,7 +8,7 @@ using Ocelot.Responder.Middleware; using Ocelot.Security.Middleware; using Ocelot.Authentication.Middleware; - using Ocelot.Authorisation.Middleware; + using Ocelot.Authorization.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; using Ocelot.DownstreamRouteFinder.Middleware; @@ -102,23 +102,23 @@ public static RequestDelegate BuildOcelotPipeline(this IApplicationBuilder app, app.Use(pipelineConfiguration.AuthenticationMiddleware); } - // The next thing we do is look at any claims transforms in case this is important for authorisation + // The next thing we do is look at any claims transforms in case this is important for authorization app.UseClaimsToClaimsMiddleware(); - // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. - app.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); + // Allow pre authorization logic. The idea being people might want to run something custom before what is built in. + app.UseIfNotNull(pipelineConfiguration.PreAuthorizationMiddleware); // Now we have authenticated and done any claims transformation we - // can authorise the request + // can authorize the request // We allow the ocelot middleware to be overriden by whatever the // user wants - if (pipelineConfiguration.AuthorisationMiddleware == null) + if (pipelineConfiguration.AuthorizationMiddleware == null) { - app.UseAuthorisationMiddleware(); + app.UseAuthorizationMiddleware(); } else { - app.Use(pipelineConfiguration.AuthorisationMiddleware); + app.Use(pipelineConfiguration.AuthorizationMiddleware); } // Now we can run the claims to headers transformation middleware diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 8f4f981d1..4cd8640f6 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1 + net5.0 true Ocelot is an API Gateway. The 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. reference tokens. 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 @@ -24,12 +24,12 @@ - - - + + + NU1701 - + all diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index 39b5e749a..0f74a7adc 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -14,8 +14,8 @@ public int Map(List errors) } if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError - || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError - || e.Code == OcelotErrorCode.ScopeNotAuthorisedError + || e.Code == OcelotErrorCode.ClaimValueNotAuthorizedError + || e.Code == OcelotErrorCode.ScopeNotAuthorizedError || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError || e.Code == OcelotErrorCode.CannotFindClaimError)) { diff --git a/src/Ocelot/Responses/Response.cs b/src/Ocelot/Responses/Response.cs index a10d480b9..7158f010a 100644 --- a/src/Ocelot/Responses/Response.cs +++ b/src/Ocelot/Responses/Response.cs @@ -1,22 +1,22 @@ -using Ocelot.Errors; -using System.Collections.Generic; - -namespace Ocelot.Responses -{ - public abstract class Response - { - protected Response() - { - Errors = new List(); - } - - protected Response(List errors) - { - Errors = errors ?? new List(); +using Ocelot.Errors; +using System.Collections.Generic; + +namespace Ocelot.Responses +{ + public abstract class Response + { + protected Response() + { + Errors = new List(); + } + + protected Response(List errors) + { + Errors = errors ?? new List(); } - public List Errors { get; } - - public bool IsError => Errors.Count > 0; - } + public List Errors { get; } + + public bool IsError => Errors.Count > 0; + } } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index f019c12f2..5c541ac6f 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -1,119 +1,119 @@ -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.AccessTokenValidation; - using IdentityServer4.Models; - using IdentityServer4.Test; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Configuration.File; - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Security.Claims; - using TestStack.BDDfy; +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.AccessTokenValidation; + using IdentityServer4.Models; + using IdentityServer4.Test; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Security.Claims; + using TestStack.BDDfy; using Xunit; - - public class AuthenticationTests : IDisposable - { - private readonly Steps _steps; - private IWebHost _identityServerBuilder; - private string _identityServerRootUrl; - private string _downstreamServicePath = "/"; - private string _downstreamServiceHost = "localhost"; - private string _downstreamServiceScheme = "http"; - private string _downstreamServiceUrl = "http://localhost:"; - private readonly Action _options; - private readonly ServiceHandler _serviceHandler; - - public AuthenticationTests() - { - _serviceHandler = new ServiceHandler(); + + public class AuthenticationTests : IDisposable + { + private readonly Steps _steps; + private IWebHost _identityServerBuilder; + private string _identityServerRootUrl; + private string _downstreamServicePath = "/"; + private string _downstreamServiceHost = "localhost"; + private string _downstreamServiceScheme = "http"; + private string _downstreamServiceUrl = "http://localhost:"; + private readonly Action _options; + private readonly ServiceHandler _serviceHandler; + + public AuthenticationTests() + { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); var identityServerPort = RandomPortFinder.GetRandomPort(); - _identityServerRootUrl = $"http://localhost:{identityServerPort}"; - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_401_using_identity_server_access_token() - { + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_401_using_identity_server_access_token() + { int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host =_downstreamServiceHost, - Port = port, - } - }, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 201, string.Empty)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_using_identity_server() - { - int port = RandomPortFinder.GetRandomPort(); + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = port, + }, + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn($"{_downstreamServiceUrl}{port}", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_using_identity_server() + { + int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host =_downstreamServiceHost, - Port = port, - } - }, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = port, + }, + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) @@ -125,38 +125,38 @@ public void should_return_response_200_using_identity_server() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host =_downstreamServiceHost, - Port = port, - } - }, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = port, + }, + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) @@ -167,38 +167,38 @@ public void should_return_response_401_using_identity_server_with_token_requeste .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_201_using_identity_server_access_token() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_return_201_using_identity_server_access_token() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host =_downstreamServiceHost, - Port = port, - } - }, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = port, + }, + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt)) @@ -210,38 +210,38 @@ public void should_return_201_using_identity_server_access_token() .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - [Fact] - public void should_return_201_using_identity_server_reference_token() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_return_201_using_identity_server_reference_token() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = _downstreamServicePath, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host =_downstreamServiceHost, - Port = port, - } - }, - DownstreamScheme = _downstreamServiceScheme, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = _downstreamServicePath, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host =_downstreamServiceHost, + Port = port, + }, + }, + DownstreamScheme = _downstreamServiceScheme, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test" + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference)) @@ -253,126 +253,131 @@ public void should_return_201_using_identity_server_reference_token() .And(x => _steps.GivenThePostHasContent("postContent")) .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("api.readOnly"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId" - } - }, - new ApiResource - { - Name = api2Name, - Description = "My second API", - Enabled = true, - DisplayName = "second test", - Scopes = new List() - { - new Scope("api2"), - new Scope("api2.readOnly"), - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId" - } - }, - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, api2Name, "api.readOnly", "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "321") - } - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + new ApiScope(api2Name, "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "api.readOnly", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", + }, + }, + new ApiResource + { + Name = api2Name, + Description = "My second API", + Enabled = true, + DisplayName = "second test", + Scopes = new List() + { + "api2", + "api2.readOnly", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, api2Name, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "321"), + }, + }, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } } diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorizationTests.cs similarity index 60% rename from test/Ocelot.AcceptanceTests/AuthorisationTests.cs rename to test/Ocelot.AcceptanceTests/AuthorizationTests.cs index 4f8a28dfc..394c76bc2 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorizationTests.cs @@ -1,91 +1,91 @@ -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.AccessTokenValidation; - using IdentityServer4.Models; - using IdentityServer4.Test; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Configuration.File; - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using System.Security.Claims; - using TestStack.BDDfy; - using Xunit; - - public class AuthorisationTests : IDisposable - { - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private readonly Action _options; - private string _identityServerRootUrl; - private readonly ServiceHandler _serviceHandler; - - public AuthorisationTests() - { - _serviceHandler = new ServiceHandler(); +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.AccessTokenValidation; + using IdentityServer4.Models; + using IdentityServer4.Test; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Security.Claims; + using TestStack.BDDfy; + using Xunit; + + public class AuthorizationTests : IDisposable + { + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private readonly Action _options; + private string _identityServerRootUrl; + private readonly ServiceHandler _serviceHandler; + + public AuthorizationTests() + { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); var identityServerPort = RandomPortFinder.GetRandomPort(); - _identityServerRootUrl = $"http://localhost:{identityServerPort}"; - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_authorising_route() - { + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_authorizing_route() + { int port = RandomPortFinder.GetRandomPort(); - - var configuration = new FileConfiguration + + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + RouteClaimsRequirement = + { + {"UserType", "registered"}, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) @@ -97,54 +97,54 @@ public void should_return_response_200_authorising_route() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_403_authorising_route() - { - int port = RandomPortFinder.GetRandomPort(); + .BDDfy(); + } + + [Fact] + public void should_return_response_403_authorizing_route() + { + int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - AddClaimsToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"UserId", "Claims[sub] > value[1] > |"} - }, - RouteClaimsRequirement = - { - {"UserType", "registered"} - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + AddClaimsToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + RouteClaimsRequirement = + { + {"UserType", "registered"}, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) @@ -155,39 +155,39 @@ public void should_return_response_403_authorising_route() .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_using_identity_server_with_allowed_scope() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_return_response_200_using_identity_server_with_allowed_scope() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, - }, - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List{ "api", "api.readOnly", "openid", "offline_access" }, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) @@ -198,39 +198,39 @@ public void should_return_response_200_using_identity_server_with_allowed_scope( .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_403_using_identity_server_with_scope_not_allowed() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_return_response_403_using_identity_server_with_scope_not_allowed() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List{ "api", "openid", "offline_access" }, - }, - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List{ "api", "openid", "offline_access" }, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) @@ -241,57 +241,57 @@ public void should_return_response_403_using_identity_server_with_scope_not_allo .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) - .BDDfy(); - } - - [Fact] - public void should_fix_issue_240() - { - int port = RandomPortFinder.GetRandomPort(); - + .BDDfy(); + } + + [Fact] + public void should_fix_issue_240() + { + int port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test" - }, - RouteClaimsRequirement = - { - {"Role", "User"} - } - } - } - }; - - var users = new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + }, + RouteClaimsRequirement = + { + {"Role", "User"}, + }, + }, + }, + }; + + var users = new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { new Claim("Role", "AdminUser"), - new Claim("Role", "User") - }, - } + new Claim("Role", "User"), + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, users)) @@ -303,170 +303,181 @@ public void should_fix_issue_240() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("api.readOnly"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - }, - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "321") - } - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, List users) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("api.readOnly"), - new Scope("openid"), - new Scope("offline_access"), - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId", "Role" - } - }, - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false, - } - }) - .AddTestUsers(users); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + new ApiScope("openid", "test"), + new ApiScope("offline_access", "test"), + new ApiScope("api.readOnly", "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "api.readOnly", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "321"), + }, + }, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, List users) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "api.readOnly", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", "Role", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "api.readOnly", "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(users); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index 08f07f8a3..4c998ef9e 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -1,11 +1,10 @@ namespace Ocelot.AcceptanceTests { using Butterfly.Client.AspNetCore; - using Configuration.File; + using Ocelot.Configuration.File; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; - using Rafty.Infrastructure; using Shouldly; using System; using System.Collections.Generic; diff --git a/test/Ocelot.AcceptanceTests/ClaimsToDownstreamPathTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToDownstreamPathTests.cs index 647948ec3..3bb37848f 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToDownstreamPathTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToDownstreamPathTests.cs @@ -1,205 +1,217 @@ -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.AccessTokenValidation; - using IdentityServer4.Models; - using IdentityServer4.Test; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Configuration.File; - using Shouldly; - using System; - using System.Collections.Generic; - using System.IO; - using System.Net; - using TestStack.BDDfy; - - public class ClaimsToDownstreamPathTests : IDisposable - { - private IWebHost _servicebuilder; - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl; - private string _downstreamFinalPath; - - public ClaimsToDownstreamPathTests() - { - var identityServerPort = RandomPortFinder.GetRandomPort(); - _identityServerRootUrl = $"http://localhost:{identityServerPort}"; - _steps = new Steps(); - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_200_and_change_downstream_path() - { - var user = new TestUser() - { - Username = "test", - Password = "test", - SubjectId = "registered|1231231", - }; - - int port = RandomPortFinder.GetRandomPort(); - - var configuration = new FileConfiguration - { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/users/{userId}", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - }, - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/users", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api", - }, - }, - ChangeDownstreamPathTemplate = - { - {"userId", "Claims[sub] > value[1] > |"}, - }, - }, - }, - }; - - this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, user)) - .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200)) - .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) - .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/users")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("UserId: 1231231")) - .And(x => _downstreamFinalPath.ShouldBe("/users/1231231")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - _downstreamFinalPath = context.Request.Path.Value; - - string userId = _downstreamFinalPath.Replace("/users/", string.Empty); - - var responseBody = $"UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - user - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } -} +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.AccessTokenValidation; + using IdentityServer4.Models; + using IdentityServer4.Test; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + using Shouldly; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using TestStack.BDDfy; + + public class ClaimsToDownstreamPathTests : IDisposable + { + private IWebHost _servicebuilder; + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl; + private string _downstreamFinalPath; + + public ClaimsToDownstreamPathTests() + { + var identityServerPort = RandomPortFinder.GetRandomPort(); + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _steps = new Steps(); + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_200_and_change_downstream_path() + { + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + }; + + int port = RandomPortFinder.GetRandomPort(); + + var configuration = new FileConfiguration + { + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/users/{userId}", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/users", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api", + }, + }, + ChangeDownstreamPathTemplate = + { + {"userId", "Claims[sub] > value[1] > |"}, + }, + }, + }, + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", 200)) + .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/users")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("UserId: 1231231")) + .And(x => ThenTheDownstreamPathIs("/users/1231231")) + .BDDfy(); + } + + private void ThenTheDownstreamPathIs(string path) + { + _downstreamFinalPath.ShouldBe(path); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + _downstreamFinalPath = context.Request.Path.Value; + + string userId = _downstreamFinalPath.Replace("/users/", string.Empty); + + var responseBody = $"UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + new ApiScope("openid", "test"), + new ApiScope("offline_access", "test"), + new ApiScope("api.readOnly", "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(new List + { + user, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 2712e2b79..711450928 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -1,101 +1,101 @@ -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.AccessTokenValidation; - using IdentityServer4.Models; - using IdentityServer4.Test; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Configuration.File; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using System.Security.Claims; - using TestStack.BDDfy; - - public class ClaimsToHeadersForwardingTests : IDisposable - { - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl; - private readonly ServiceHandler _serviceHandler; - - public ClaimsToHeadersForwardingTests() - { - _serviceHandler = new ServiceHandler(); +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.AccessTokenValidation; + using IdentityServer4.Models; + using IdentityServer4.Test; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Security.Claims; + using TestStack.BDDfy; + + public class ClaimsToHeadersForwardingTests : IDisposable + { + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl; + private readonly ServiceHandler _serviceHandler; + + public ClaimsToHeadersForwardingTests() + { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); var identityServerPort = RandomPortFinder.GetRandomPort(); - _identityServerRootUrl = $"http://localhost:{identityServerPort}"; - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_and_foward_claim_as_header() + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_and_foward_claim_as_header() { var user = new TestUser() { Username = "test", Password = "test", SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1"), + }, }; int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - }, - AddHeadersToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api", + }, + }, + AddHeadersToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, user)) @@ -107,98 +107,105 @@ public void should_return_response_200_and_foward_claim_as_header() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First(); - var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); - var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); - var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); - - var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - user - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + var customerId = context.Request.Headers.First(x => x.Key == "CustomerId").Value.First(); + var locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); + var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); + var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); + + var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + new ApiScope("openid", "test"), + new ApiScope("offline_access", "test"), + new ApiScope("api.readOnly", "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(new List + { + user, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } } diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index cfdbdbb49..22285e153 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -1,100 +1,99 @@ -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Primitives; -using Ocelot.Configuration.File; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Security.Claims; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - using IdentityServer4.Test; - using Shouldly; - - public class ClaimsToQueryStringForwardingTests : IDisposable - { - private IWebHost _servicebuilder; - private IWebHost _identityServerBuilder; - private readonly Steps _steps; - private Action _options; - private string _identityServerRootUrl; - private string _downstreamQueryString; - - public ClaimsToQueryStringForwardingTests() - { +namespace Ocelot.AcceptanceTests +{ + using IdentityServer4.Test; + using Shouldly; + using IdentityServer4.AccessTokenValidation; + using IdentityServer4.Models; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Primitives; + using Ocelot.Configuration.File; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Security.Claims; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsToQueryStringForwardingTests : IDisposable + { + private IWebHost _servicebuilder; + private IWebHost _identityServerBuilder; + private readonly Steps _steps; + private Action _options; + private string _identityServerRootUrl; + private string _downstreamQueryString; + + public ClaimsToQueryStringForwardingTests() + { _steps = new Steps(); var identityServerPort = RandomPortFinder.GetRandomPort(); - _identityServerRootUrl = $"http://localhost:{identityServerPort}"; - _options = o => - { - o.Authority = _identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - } - - [Fact] - public void should_return_response_200_and_foward_claim_as_query_string() + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _options = o => + { + o.Authority = _identityServerRootUrl; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + } + + [Fact] + public void should_return_response_200_and_foward_claim_as_query_string() { var user = new TestUser() { Username = "test", Password = "test", SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1"), + }, }; int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - }, - AddQueriesToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api", + }, + }, + AddQueriesToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, user)) @@ -106,61 +105,61 @@ public void should_return_response_200_and_foward_claim_as_query_string() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_and_foward_claim_as_query_string_and_preserve_original_string() + .BDDfy(); + } + + [Fact] + public void should_return_response_200_and_foward_claim_as_query_string_and_preserve_original_string() { var user = new TestUser() { Username = "test", Password = "test", SubjectId = "registered|1231231", - Claims = new List - { - new Claim("CustomerId", "123"), - new Claim("LocationId", "1") - } + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1"), + }, }; int port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { - Routes = new List - { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - AuthenticationOptions = new FileAuthenticationOptions - { - AuthenticationProviderKey = "Test", - AllowedScopes = new List - { - "openid", "offline_access", "api" - }, - }, - AddQueriesToRequest = - { - {"CustomerId", "Claims[CustomerId] > value"}, - {"LocationId", "Claims[LocationId] > value"}, - {"UserType", "Claims[sub] > value[0] > |"}, - {"UserId", "Claims[sub] > value[1] > |"} - } - } - } + Routes = new List + { + new FileRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api", + }, + }, + AddQueriesToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"}, + }, + }, + }, }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt, user)) @@ -172,120 +171,132 @@ public void should_return_response_200_and_foward_claim_as_query_string_and_pres .When(x => _steps.WhenIGetUrlOnTheApiGateway("/?test=1&test=2")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) - .And(_ => _downstreamQueryString.ShouldBe("?test=1&test=2&CustomerId=123&LocationId=1&UserId=1231231&UserType=registered")) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode) - { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - _downstreamQueryString = context.Request.QueryString.Value; - - StringValues customerId; - context.Request.Query.TryGetValue("CustomerId", out customerId); - - StringValues locationId; - context.Request.Query.TryGetValue("LocationId", out locationId); - - StringValues userType; - context.Request.Query.TryGetValue("UserType", out userType); - - StringValues userId; - context.Request.Query.TryGetValue("UserId", out userId); - - var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = "My API", - Enabled = true, - DisplayName = "test", - Scopes = new List() - { - new Scope("api"), - new Scope("openid"), - new Scope("offline_access") - }, - ApiSecrets = new List() - { - new Secret - { - Value = "secret".Sha256() - } - }, - UserClaims = new List() - { - "CustomerId", "LocationId", "UserType", "UserId" - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = "client", - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName, "openid", "offline_access" }, - AccessTokenType = tokenType, - Enabled = true, - RequireClientSecret = false - } - }) - .AddTestUsers(new List - { - user - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - _steps.VerifyIdentiryServerStarted(url); - } - - public void Dispose() - { - _servicebuilder?.Dispose(); - _steps.Dispose(); - _identityServerBuilder?.Dispose(); - } - } + .And(_ => ThenTheQueryStringIs("?test=1&test=2&CustomerId=123&LocationId=1&UserId=1231231&UserType=registered")) + .BDDfy(); + } + + private void ThenTheQueryStringIs(string queryString) + { + _downstreamQueryString.ShouldBe(queryString); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode) + { + _servicebuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + _downstreamQueryString = context.Request.QueryString.Value; + + StringValues customerId; + context.Request.Query.TryGetValue("CustomerId", out customerId); + + StringValues locationId; + context.Request.Query.TryGetValue("LocationId", out locationId); + + StringValues userType; + context.Request.Query.TryGetValue("UserType", out userType); + + StringValues userId; + context.Request.Query.TryGetValue("UserId", out userId); + + var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _servicebuilder.Start(); + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) + { + _identityServerBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List + { + new ApiScope(apiName, "test"), + new ApiScope("openid", "test"), + new ApiScope("offline_access", "test"), + new ApiScope("api.readOnly", "test"), + }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = "My API", + Enabled = true, + DisplayName = "test", + Scopes = new List() + { + "api", + "openid", + "offline_access", + }, + ApiSecrets = new List() + { + new Secret + { + Value = "secret".Sha256(), + }, + }, + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId", + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List { apiName, "openid", "offline_access" }, + AccessTokenType = tokenType, + Enabled = true, + RequireClientSecret = false, + }, + }) + .AddTestUsers(new List + { + user, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + }) + .Build(); + + _identityServerBuilder.Start(); + + _steps.VerifyIdentiryServerStarted(url); + } + + public void Dispose() + { + _servicebuilder?.Dispose(); + _steps.Dispose(); + _identityServerBuilder?.Dispose(); + } + } } diff --git a/test/Ocelot.AcceptanceTests/ContentTests.cs b/test/Ocelot.AcceptanceTests/ContentTests.cs index 8137bc8a5..1c5aefd8c 100644 --- a/test/Ocelot.AcceptanceTests/ContentTests.cs +++ b/test/Ocelot.AcceptanceTests/ContentTests.cs @@ -147,7 +147,7 @@ private void ThenTheContentTypeIsIs(string expected) private void ThenTheContentLengthShouldBeZero() { - _contentLength.ShouldBeEquivalentTo(0L); + _contentLength.ShouldBeNull(); } private void ThenTheContentLengthIs(int expected) diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index f420af6a0..e155010d0 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -10,7 +10,7 @@ using System.Net; using System.Threading.Tasks; using TestStack.BDDfy; - using Xunit; + using Xunit; public class CustomMiddlewareTests : IDisposable { @@ -32,13 +32,13 @@ public void should_call_pre_query_string_builder_middleware() { var configuration = new OcelotPipelineConfiguration { - AuthorisationMiddleware = async (ctx, next) => + AuthorizationMiddleware = async (ctx, next) => { _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -73,17 +73,17 @@ public void should_call_pre_query_string_builder_middleware() } [Fact] - public void should_call_authorisation_middleware() + public void should_call_authorization_middleware() { var configuration = new OcelotPipelineConfiguration { - AuthorisationMiddleware = async (ctx, next) => + AuthorizationMiddleware = async (ctx, next) => { _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -127,8 +127,8 @@ public void should_call_authentication_middleware() _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -172,8 +172,8 @@ public void should_call_pre_error_middleware() _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -208,17 +208,17 @@ public void should_call_pre_error_middleware() } [Fact] - public void should_call_pre_authorisation_middleware() + public void should_call_pre_authorization_middleware() { var configuration = new OcelotPipelineConfiguration { - PreAuthorisationMiddleware = async (ctx, next) => + PreAuthorizationMiddleware = async (ctx, next) => { _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -262,8 +262,8 @@ public void should_call_pre_http_authentication_middleware() _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -295,8 +295,8 @@ public void should_call_pre_http_authentication_middleware() .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => x.ThenTheCounterIs(1)) .BDDfy(); - } - + } + [Fact(Skip = "This is just an example to show how you could hook into Ocelot pipeline with your own middleware. At the moment you must use Response.OnCompleted callback and cannot change the response :( I will see if this can be changed one day!")] public void should_fix_issue_237() { @@ -305,14 +305,14 @@ public void should_fix_issue_237() var httpContext = (HttpContext)state; if (httpContext.Response.StatusCode > 400) - { + { Debug.WriteLine("COUNT CALLED"); Console.WriteLine("COUNT CALLED"); } return Task.CompletedTask; - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -376,8 +376,8 @@ public void Dispose() public class FakeMiddleware { private readonly RequestDelegate _next; - private readonly Func _callback; - + private readonly Func _callback; + public FakeMiddleware(RequestDelegate next, Func callback) { _next = next; @@ -386,10 +386,10 @@ public FakeMiddleware(RequestDelegate next, Func callback) public async Task Invoke(HttpContext context) { - await _next(context); - + await _next(context); + context.Response.OnCompleted(_callback, context); } } } -} +} diff --git a/test/Ocelot.AcceptanceTests/EurekaServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/EurekaServiceDiscoveryTests.cs index dedb32bd3..5f984a272 100644 --- a/test/Ocelot.AcceptanceTests/EurekaServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/EurekaServiceDiscoveryTests.cs @@ -1,6 +1,6 @@ namespace Ocelot.AcceptanceTests { - using Configuration.File; + using Ocelot.Configuration.File; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Steeltoe.Common.Discovery; @@ -38,24 +38,24 @@ public void should_use_eureka_service_discovery_and_make_request() var configuration = new FileConfiguration { Routes = new List + { + new FileRoute { - new FileRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - } + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = serviceName, + LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, }, + }, GlobalConfiguration = new FileGlobalConfiguration() { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { - Type = "Eureka" - } - } + Type = "Eureka", + }, + }, }; this.Given(x => x.GivenEurekaProductServiceOneIsRunning(downstreamServiceOneUrl)) @@ -91,42 +91,42 @@ private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string { name = serviceName, instance = new List + { + new Instance + { + instanceId = $"{serviceInstance.Host}:{serviceInstance}", + hostName = serviceInstance.Host, + app = serviceName, + ipAddr = "127.0.0.1", + status = "UP", + overriddenstatus = "UNKNOWN", + port = new Port {value = serviceInstance.Port, enabled = "true"}, + securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"}, + countryId = 1, + dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"}, + leaseInfo = new LeaseInfo + { + renewalIntervalInSecs = 30, + durationInSecs = 90, + registrationTimestamp = 1457714988223, + lastRenewalTimestamp= 1457716158319, + evictionTimestamp = 0, + serviceUpTimestamp = 1457714988223, + }, + metadata = new Metadata { - new Instance - { - instanceId = $"{serviceInstance.Host}:{serviceInstance}", - hostName = serviceInstance.Host, - app = serviceName, - ipAddr = "127.0.0.1", - status = "UP", - overriddenstatus = "UNKNOWN", - port = new Port {value = serviceInstance.Port, enabled = "true"}, - securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"}, - countryId = 1, - dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"}, - leaseInfo = new LeaseInfo - { - renewalIntervalInSecs = 30, - durationInSecs = 90, - registrationTimestamp = 1457714988223, - lastRenewalTimestamp= 1457716158319, - evictionTimestamp = 0, - serviceUpTimestamp = 1457714988223 - }, - metadata = new Metadata - { - value = "java.util.Collections$EmptyMap" - }, - homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - vipAddress = serviceName, - isCoordinatingDiscoveryServer = "false", - lastUpdatedTimestamp = "1457714988223", - lastDirtyTimestamp = "1457714988172", - actionType = "ADDED" - } - } + value = "java.util.Collections$EmptyMap", + }, + homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + vipAddress = serviceName, + isCoordinatingDiscoveryServer = "false", + lastUpdatedTimestamp = "1457714988223", + lastDirtyTimestamp = "1457714988172", + actionType = "ADDED", + }, + }, }; apps.Add(a); @@ -138,8 +138,8 @@ private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string { application = apps, apps__hashcode = "UP_1_", - versions__delta = "1" - } + versions__delta = "1", + }, }; var json = JsonConvert.SerializeObject(applications); diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index f5d69d138..728d455ec 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -24,8 +24,8 @@ public HeaderTests() [Fact] public void should_transform_upstream_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -65,8 +65,8 @@ public void should_transform_upstream_header() [Fact] public void should_transform_downstream_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -105,8 +105,8 @@ public void should_transform_downstream_header() [Fact] public void should_fix_issue_190() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -149,8 +149,8 @@ public void should_fix_issue_190() [Fact] public void should_fix_issue_205() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -193,8 +193,8 @@ public void should_fix_issue_205() [Fact] public void should_fix_issue_417() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -241,8 +241,8 @@ public void should_fix_issue_417() [Fact] public void request_should_reuse_cookies_with_cookie_container() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -284,8 +284,8 @@ public void request_should_reuse_cookies_with_cookie_container() [Fact] public void request_should_have_own_cookies_no_cookie_container() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -327,8 +327,8 @@ public void request_should_have_own_cookies_no_cookie_container() [Fact] public void issue_474_should_not_put_spaces_in_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -364,8 +364,8 @@ public void issue_474_should_not_put_spaces_in_header() [Fact] public void issue_474_should_put_spaces_in_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -413,9 +413,18 @@ private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int return Task.CompletedTask; } - if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + if (context.Request.Cookies.TryGetValue("test", out var cookieValue)) + { + if (cookieValue == "0") + { + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + } + } + + if (context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) { - if (cookieValue == "0" || headerValue == "test=1; path=/") + if (headerValue == "test=1; path=/") { context.Response.StatusCode = statusCode; return Task.CompletedTask; diff --git a/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs b/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs index 129935d48..ab9f7dffe 100644 --- a/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs @@ -62,7 +62,7 @@ public void should_cache_one_http_client_same_re_route() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => cache.Count.ShouldBe(1)) + .And(x => ThenTheCountShouldBe(cache, 1)) .BDDfy(); } @@ -122,10 +122,15 @@ public void should_cache_two_http_client_different_re_route() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => cache.Count.ShouldBe(2)) + .And(x => ThenTheCountShouldBe(cache, 2)) .BDDfy(); } + private void ThenTheCountShouldBe(FakeHttpClientCache cache, int count) + { + cache.Count.ShouldBe(count); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, int statusCode, string responseBody) { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 4ddf06a3e..e09db82d3 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,77 +1,76 @@ - - - 0.0.0-dev - netcoreapp3.1 - Ocelot.AcceptanceTests - Exe - Ocelot.AcceptanceTests - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - ..\..\codeanalysis.ruleset - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + 0.0.0-dev + net5.0 + Ocelot.AcceptanceTests + Exe + Ocelot.AcceptanceTests + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + ..\..\codeanalysis.ruleset + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Ocelot.AcceptanceTests/OpenTracingTests.cs b/test/Ocelot.AcceptanceTests/OpenTracingTests.cs index d93f9c1b4..8adf54e81 100644 --- a/test/Ocelot.AcceptanceTests/OpenTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/OpenTracingTests.cs @@ -8,7 +8,6 @@ namespace Ocelot.AcceptanceTests using OpenTracing; using OpenTracing.Propagation; using OpenTracing.Tag; - using Rafty.Infrastructure; using Shouldly; using System; using System.Collections.Generic; @@ -17,6 +16,8 @@ namespace Ocelot.AcceptanceTests using TestStack.BDDfy; using Xunit; using Xunit.Abstractions; + using System.Diagnostics; + using System.Threading.Tasks; public class OpenTracingTests : IDisposable { @@ -513,4 +514,70 @@ public IEnumerable> GetBaggageItems() throw new NotImplementedException(); } } + + public class Wait + { + public static Waiter WaitFor(int milliSeconds) + { + return new Waiter(milliSeconds); + } + } + + public class Waiter + { + private readonly int _milliSeconds; + + public Waiter(int milliSeconds) + { + _milliSeconds = milliSeconds; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + + public async Task Until(Func> condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (await condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 329c410ed..f56e3d281 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -322,7 +322,7 @@ public void should_use_token_to_make_request_to_consul() .When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home")) .Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(_ => _receivedToken.ShouldBe(token)) + .And(_ => ThenTheTokenIs(token)) .BDDfy(); } @@ -462,6 +462,11 @@ public void should_handle_request_to_poll_consul_for_downstream_service_and_make .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); + } + + private void ThenTheTokenIs(string token) + { + _receivedToken.ShouldBe(token); } private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 453303117..233be3c1a 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -814,7 +814,7 @@ public void GivenIHaveAToken(string url) new KeyValuePair("scope", "api"), new KeyValuePair("username", "test"), new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") + new KeyValuePair("grant_type", "password"), }; var content = new FormUrlEncodedContent(formData); @@ -837,7 +837,7 @@ public void GivenIHaveATokenForApiReadOnlyScope(string url) new KeyValuePair("scope", "api.readOnly"), new KeyValuePair("username", "test"), new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") + new KeyValuePair("grant_type", "password"), }; var content = new FormUrlEncodedContent(formData); diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index 048484b6a..80e9b5f09 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -57,7 +57,7 @@ public void should_proxy_websocket_input_to_downstream_service() .And(_ => _steps.StartFakeOcelotWithWebSockets()) .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) .When(_ => StartClient("ws://localhost:5000/")) - .Then(_ => _firstRecieved.Count.ShouldBe(10)) + .Then(_ => ThenTheReceivedCountIs(10)) .BDDfy(); } @@ -323,8 +323,12 @@ private async Task Message(WebSocket webSocket) { Console.WriteLine(e); } + } + + private void ThenTheReceivedCountIs(int count) + { + _firstRecieved.Count.ShouldBe(count); } - public void Dispose() { _serviceHandler?.Dispose(); diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index 0cbe43071..99d9b0c60 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -2,7 +2,7 @@ 0.0.0-dev - netcoreapp3.1 + net5.0 Ocelot.Benchmarks Exe Ocelot.Benchmarks @@ -18,7 +18,7 @@ - + all @@ -27,4 +27,4 @@ - \ No newline at end of file + diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index d45bac237..8b39c9cb4 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -1,912 +1,917 @@ -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; -using Ocelot.Administration; -using Ocelot.Cache; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using TestStack.BDDfy; -using Ocelot.Configuration.ChangeTracking; -using Xunit; - -namespace Ocelot.IntegrationTests -{ - public class AdministrationTests : IDisposable - { - private HttpClient _httpClient; - private readonly HttpClient _httpClientTwo; - private HttpResponseMessage _response; - private IHost _builder; - private IHostBuilder _webHostBuilder; - private string _ocelotBaseUrl; - private BearerToken _token; - private IHostBuilder _webHostBuilderTwo; - private IHost _builderTwo; - private IHost _identityServerBuilder; - private IHost _fooServiceBuilder; - private IHost _barServiceBuilder; - - public AdministrationTests() - { - _httpClient = new HttpClient(); - _httpClientTwo = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_response_401_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config() - { - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5011"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - BaseUrl = _ocelotBaseUrl - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl)) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenAnotherOcelotIsRunning("http://localhost:5017")) - .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "RequestId", - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Scheme = "https", - Host = "127.0.0.1", - } - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Geoff" - } - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Dave" - } - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" - } - }, - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration)) - .BDDfy(); - } - - [Fact] - public void should_activate_change_token_when_configuration_is_updated() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration(), - Routes = new List - { - new FileRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - }, - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - }, - }, - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", configuration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => TheChangeTokenShouldBeActive()) - .And(x => ThenTheResponseShouldBe(configuration)) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .And(x => ThenTheResponseShouldBe(configuration)) - .And(_ => ThenTheConfigurationIsSavedCorrectly(configuration)) - .BDDfy(); - } - - private void TheChangeTokenShouldBeActive() - { - _builder.Services.GetRequiredService().ChangeToken.HasChanged.ShouldBeTrue(); - } - - private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected) - { - var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json"; - var resultText = File.ReadAllText(ocelotJsonPath); - var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); - resultText.ShouldBe(expectedText); - - var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json"; - resultText = File.ReadAllText(environmentSpecificPath); - expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); - resultText.ShouldBe(expectedText); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version_redirecting_route() - { - var fooPort = 47689; - var barPort = 27654; - - var initialConfiguration = new FileConfiguration - { - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = fooPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/foo", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = barPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/bar", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}")) - .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}")) - .And(x => GivenOcelotIsRunning()) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("bar")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(initialConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .BDDfy(); - } - - [Fact] - public void should_clear_region() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - } - } - }; - - var regionToClear = "gettest"; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area() - { - var configuration = new FileConfiguration(); - - var identityServerRootUrl = "http://localhost:5123"; - - Action options = o => - { - o.Authority = identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api")) - .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options)) - .And(x => GivenIHaveAToken(identityServerRootUrl)) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - private void GivenIHaveAToken(string url) - { - var formData = new List> - { - new KeyValuePair("client_id", "api"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync($"{url}/connect/token", content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName) - { - _identityServerBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = apiName, - Enabled = true, - DisplayName = apiName, - Scopes = new List() - { - new Scope(apiName), - }, - }, - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = apiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List { new Secret("secret".Sha256()) }, - AllowedScopes = new List { apiName }, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true - }, - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "1231231" - }, - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - } - ); - }).Build(); - - _identityServerBuilder.Start(); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; - response.EnsureSuccessStatusCode(); - } - } - - private void GivenAnotherOcelotIsRunning(string baseUrl) - { - _httpClientTwo.BaseAddress = new Uri(baseUrl); - - _webHostBuilderTwo = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddMvc(option => option.EnableEndpointRouting = false); - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - }); - - _builderTwo = _webHostBuilderTwo.Build(); - - _builderTwo.Start(); - } - - private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); - } - - private void WhenIGetUrlOnTheSecondOcelot(string url) - { - _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - _response = _httpClientTwo.GetAsync(url).Result; - } - - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(List expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - var result = JsonConvert.DeserializeObject(content); - result.Value.ShouldBe(expected); - } - - private void ThenTheResponseBodyShouldBe(string expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - content.ShouldBe(expected); - } - - private void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Scheme.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Scheme); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.Routes.Count; i++) - { - for (var j = 0; j < response.Routes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.Routes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.Routes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.Routes[i].DownstreamPathTemplate.ShouldBe(expecteds.Routes[i].DownstreamPathTemplate); - response.Routes[i].DownstreamScheme.ShouldBe(expecteds.Routes[i].DownstreamScheme); - response.Routes[i].UpstreamPathTemplate.ShouldBe(expecteds.Routes[i].UpstreamPathTemplate); - response.Routes[i].UpstreamHttpMethod.ShouldBe(expecteds.Routes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = _httpClient.GetAsync(configPath).Result; - response.EnsureSuccessStatusCode(); - } - - private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions) - { - _webHostBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddMvc(option => option.EnableEndpointRouting = false); - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddAdministration("/administration", configOptions); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddMvc(s => s.EnableEndpointRouting = false); - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) - { - _webHostBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddMvc(option => option.EnableEndpointRouting = false); - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _httpClient.GetAsync(url).Result; - } - - private void WhenIDeleteOnTheApiGateway(string url) - { - _response = _httpClient.DeleteAsync(url).Result; - } - - private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); - _builder?.Dispose(); - _httpClient?.Dispose(); - _identityServerBuilder?.Dispose(); - } - - private void GivenThereIsAFooServiceRunningOn(string baseUrl) - { - _fooServiceBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/foo"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("foo"); - }); - }); - }).Build(); - - _fooServiceBuilder.Start(); - } - - private void GivenThereIsABarServiceRunningOn(string baseUrl) - { - _barServiceBuilder = Host.CreateDefaultBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/bar"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("bar"); - }); - }); - }).Build(); - - _barServiceBuilder.Start(); - } - } -} +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using IdentityServer4.Test; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using Ocelot.Administration; +using Ocelot.Cache; +using Ocelot.Configuration.File; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using TestStack.BDDfy; +using Ocelot.Configuration.ChangeTracking; +using Xunit; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +namespace Ocelot.IntegrationTests +{ + public class AdministrationTests : IDisposable + { + private HttpClient _httpClient; + private readonly HttpClient _httpClientTwo; + private HttpResponseMessage _response; + private IHost _builder; + private IHostBuilder _webHostBuilder; + private string _ocelotBaseUrl; + private BearerToken _token; + private IHostBuilder _webHostBuilderTwo; + private IHost _builderTwo; + private IHost _identityServerBuilder; + private IHost _fooServiceBuilder; + private IHost _barServiceBuilder; + + public AdministrationTests() + { + _httpClient = new HttpClient(); + _httpClientTwo = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_response_401_with_call_re_routes_controller() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + //this seems to be be answer https://github.com/IdentityServer/IdentityServer4/issues/4914 + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config() + { + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5011"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + BaseUrl = _ocelotBaseUrl, + }, + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl)) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() + { + var configuration = new FileConfiguration(); + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenAnotherOcelotIsRunning("http://localhost:5017")) + .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "RequestId", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Scheme = "https", + Host = "127.0.0.1", + }, + }, + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Geoff", + }, + }, + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + Region = "Dave", + }, + }, + }, + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + + [Fact] + public void should_get_file_configuration_edit_and_post_updated_version() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + }, + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + }, + }, + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + }, + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.123.123", + Port = 443, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = new List { "post" }, + UpstreamPathTemplate = "/test", + }, + }, + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration)) + .BDDfy(); + } + + [Fact] + public void should_activate_change_token_when_configuration_is_updated() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration(), + Routes = new List + { + new FileRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + }, + }, + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", configuration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => TheChangeTokenShouldBeActive()) + .And(x => ThenTheResponseShouldBe(configuration)) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .And(x => ThenTheResponseShouldBe(configuration)) + .And(_ => ThenTheConfigurationIsSavedCorrectly(configuration)) + .BDDfy(); + } + + private void TheChangeTokenShouldBeActive() + { + _builder.Services.GetRequiredService().ChangeToken.HasChanged.ShouldBeTrue(); + } + + private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected) + { + var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json"; + var resultText = File.ReadAllText(ocelotJsonPath); + var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + resultText.ShouldBe(expectedText); + + var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json"; + resultText = File.ReadAllText(environmentSpecificPath); + expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + + [Fact] + public void should_get_file_configuration_edit_and_post_updated_version_redirecting_route() + { + var fooPort = 47689; + var barPort = 27654; + + var initialConfiguration = new FileConfiguration + { + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = fooPort, + }, + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/foo", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/foo", + }, + }, + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = barPort, + }, + }, + DownstreamScheme = "http", + DownstreamPathTemplate = "/bar", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/foo", + }, + }, + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}")) + .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}")) + .And(x => GivenOcelotIsRunning()) + .And(x => WhenIGetUrlOnTheApiGateway("/foo")) + .Then(x => ThenTheResponseBodyShouldBe("foo")) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .And(x => WhenIGetUrlOnTheApiGateway("/foo")) + .Then(x => ThenTheResponseBodyShouldBe("bar")) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(initialConfiguration)) + .And(x => WhenIGetUrlOnTheApiGateway("/foo")) + .Then(x => ThenTheResponseBodyShouldBe("foo")) + .BDDfy(); + } + + [Fact] + public void should_clear_region() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + }, + Routes = new List() + { + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + }, + }, + new FileRoute() + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + }, + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "get" }, + UpstreamPathTemplate = "/test", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 10, + }, + }, + }, + }; + + var regionToClear = "gettest"; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area() + { + var configuration = new FileConfiguration(); + + var identityServerRootUrl = "http://localhost:5123"; + + Action options = o => + { + o.Authority = identityServerRootUrl; + o.RequireHttpsMetadata = false; + o.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + }; + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api")) + .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options)) + .And(x => GivenIHaveAToken(identityServerRootUrl)) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenIHaveAToken(string url) + { + var formData = new List> + { + new KeyValuePair("client_id", "api"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password"), + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync($"{url}/connect/token", content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + private void GivenThereIsAnIdentityServerOn(string url, string apiName) + { + _identityServerBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(services => + { + services.AddLogging(); + services.AddIdentityServer() + .AddDeveloperSigningCredential() + .AddInMemoryApiScopes(new List { new ApiScope(apiName) }) + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = apiName, + Description = apiName, + Enabled = true, + DisplayName = apiName, + Scopes = new List() + { + apiName, + }, + }, + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = apiName, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List { new Secret("secret".Sha256()) }, + AllowedScopes = new List { apiName }, + AccessTokenType = AccessTokenType.Jwt, + Enabled = true, + }, + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "test", + Password = "test", + SubjectId = "1231231", + }, + }); + }) + .Configure(app => + { + app.UseIdentityServer(); + } + ); + }).Build(); + + _identityServerBuilder.Start(); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; + response.EnsureSuccessStatusCode(); + } + } + + private void GivenAnotherOcelotIsRunning(string baseUrl) + { + _httpClientTwo.BaseAddress = new Uri(baseUrl); + + _webHostBuilderTwo = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddOcelot() + .AddAdministration("/administration", "secret"); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); + + _builderTwo = _webHostBuilderTwo.Build(); + + _builderTwo.Start(); + } + + private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() + { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); + } + + private void WhenIGetUrlOnTheSecondOcelot(string url) + { + _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + _response = _httpClientTwo.GetAsync(url).Result; + } + + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void ThenTheResponseShouldBe(List expected) + { + var content = _response.Content.ReadAsStringAsync().Result; + var result = JsonConvert.DeserializeObject(content); + result.Value.ShouldBe(expected); + } + + private void ThenTheResponseBodyShouldBe(string expected) + { + var content = _response.Content.ReadAsStringAsync().Result; + content.ShouldBe(expected); + } + + private void ThenTheResponseShouldBe(FileConfiguration expecteds) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Scheme.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Scheme); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.Routes.Count; i++) + { + for (var j = 0; j < response.Routes[i].DownstreamHostAndPorts.Count; j++) + { + var result = response.Routes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.Routes[i].DownstreamHostAndPorts[j]; + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + response.Routes[i].DownstreamPathTemplate.ShouldBe(expecteds.Routes[i].DownstreamPathTemplate); + response.Routes[i].DownstreamScheme.ShouldBe(expecteds.Routes[i].DownstreamScheme); + response.Routes[i].UpstreamPathTemplate.ShouldBe(expecteds.Routes[i].UpstreamPathTemplate); + response.Routes[i].UpstreamHttpMethod.ShouldBe(expecteds.Routes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("grant_type", "client_credentials"), + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + var configPath = $"{adminPath}/.well-known/openid-configuration"; + response = _httpClient.GetAsync(configPath).Result; + response.EnsureSuccessStatusCode(); + } + + private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions) + { + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddSingleton(_webHostBuilder); + x.AddOcelot() + .AddAdministration("/administration", configOptions); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(s => s.EnableEndpointRouting = false); + x.AddOcelot() + .AddAdministration("/administration", "secret"); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) + { + _webHostBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddMvc(option => option.EnableEndpointRouting = false); + x.AddSingleton(_webHostBuilder); + x.AddOcelot() + .AddAdministration("/administration", "secret"); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _httpClient.GetAsync(url).Result; + } + + private void WhenIDeleteOnTheApiGateway(string url) + { + _response = _httpClient.DeleteAsync(url).Result; + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); + Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); + _builder?.Dispose(); + _httpClient?.Dispose(); + _identityServerBuilder?.Dispose(); + } + + private void GivenThereIsAFooServiceRunningOn(string baseUrl) + { + _fooServiceBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase("/foo"); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("foo"); + }); + }); + }).Build(); + + _fooServiceBuilder.Start(); + } + + private void GivenThereIsABarServiceRunningOn(string baseUrl) + { + _barServiceBuilder = Host.CreateDefaultBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase("/bar"); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("bar"); + }); + }); + }).Build(); + + _barServiceBuilder.Start(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 59b45dc49..53a899c19 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -1,61 +1,60 @@ - - - 0.0.0-dev - netcoreapp3.1 - Ocelot.IntegrationTests - Exe - Ocelot.IntegrationTests - true - win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 - false - false - false - ..\..\codeanalysis.ruleset - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + 0.0.0-dev + net5.0 + Ocelot.IntegrationTests + Exe + Ocelot.IntegrationTests + true + win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 + false + false + false + ..\..\codeanalysis.ruleset + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs deleted file mode 100644 index 359aa6f5e..000000000 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ /dev/null @@ -1,513 +0,0 @@ -namespace Ocelot.IntegrationTests -{ - using Administration; - using Configuration.File; - using DependencyInjection; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Data.Sqlite; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Middleware; - using Newtonsoft.Json; - using Ocelot.Provider.Rafty; - using Rafty.Infrastructure; - using Shouldly; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading; - using System.Threading.Tasks; - using Xunit; - using Xunit.Abstractions; - using Wait = Rafty.Infrastructure.Wait; - - public class RaftTests : IDisposable - { - private readonly List _builders; - private readonly List _webHostBuilders; - private readonly List _threads; - private FilePeers _peers; - private HttpClient _httpClient; - private readonly HttpClient _httpClientForAssertions; - private BearerToken _token; - private HttpResponseMessage _response; - private static readonly object _lock = new object(); - private ITestOutputHelper _output; - - public RaftTests(ITestOutputHelper output) - { - _output = output; - _httpClientForAssertions = new HttpClient(); - _webHostBuilders = new List(); - _builders = new List(); - _threads = new List(); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5000"}, - - new FilePeer {HostAndPort = "http://localhost:5001"}, - - new FilePeer {HostAndPort = "http://localhost:5002"}, - - new FilePeer {HostAndPort = "http://localhost:5003"}, - - new FilePeer {HostAndPort = "http://localhost:5004"} - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - await WhenISendACommandIntoTheCluster(command); - Thread.Sleep(5000); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers_when_using_administration_api() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5005"}, - - new FilePeer {HostAndPort = "http://localhost:5006"}, - - new FilePeer {HostAndPort = "http://localhost:5007"}, - - new FilePeer {HostAndPort = "http://localhost:5008"}, - - new FilePeer {HostAndPort = "http://localhost:5009"} - }; - - var configuration = new FileConfiguration - { - }; - - var updatedConfiguration = new FileConfiguration - { - Routes = new List() - { - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - GivenIHaveAddedATokenToMyRequest(); - await WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - private void GivenThePeersAre(List peers) - { - FilePeers filePeers = new FilePeers(); - filePeers.Peers.AddRange(peers); - var json = JsonConvert.SerializeObject(filePeers); - File.WriteAllText("peers.json", json); - _httpClient = new HttpClient(); - var ocelotBaseUrl = peers[0].HostAndPort; - _httpClient.BaseAddress = new Uri(ocelotBaseUrl); - } - - private async Task WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) - { - async Task SendCommand() - { - try - { - var p = _peers.Peers.First(); - var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }); - var httpContent = new StringContent(json); - httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - using (var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var response = await httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent); - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - - var errorResult = JsonConvert.DeserializeObject>(content); - - if (!string.IsNullOrEmpty(errorResult.Error)) - { - return false; - } - - var okResult = JsonConvert.DeserializeObject>(content); - - if (okResult.Command.Configuration.Routes.Count == 2) - { - return true; - } - } - - return false; - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - } - - var commandSent = await Wait.WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private async Task ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds) - { - async Task CommandCalledOnAllStateMachines() - { - try - { - var passed = 0; - foreach (var peer in _peers.Peers) - { - var path = $"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"; - using (var connection = new SqliteConnection($"Data Source={path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using (var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - index.ShouldBe(1); - } - } - - _httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var result = await _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration"); - var json = await result.Content.ReadAsStringAsync(); - var response = JsonConvert.DeserializeObject(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.Routes.Count; i++) - { - for (var j = 0; j < response.Routes[i].DownstreamHostAndPorts.Count; j++) - { - var res = response.Routes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.Configuration.Routes[i].DownstreamHostAndPorts[j]; - res.Host.ShouldBe(expected.Host); - res.Port.ShouldBe(expected.Port); - } - - response.Routes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.Routes[i].DownstreamPathTemplate); - response.Routes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.Routes[i].DownstreamScheme); - response.Routes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.Routes[i].UpstreamPathTemplate); - response.Routes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.Routes[i].UpstreamHttpMethod); - } - - passed++; - } - - return passed == 5; - } - catch (Exception e) - { - //_output.WriteLine($"{e.Message}, {e.StackTrace}"); - Console.WriteLine(e); - return false; - } - } - - var commandOnAllStateMachines = await Wait.WaitFor(40000).Until(async () => - { - var result = await CommandCalledOnAllStateMachines(); - Thread.Sleep(1000); - return result; - }); - - commandOnAllStateMachines.ShouldBeTrue(); - } - - private async Task WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - async Task SendCommand() - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - - var content = new StringContent(json); - - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - _response = await _httpClient.PostAsync(url, content); - - var responseContent = await _response.Content.ReadAsStringAsync(); - - if (responseContent == "There was a problem. This error message sucks raise an issue in GitHub.") - { - return false; - } - - if (string.IsNullOrEmpty(responseContent)) - { - return false; - } - - return _response.IsSuccessStatusCode; - } - - var commandSent = await Wait.WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private async Task GivenIHaveAnOcelotToken(string adminPath) - { - async Task AddToken() - { - try - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = await _httpClient.PostAsync(tokenUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - if (!response.IsSuccessStatusCode) - { - return false; - } - - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = await _httpClient.GetAsync(configPath); - return response.IsSuccessStatusCode; - } - catch (Exception) - { - return false; - } - } - - var addToken = await Wait.WaitFor(40000).Until(async () => - { - var result = await AddToken(); - Thread.Sleep(1000); - return result; - }); - - addToken.ShouldBeTrue(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void GivenAServerIsRunning(string url) - { - lock (_lock) - { - IWebHostBuilder webHostBuilder = new WebHostBuilder(); - webHostBuilder.UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddJsonFile("peers.json", optional: true, reloadOnChange: false); -#pragma warning disable CS0618 - config.AddOcelotBaseUrl(url); -#pragma warning restore CS0618 - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddSingleton(new NodeId(url)); - x - .AddOcelot() - .AddAdministration("/administration", "secret") - .AddRafty(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - var builder = webHostBuilder.Build(); - builder.Start(); - - _webHostBuilders.Add(webHostBuilder); - _builders.Add(builder); - } - } - - private void GivenFiveServersAreRunning() - { - var bytes = File.ReadAllText("peers.json"); - _peers = JsonConvert.DeserializeObject(bytes); - - foreach (var peer in _peers.Peers) - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); - thread.Start(); - _threads.Add(thread); - } - } - - public void Dispose() - { - foreach (var builder in _builders) - { - builder?.Dispose(); - } - - foreach (var peer in _peers.Peers) - { - try - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } - } -} diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 40e36ebcc..3eae2086b 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -1,7 +1,7 @@  0.0.0-dev - netcoreapp3.1 + net5.0 true Ocelot.ManualTest Exe @@ -28,13 +28,13 @@ - - - - - - - + + + + + + + all @@ -42,4 +42,4 @@ - \ No newline at end of file + diff --git a/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs b/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs index e19025d1d..c4f675cff 100644 --- a/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs +++ b/test/Ocelot.UnitTests/Administration/OcelotAdministrationBuilderTests.cs @@ -1,101 +1,102 @@ -namespace Ocelot.UnitTests.Administration -{ +namespace Ocelot.UnitTests.Administration +{ using IdentityServer4.AccessTokenValidation; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Administration; - using Ocelot.DependencyInjection; - using Shouldly; - using System; - using System.Collections.Generic; - using System.Reflection; - using TestStack.BDDfy; - using Xunit; - - public class OcelotAdministrationBuilderTests - { - private readonly IServiceCollection _services; - private IServiceProvider _serviceProvider; - private readonly IConfiguration _configRoot; - private IOcelotBuilder _ocelotBuilder; - private Exception _ex; - - public OcelotAdministrationBuilderTests() - { - _configRoot = new ConfigurationRoot(new List()); - _services = new ServiceCollection(); - _services.AddSingleton(GetHostingEnvironment()); - _services.AddSingleton(_configRoot); - } - - private IWebHostEnvironment GetHostingEnvironment() - { - var environment = new Mock(); - environment - .Setup(e => e.ApplicationName) - .Returns(typeof(OcelotAdministrationBuilderTests).GetTypeInfo().Assembly.GetName().Name); - - return environment.Object; - } - - //keep - [Fact] - public void should_set_up_administration_with_identity_server_options() - { - Action options = o => { }; - - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration(options)) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - //keep - [Fact] - public void should_set_up_administration() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - private void WhenISetUpAdministration() - { - _ocelotBuilder.AddAdministration("/administration", "secret"); - } - - private void WhenISetUpAdministration(Action options) - { - _ocelotBuilder.AddAdministration("/administration", options); - } - - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - - private void WhenISetUpOcelotServices() - { - try - { - _ocelotBuilder = _services.AddOcelot(_configRoot); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnExceptionIsntThrown() - { - _ex.ShouldBeNull(); - } - } -} + using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Administration; + using Ocelot.DependencyInjection; + using Shouldly; + using System; + using System.Collections.Generic; + using System.Reflection; + using TestStack.BDDfy; + using Xunit; + + public class OcelotAdministrationBuilderTests + { + private readonly IServiceCollection _services; + private IServiceProvider _serviceProvider; + private readonly IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private Exception _ex; + + public OcelotAdministrationBuilderTests() + { + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(GetHostingEnvironment()); + _services.AddSingleton(_configRoot); + } + + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotAdministrationBuilderTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + + //keep + [Fact] + public void should_set_up_administration_with_identity_server_options() + { + Action options = o => { }; + + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpAdministration(options)) + .Then(x => ThenAnExceptionIsntThrown()) + .Then(x => ThenTheCorrectAdminPathIsRegitered()) + .BDDfy(); + } + + //keep + [Fact] + public void should_set_up_administration() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpAdministration()) + .Then(x => ThenAnExceptionIsntThrown()) + .Then(x => ThenTheCorrectAdminPathIsRegitered()) + .BDDfy(); + } + + private void WhenISetUpAdministration() + { + _ocelotBuilder.AddAdministration("/administration", "secret"); + } + + private void WhenISetUpAdministration(Action options) + { + _ocelotBuilder.AddAdministration("/administration", options); + } + + private void ThenTheCorrectAdminPathIsRegitered() + { + _serviceProvider = _services.BuildServiceProvider(); + var path = _serviceProvider.GetService(); + path.Path.ShouldBe("/administration"); + } + + private void WhenISetUpOcelotServices() + { + try + { + _ocelotBuilder = _services.AddOcelot(_configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs similarity index 78% rename from test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs rename to test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs index 4c72849e7..f1b14b0ad 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs @@ -3,8 +3,8 @@ namespace Ocelot.UnitTests.Authorization { using Microsoft.AspNetCore.Http; using Moq; - using Ocelot.Authorisation; - using Ocelot.Authorisation.Middleware; + using Ocelot.Authorization; + using Ocelot.Authorization.Middleware; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder.Middleware; @@ -18,35 +18,35 @@ namespace Ocelot.UnitTests.Authorization using TestStack.BDDfy; using Xunit; - public class AuthorisationMiddlewareTests + public class AuthorizationMiddlewareTests { - private readonly Mock _authService; - private readonly Mock _authScopesService; + private readonly Mock _authService; + private readonly Mock _authScopesService; private Mock _loggerFactory; private Mock _logger; - private readonly AuthorisationMiddleware _middleware; + private readonly AuthorizationMiddleware _middleware; private RequestDelegate _next; private HttpContext _httpContext; - public AuthorisationMiddlewareTests() + public AuthorizationMiddlewareTests() { _httpContext = new DefaultHttpContext(); - _authService = new Mock(); - _authScopesService = new Mock(); + _authService = new Mock(); + _authScopesService = new Mock(); _loggerFactory = new Mock(); _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _middleware = new AuthorisationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); + _middleware = new AuthorizationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); } [Fact] - public void should_call_authorisation_service() + public void should_call_authorization_service() { this.Given(x => x.GivenTheDownStreamRouteIs(new List(), new DownstreamRouteBuilder() .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().Build()) - .WithIsAuthorised(true) + .WithIsAuthorized(true) .WithUpstreamHttpMethod(new List { "Get" }) .Build())) .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) @@ -69,7 +69,7 @@ private void GivenTheDownStreamRouteIs(List templatePla private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise( + .Setup(x => x.Authorize( It.IsAny(), It.IsAny>(), It.IsAny>())) @@ -79,7 +79,7 @@ private void GivenTheAuthServiceReturns(Response expected) private void ThenTheAuthServiceIsCalledCorrectly() { _authService - .Verify(x => x.Authorise( + .Verify(x => x.Authorize( It.IsAny(), It.IsAny>(), It.IsAny>()) diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthorizerTests.cs similarity index 75% rename from test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs rename to test/Ocelot.UnitTests/Authorization/ClaimsAuthorizerTests.cs index b22e7ffce..34ed4afa5 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthorizerTests.cs @@ -1,4 +1,4 @@ -using Ocelot.Authorisation; +using Ocelot.Authorization; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; using Shouldly; @@ -11,21 +11,21 @@ namespace Ocelot.UnitTests.Authorization { using Ocelot.Infrastructure.Claims.Parser; - public class ClaimsAuthoriserTests + public class ClaimsAuthorizerTests { - private readonly ClaimsAuthoriser _claimsAuthoriser; + private readonly ClaimsAuthorizer _claimsAuthorizer; private ClaimsPrincipal _claimsPrincipal; private Dictionary _requirement; private List _urlPathPlaceholderNameAndValues; private Response _result; - public ClaimsAuthoriserTests() + public ClaimsAuthorizerTests() { - _claimsAuthoriser = new ClaimsAuthoriser(new ClaimsParser()); + _claimsAuthorizer = new ClaimsAuthorizer(new ClaimsParser()); } [Fact] - public void should_authorise_user() + public void should_authorize_user() { this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List { @@ -35,8 +35,8 @@ public void should_authorise_user() { {"UserType", "registered"} })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsAuthorized()) .BDDfy(); } @@ -55,8 +55,8 @@ public void should_authorize_dynamic_user() { new PlaceholderNameAndValue("{userId}", "14") })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsAuthorized()) .BDDfy(); } @@ -75,13 +75,13 @@ public void should_not_authorize_dynamic_user() { new PlaceholderNameAndValue("{userId}", "14") })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsntAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsntAuthorized()) .BDDfy(); - } + } [Fact] - public void should_authorise_user_multiple_claims_of_same_type() + public void should_authorize_user_multiple_claims_of_same_type() { this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List { @@ -92,21 +92,21 @@ public void should_authorise_user_multiple_claims_of_same_type() { {"UserType", "registered"} })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsAuthorized()) .BDDfy(); } [Fact] - public void should_not_authorise_user() + public void should_not_authorize_user() { this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List())))) .And(x => x.GivenARouteClaimsRequirement(new Dictionary { { "UserType", "registered" } })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsntAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsntAuthorized()) .BDDfy(); } @@ -125,19 +125,19 @@ private void GivenAPlaceHolderNameAndValueList(List url _urlPathPlaceholderNameAndValues = urlPathPlaceholderNameAndValues; } - private void WhenICallTheAuthoriser() + private void WhenICallTheAuthorizer() { - _result = _claimsAuthoriser.Authorise(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues); + _result = _claimsAuthorizer.Authorize(_claimsPrincipal, _requirement, _urlPathPlaceholderNameAndValues); } - private void ThenTheUserIsAuthorised() + private void ThenTheUserIsAuthorized() { _result.Data.ShouldBe(true); } - private void ThenTheUserIsntAuthorised() + private void ThenTheUserIsntAuthorized() { _result.Data.ShouldBe(false); } } -} +} diff --git a/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs index c5e0d75ef..c450abf58 100644 --- a/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs @@ -46,7 +46,7 @@ public void should_create_re_route_options() var expected = new RouteOptionsBuilder() .WithIsAuthenticated(true) - .WithIsAuthorised(true) + .WithIsAuthorized(true) .WithIsCached(true) .WithRateLimiting(true) .WithUseServiceDiscovery(true) @@ -71,7 +71,7 @@ private void WhenICreate() private void ThenTheFollowingIsReturned(RouteOptions expected) { _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); - _result.IsAuthorised.ShouldBe(expected.IsAuthorised); + _result.IsAuthorized.ShouldBe(expected.IsAuthorized); _result.IsCached.ShouldBe(expected.IsCached); _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); _result.UseServiceDiscovery.ShouldBe(expected.UseServiceDiscovery); diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index 81500e51f..1e168cff0 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -218,7 +218,7 @@ private void ThenTheRouteIsSet(FileRoute expected, int routeIndex) { _result[routeIndex].DownstreamRoute[0].DownstreamHttpVersion.ShouldBe(_expectedVersion); _result[routeIndex].DownstreamRoute[0].IsAuthenticated.ShouldBe(_rro.IsAuthenticated); - _result[routeIndex].DownstreamRoute[0].IsAuthorised.ShouldBe(_rro.IsAuthorised); + _result[routeIndex].DownstreamRoute[0].IsAuthorized.ShouldBe(_rro.IsAuthorized); _result[routeIndex].DownstreamRoute[0].IsCached.ShouldBe(_rro.IsCached); _result[routeIndex].DownstreamRoute[0].EnableEndpointEndpointRateLimiting.ShouldBe(_rro.EnableRateLimiting); _result[routeIndex].DownstreamRoute[0].RequestIdKey.ShouldBe(_requestId); diff --git a/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs index 8e06f1115..6e46ba768 100644 --- a/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs @@ -87,7 +87,7 @@ public void should_use_token() Address = "localhost", Port = 50881, ID = Guid.NewGuid().ToString(), - Tags = new string[0] + Tags = new string[0], }, }; @@ -95,7 +95,7 @@ public void should_use_token() .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) .When(_ => WhenIGetTheServices()) .Then(_ => ThenTheCountIs(1)) - .And(_ => _receivedToken.ShouldBe(token)) + .And(_ => ThenTheTokenIs(token)) .BDDfy(); } @@ -253,6 +253,11 @@ private void WhenIGetTheServices() _services = _provider.Get().GetAwaiter().GetResult(); } + private void ThenTheTokenIs(string token) + { + _receivedToken.ShouldBe(token); + } + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) { foreach (var serviceEntry in serviceEntries) diff --git a/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs index 5a6a5306d..01e2ee30e 100644 --- a/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Eureka/EurekaMiddlewareConfigurationProviderTests.cs @@ -6,10 +6,10 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Repository; - using Provider.Eureka; - using Responses; + using Ocelot.Provider.Eureka; + using Ocelot.Responses; using Shouldly; - using Steeltoe.Common.Discovery; + using Steeltoe.Discovery; using System.Threading.Tasks; using Xunit; @@ -25,7 +25,7 @@ public void should_not_build() services.AddSingleton(configRepo.Object); var sp = services.BuildServiceProvider(); var provider = EurekaMiddlewareConfigurationProvider.Get(new ApplicationBuilder(sp)); - provider.ShouldBeOfType(); + provider.Status.ShouldBe(TaskStatus.RanToCompletion); } [Fact] @@ -41,7 +41,7 @@ public void should_build() services.AddSingleton(client.Object); var sp = services.BuildServiceProvider(); var provider = EurekaMiddlewareConfigurationProvider.Get(new ApplicationBuilder(sp)); - provider.ShouldBeOfType(); + provider.Status.ShouldBe(TaskStatus.RanToCompletion); } } } diff --git a/test/Ocelot.UnitTests/Eureka/EurekaProviderFactoryTests.cs b/test/Ocelot.UnitTests/Eureka/EurekaProviderFactoryTests.cs index 9e1fd3e2c..157359403 100644 --- a/test/Ocelot.UnitTests/Eureka/EurekaProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Eureka/EurekaProviderFactoryTests.cs @@ -5,7 +5,7 @@ using Ocelot.Configuration.Builder; using Provider.Eureka; using Shouldly; - using Steeltoe.Common.Discovery; + using Steeltoe.Discovery; using Xunit; public class EurekaProviderFactoryTests diff --git a/test/Ocelot.UnitTests/Eureka/EurekaServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Eureka/EurekaServiceDiscoveryProviderTests.cs index ec332605e..2d473ce6f 100644 --- a/test/Ocelot.UnitTests/Eureka/EurekaServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Eureka/EurekaServiceDiscoveryProviderTests.cs @@ -1,117 +1,118 @@ -namespace Ocelot.UnitTests.Eureka -{ - using Moq; - using Provider.Eureka; - using Shouldly; +namespace Ocelot.UnitTests.Eureka +{ + using Moq; + using Ocelot.Provider.Eureka; + using Shouldly; using Steeltoe.Common.Discovery; - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Values; - using Xunit; - - public class EurekaServiceDiscoveryProviderTests - { - private readonly Eureka _provider; - private readonly Mock _client; - private readonly string _serviceId; - private List _instances; - private List _result; - - public EurekaServiceDiscoveryProviderTests() - { - _serviceId = "Laura"; - _client = new Mock(); - _provider = new Eureka(_serviceId, _client.Object); - } - - [Fact] - public void should_return_empty_services() - { - this.When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(0)) - .BDDfy(); - } - - [Fact] - public void should_return_service_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(1)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .And(_ => ThenTheServiceIsMapped()) - .BDDfy(); - } - - [Fact] - public void should_return_services_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()), - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(2)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .BDDfy(); - } - - private void ThenTheServiceIsMapped() - { - _result[0].HostAndPort.DownstreamHost.ShouldBe("somehost"); - _result[0].HostAndPort.DownstreamPort.ShouldBe(801); - _result[0].Name.ShouldBe(_serviceId); - } - - private void ThenTheCountIs(int expected) - { - _result.Count.ShouldBe(expected); - } - - private void ThenTheClientIsCalledCorrectly() - { - _client.Verify(x => x.GetInstances(_serviceId), Times.Once); - } - - private async Task WhenIGet() - { - _result = await _provider.Get(); - } - - private void GivenThe(List instances) - { - _instances = instances; - _client.Setup(x => x.GetInstances(It.IsAny())).Returns(instances); - } - } - - public class EurekaService : IServiceInstance - { - public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) - { - ServiceId = serviceId; - Host = host; - Port = port; - IsSecure = isSecure; - Uri = uri; - Metadata = metadata; - } - - public string ServiceId { get; } - public string Host { get; } - public int Port { get; } - public bool IsSecure { get; } - public Uri Uri { get; } - public IDictionary Metadata { get; } - } -} + using Steeltoe.Discovery; + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Ocelot.Values; + using Xunit; + + public class EurekaServiceDiscoveryProviderTests + { + private readonly Eureka _provider; + private readonly Mock _client; + private readonly string _serviceId; + private List _instances; + private List _result; + + public EurekaServiceDiscoveryProviderTests() + { + _serviceId = "Laura"; + _client = new Mock(); + _provider = new Eureka(_serviceId, _client.Object); + } + + [Fact] + public void should_return_empty_services() + { + this.When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(0)) + .BDDfy(); + } + + [Fact] + public void should_return_service_from_client() + { + var instances = new List + { + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) + }; + + this.Given(_ => GivenThe(instances)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(1)) + .And(_ => ThenTheClientIsCalledCorrectly()) + .And(_ => ThenTheServiceIsMapped()) + .BDDfy(); + } + + [Fact] + public void should_return_services_from_client() + { + var instances = new List + { + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()), + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) + }; + + this.Given(_ => GivenThe(instances)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(2)) + .And(_ => ThenTheClientIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheServiceIsMapped() + { + _result[0].HostAndPort.DownstreamHost.ShouldBe("somehost"); + _result[0].HostAndPort.DownstreamPort.ShouldBe(801); + _result[0].Name.ShouldBe(_serviceId); + } + + private void ThenTheCountIs(int expected) + { + _result.Count.ShouldBe(expected); + } + + private void ThenTheClientIsCalledCorrectly() + { + _client.Verify(x => x.GetInstances(_serviceId), Times.Once); + } + + private async Task WhenIGet() + { + _result = await _provider.Get(); + } + + private void GivenThe(List instances) + { + _instances = instances; + _client.Setup(x => x.GetInstances(It.IsAny())).Returns(instances); + } + } + + public class EurekaService : IServiceInstance + { + public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) + { + ServiceId = serviceId; + Host = host; + Port = port; + IsSecure = isSecure; + Uri = uri; + Metadata = metadata; + } + + public string ServiceId { get; } + public string Host { get; } + public int Port { get; } + public bool IsSecure { get; } + public Uri Uri { get; } + public IDictionary Metadata { get; } + } +} diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs index cc2916a2d..db2d17b68 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs @@ -1,128 +1,129 @@ -namespace Ocelot.UnitTests.Headers -{ +namespace Ocelot.UnitTests.Headers +{ using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration.Creator; - using Ocelot.Headers; - using Ocelot.Infrastructure; - using Ocelot.Infrastructure.Claims.Parser; - using Ocelot.Logging; - using Responder; - using Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class AddHeadersToRequestPlainTests - { - private readonly AddHeadersToRequest _addHeadersToRequest; - private HttpContext _context; - private AddHeader _addedHeader; - private readonly Mock _placeholders; - private Mock _factory; - private readonly Mock _logger; - - public AddHeadersToRequestPlainTests() - { - _placeholders = new Mock(); - _factory = new Mock(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _addHeadersToRequest = new AddHeadersToRequest(Mock.Of(), _placeholders.Object, _factory.Object); - } - - [Fact] - public void should_log_error_if_cannot_find_placeholder() - { - _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); - - this.Given(_ => GivenHttpRequestWithoutHeaders()) - .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) - .Then(_ => ThenAnErrorIsLogged("X-Forwarded-For", "{RemoteIdAddress}")) - .BDDfy(); - } - - [Fact] - public void should_add_placeholder_to_downstream_request() - { - _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse("replaced")); - - this.Given(_ => GivenHttpRequestWithoutHeaders()) - .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) - .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders("replaced")) - .BDDfy(); - } - - [Fact] - public void should_add_plain_text_header_to_downstream_request() - { - this.Given(_ => GivenHttpRequestWithoutHeaders()) - .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) - .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) - .BDDfy(); - } - - [Fact] - public void should_overwrite_existing_header_with_added_header() - { - this.Given(_ => GivenHttpRequestWithHeader("X-Custom-Header", "This should get overwritten")) - .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) - .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) - .BDDfy(); - } - - private void ThenAnErrorIsLogged(string key, string value) - { - _logger.Verify(x => x.LogWarning($"Unable to add header to response {key}: {value}"), Times.Once); - } - - private void GivenHttpRequestWithoutHeaders() - { - _context = new DefaultHttpContext - { - Request = - { - Headers = - { - } - } - }; - } - - private void GivenHttpRequestWithHeader(string headerKey, string headerValue) - { - _context = new DefaultHttpContext - { - Request = - { - Headers = - { - { headerKey, headerValue } - } - } - }; - } - - private void WhenAddingHeader(string headerKey, string headerValue) - { - _addedHeader = new AddHeader(headerKey, headerValue); - _addHeadersToRequest.SetHeadersOnDownstreamRequest(new[] { _addedHeader }, _context); - } - - private void ThenTheHeaderGetsTakenOverToTheRequestHeaders() - { - var requestHeaders = _context.Request.Headers; - requestHeaders.ContainsKey(_addedHeader.Key).ShouldBeTrue($"Header {_addedHeader.Key} was expected but not there."); - var value = requestHeaders[_addedHeader.Key]; - value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null."); - value.ToString().ShouldBe(_addedHeader.Value); - } - - private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected) - { - var requestHeaders = _context.Request.Headers; - var value = requestHeaders[_addedHeader.Key]; - value.ToString().ShouldBe(expected); - } - } -} + using Microsoft.Extensions.Primitives; + using Moq; + using Ocelot.Configuration.Creator; + using Ocelot.Headers; + using Ocelot.Infrastructure; + using Ocelot.Infrastructure.Claims.Parser; + using Ocelot.Logging; + using Responder; + using Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class AddHeadersToRequestPlainTests + { + private readonly AddHeadersToRequest _addHeadersToRequest; + private HttpContext _context; + private AddHeader _addedHeader; + private readonly Mock _placeholders; + private Mock _factory; + private readonly Mock _logger; + + public AddHeadersToRequestPlainTests() + { + _placeholders = new Mock(); + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _addHeadersToRequest = new AddHeadersToRequest(Mock.Of(), _placeholders.Object, _factory.Object); + } + + [Fact] + public void should_log_error_if_cannot_find_placeholder() + { + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); + + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) + .Then(_ => ThenAnErrorIsLogged("X-Forwarded-For", "{RemoteIdAddress}")) + .BDDfy(); + } + + [Fact] + public void should_add_placeholder_to_downstream_request() + { + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse("replaced")); + + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Forwarded-For", "{RemoteIdAddress}")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders("replaced")) + .BDDfy(); + } + + [Fact] + public void should_add_plain_text_header_to_downstream_request() + { + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) + .BDDfy(); + } + + [Fact] + public void should_overwrite_existing_header_with_added_header() + { + this.Given(_ => GivenHttpRequestWithHeader("X-Custom-Header", "This should get overwritten")) + .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) + .BDDfy(); + } + + private void ThenAnErrorIsLogged(string key, string value) + { + _logger.Verify(x => x.LogWarning($"Unable to add header to response {key}: {value}"), Times.Once); + } + + private void GivenHttpRequestWithoutHeaders() + { + _context = new DefaultHttpContext + { + Request = + { + Headers = + { + } + } + }; + } + + private void GivenHttpRequestWithHeader(string headerKey, string headerValue) + { + _context = new DefaultHttpContext + { + Request = + { + Headers = + { + { headerKey, headerValue } + } + } + }; + } + + private void WhenAddingHeader(string headerKey, string headerValue) + { + _addedHeader = new AddHeader(headerKey, headerValue); + _addHeadersToRequest.SetHeadersOnDownstreamRequest(new[] { _addedHeader }, _context); + } + + private void ThenTheHeaderGetsTakenOverToTheRequestHeaders() + { + var requestHeaders = _context.Request.Headers; + requestHeaders.ContainsKey(_addedHeader.Key).ShouldBeTrue($"Header {_addedHeader.Key} was expected but not there."); + var value = requestHeaders[_addedHeader.Key]; + value.ShouldNotBe(default(StringValues), $"Value of header {_addedHeader.Key} was expected to not be null."); + value.ToString().ShouldBe(_addedHeader.Value); + } + + private void ThenTheHeaderGetsTakenOverToTheRequestHeaders(string expected) + { + var requestHeaders = _context.Request.Headers; + var value = requestHeaders[_addedHeader.Key]; + value.ToString().ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 65aaf0960..788b4cc14 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -2,7 +2,7 @@ namespace Ocelot.UnitTests.Headers { using Microsoft.AspNetCore.Http; using Moq; - using Ocelot.Authorisation.Middleware; + using Ocelot.Authorization.Middleware; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; @@ -38,7 +38,7 @@ public HttpHeadersTransformationMiddlewareTests() _postReplacer = new Mock(); _loggerFactory = new Mock(); _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _addHeadersToResponse = new Mock(); _addHeadersToRequest = new Mock(); diff --git a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs index 0effbed5a..6eb08d94a 100644 --- a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs @@ -1,88 +1,88 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Infrastructure -{ - public class HttpDataRepositoryTests - { - private readonly HttpContext _httpContext; - private IHttpContextAccessor _httpContextAccessor; - private readonly HttpDataRepository _httpDataRepository; - private object _result; - - public HttpDataRepositoryTests() - { - _httpContext = new DefaultHttpContext(); - _httpContextAccessor = new HttpContextAccessor { HttpContext = _httpContext }; - _httpDataRepository = new HttpDataRepository(_httpContextAccessor); - } - - /* - TODO - Additional tests -> Type mistmatch aka Add string, request int - TODO - Additional tests -> HttpContent null. This should never happen - */ - - [Fact] - public void get_returns_correct_key_from_http_context() - { - this.Given(x => x.GivenAHttpContextContaining("key", "string")) - .When(x => x.GetIsCalledWithKey("key")) - .Then(x => x.ThenTheResultIsAnOkResponse("string")) - .BDDfy(); - } - - [Fact] - public void get_returns_error_response_if_the_key_is_not_found() //Therefore does not return null - { - this.Given(x => x.GivenAHttpContextContaining("key", "string")) - .When(x => x.GetIsCalledWithKey("keyDoesNotExist")) - .Then(x => x.ThenTheResultIsAnErrorReposnse("string1")) - .BDDfy(); - } - - [Fact] - public void should_update() - { - this.Given(x => x.GivenAHttpContextContaining("key", "string")) - .And(x => x.UpdateIsCalledWith("key", "new string")) - .When(x => x.GetIsCalledWithKey("key")) - .Then(x => x.ThenTheResultIsAnOkResponse("new string")) - .BDDfy(); - } - - private void UpdateIsCalledWith(string key, string value) - { - _httpDataRepository.Update(key, value); - } - - private void GivenAHttpContextContaining(string key, object o) - { - _httpContext.Items.Add(key, o); - } - - private void GetIsCalledWithKey(string key) - { - _result = _httpDataRepository.Get(key); - } - - private void ThenTheResultIsAnErrorReposnse(object resultValue) - { - _result.ShouldBeOfType>(); - ((ErrorResponse)_result).Data.ShouldBeNull(); - ((ErrorResponse)_result).IsError.ShouldBe(true); - ((ErrorResponse)_result).Errors.ShouldHaveSingleItem() - .ShouldBeOfType() - .Message.ShouldStartWith("Unable to find data for key: "); - } - - private void ThenTheResultIsAnOkResponse(object resultValue) - { - _result.ShouldBeOfType>(); - ((OkResponse)_result).Data.ShouldBe(resultValue); - } - } +using Microsoft.AspNetCore.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class HttpDataRepositoryTests + { + private readonly HttpContext _httpContext; + private IHttpContextAccessor _httpContextAccessor; + private readonly HttpDataRepository _httpDataRepository; + private object _result; + + public HttpDataRepositoryTests() + { + _httpContext = new DefaultHttpContext(); + _httpContextAccessor = new HttpContextAccessor { HttpContext = _httpContext }; + _httpDataRepository = new HttpDataRepository(_httpContextAccessor); + } + + /* + TODO - Additional tests -> Type mistmatch aka Add string, request int + TODO - Additional tests -> HttpContent null. This should never happen + */ + + [Fact] + public void get_returns_correct_key_from_http_context() + { + this.Given(x => x.GivenAHttpContextContaining("key", "string")) + .When(x => x.GetIsCalledWithKey("key")) + .Then(x => x.ThenTheResultIsAnOkResponse("string")) + .BDDfy(); + } + + [Fact] + public void get_returns_error_response_if_the_key_is_not_found() //Therefore does not return null + { + this.Given(x => x.GivenAHttpContextContaining("key", "string")) + .When(x => x.GetIsCalledWithKey("keyDoesNotExist")) + .Then(x => x.ThenTheResultIsAnErrorReposnse("string1")) + .BDDfy(); + } + + [Fact] + public void should_update() + { + this.Given(x => x.GivenAHttpContextContaining("key", "string")) + .And(x => x.UpdateIsCalledWith("key", "new string")) + .When(x => x.GetIsCalledWithKey("key")) + .Then(x => x.ThenTheResultIsAnOkResponse("new string")) + .BDDfy(); + } + + private void UpdateIsCalledWith(string key, string value) + { + _httpDataRepository.Update(key, value); + } + + private void GivenAHttpContextContaining(string key, object o) + { + _httpContext.Items.Add(key, o); + } + + private void GetIsCalledWithKey(string key) + { + _result = _httpDataRepository.Get(key); + } + + private void ThenTheResultIsAnErrorReposnse(object resultValue) + { + _result.ShouldBeOfType>(); + ((ErrorResponse)_result).Data.ShouldBe(default(T)); + ((ErrorResponse)_result).IsError.ShouldBe(true); + ((ErrorResponse)_result).Errors.ShouldHaveSingleItem() + .ShouldBeOfType() + .Message.ShouldStartWith("Unable to find data for key: "); + } + + private void ThenTheResultIsAnOkResponse(object resultValue) + { + _result.ShouldBeOfType>(); + ((OkResponse)_result).Data.ShouldBe(resultValue); + } + } } diff --git a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthorizerTests.cs similarity index 87% rename from test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs rename to test/Ocelot.UnitTests/Infrastructure/ScopesAuthorizerTests.cs index 08aa78a12..ccc17f195 100644 --- a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthorizerTests.cs @@ -1,5 +1,5 @@ using Moq; -using Ocelot.Authorisation; +using Ocelot.Authorization; using Ocelot.Errors; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Responses; @@ -11,18 +11,18 @@ namespace Ocelot.UnitTests.Infrastructure { - public class ScopesAuthoriserTests + public class ScopesAuthorizerTests { - private ScopesAuthoriser _authoriser; + private ScopesAuthorizer _authorizer; public Mock _parser; private ClaimsPrincipal _principal; private List _allowedScopes; private Response _result; - public ScopesAuthoriserTests() + public ScopesAuthorizerTests() { _parser = new Mock(); - _authoriser = new ScopesAuthoriser(_parser.Object); + _authorizer = new ScopesAuthorizer(_parser.Object); } [Fact] @@ -30,7 +30,7 @@ public void should_return_ok_if_no_allowed_scopes() { this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) .And(_ => GivenTheFollowing(new List())) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) .BDDfy(); } @@ -40,7 +40,7 @@ public void should_return_ok_if_null_allowed_scopes() { this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) .And(_ => GivenTheFollowing((List)null)) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) .BDDfy(); } @@ -52,7 +52,7 @@ public void should_return_error_if_claims_parser_returns_error() this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) .And(_ => GivenTheFollowing(new List() { "doesntmatter" })) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) .BDDfy(); } @@ -66,7 +66,7 @@ public void should_match_scopes_and_return_ok_result() this.Given(_ => GivenTheFollowing(claimsPrincipal)) .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) .BDDfy(); } @@ -82,7 +82,7 @@ public void should_not_match_scopes_and_return_error_result() this.Given(_ => GivenTheFollowing(claimsPrincipal)) .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) .BDDfy(); } @@ -102,9 +102,9 @@ private void GivenTheFollowing(List allowedScopes) _allowedScopes = allowedScopes; } - private void WhenIAuthorise() + private void WhenIAuthorize() { - _result = _authoriser.Authorise(_principal, _allowedScopes); + _result = _authorizer.Authorize(_principal, _allowedScopes); } private void ThenTheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs index b3f0193cb..855388b19 100644 --- a/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Kubernetes/KubeServiceDiscoveryProviderTests.cs @@ -1,149 +1,154 @@ -using KubeClient; -using KubeClient.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Moq; -using Newtonsoft.Json; -using Ocelot.Logging; -using Ocelot.Provider.Kubernetes; -using Ocelot.Values; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Kubernetes -{ - public class KubeServiceDiscoveryProviderTests : IDisposable - { - private IWebHost _fakeKubeBuilder; - private readonly KubernetesServiceDiscoveryProvider _provider; - private EndpointsV1 _endpointEntries; - private readonly string _serviceName; - private readonly string _namespaces; - private readonly int _port; - private readonly string _kubeHost; - private readonly string _fakekubeServiceDiscoveryUrl; - private List _services; - private readonly Mock _factory; - private readonly Mock _logger; - private string _receivedToken; - private readonly IKubeApiClient _clientFactory; - - public KubeServiceDiscoveryProviderTests() - { - _serviceName = "test"; - _namespaces = "dev"; - _port = 86; - _kubeHost = "localhost"; - _fakekubeServiceDiscoveryUrl = $"http://{_kubeHost}:{_port}"; - _endpointEntries = new EndpointsV1(); - _factory = new Mock(); - - var option = new KubeClientOptions - { - ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), - AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", - AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, - AllowInsecure = true, - }; - - _clientFactory = KubeApiClient.Create(option); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - var config = new KubeRegistryConfiguration() - { - KeyOfServiceInK8s = _serviceName, - KubeNamespace = _namespaces, - }; - _provider = new KubernetesServiceDiscoveryProvider(config, _factory.Object, _clientFactory); - } - - [Fact] - public void should_return_service_from_k8s() - { - var token = "Bearer txpc696iUhbVoudg164r93CxDTrKRVWG"; - var endPointEntryOne = new EndpointsV1 - { - Kind = "endpoint", - ApiVersion = "1.0", - Metadata = new ObjectMetaV1() - { - Namespace = "dev", - }, - }; - var endpointSubsetV1 = new EndpointSubsetV1(); - endpointSubsetV1.Addresses.Add(new EndpointAddressV1() - { - Ip = "127.0.0.1", - Hostname = "localhost", - }); - endpointSubsetV1.Ports.Add(new EndpointPortV1() - { - Port = 80, - }); - endPointEntryOne.Subsets.Add(endpointSubsetV1); - - this.Given(x => GivenThereIsAFakeKubeServiceDiscoveryProvider(_fakekubeServiceDiscoveryUrl, _serviceName, _namespaces)) - .And(x => GivenTheServicesAreRegisteredWithKube(endPointEntryOne)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(1)) - .And(_ => _receivedToken.ShouldBe(token)) - .BDDfy(); - } - - private void ThenTheCountIs(int count) - { - _services.Count.ShouldBe(count); +using KubeClient; +using KubeClient.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Moq; +using Newtonsoft.Json; +using Ocelot.Logging; +using Ocelot.Provider.Kubernetes; +using Ocelot.Values; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Kubernetes +{ + public class KubeServiceDiscoveryProviderTests : IDisposable + { + private IWebHost _fakeKubeBuilder; + private readonly KubernetesServiceDiscoveryProvider _provider; + private EndpointsV1 _endpointEntries; + private readonly string _serviceName; + private readonly string _namespaces; + private readonly int _port; + private readonly string _kubeHost; + private readonly string _fakekubeServiceDiscoveryUrl; + private List _services; + private readonly Mock _factory; + private readonly Mock _logger; + private string _receivedToken; + private readonly IKubeApiClient _clientFactory; + + public KubeServiceDiscoveryProviderTests() + { + _serviceName = "test"; + _namespaces = "dev"; + _port = 86; + _kubeHost = "localhost"; + _fakekubeServiceDiscoveryUrl = $"http://{_kubeHost}:{_port}"; + _endpointEntries = new EndpointsV1(); + _factory = new Mock(); + + var option = new KubeClientOptions + { + ApiEndPoint = new Uri(_fakekubeServiceDiscoveryUrl), + AccessToken = "txpc696iUhbVoudg164r93CxDTrKRVWG", + AuthStrategy = KubeClient.KubeAuthStrategy.BearerToken, + AllowInsecure = true, + }; + + _clientFactory = KubeApiClient.Create(option); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + var config = new KubeRegistryConfiguration() + { + KeyOfServiceInK8s = _serviceName, + KubeNamespace = _namespaces, + }; + _provider = new KubernetesServiceDiscoveryProvider(config, _factory.Object, _clientFactory); + } + + [Fact] + public void should_return_service_from_k8s() + { + var token = "Bearer txpc696iUhbVoudg164r93CxDTrKRVWG"; + var endPointEntryOne = new EndpointsV1 + { + Kind = "endpoint", + ApiVersion = "1.0", + Metadata = new ObjectMetaV1() + { + Namespace = "dev", + }, + }; + var endpointSubsetV1 = new EndpointSubsetV1(); + endpointSubsetV1.Addresses.Add(new EndpointAddressV1() + { + Ip = "127.0.0.1", + Hostname = "localhost", + }); + endpointSubsetV1.Ports.Add(new EndpointPortV1() + { + Port = 80, + }); + endPointEntryOne.Subsets.Add(endpointSubsetV1); + + this.Given(x => GivenThereIsAFakeKubeServiceDiscoveryProvider(_fakekubeServiceDiscoveryUrl, _serviceName, _namespaces)) + .And(x => GivenTheServicesAreRegisteredWithKube(endPointEntryOne)) + .When(x => WhenIGetTheServices()) + .Then(x => ThenTheCountIs(1)) + .And(_ => ThenTheTokenIs(token)) + .BDDfy(); } - private void WhenIGetTheServices() + private void ThenTheTokenIs(string token) { - _services = _provider.Get().GetAwaiter().GetResult(); - } - - private void GivenTheServicesAreRegisteredWithKube(EndpointsV1 endpointEntries) - { - _endpointEntries = endpointEntries; - } - - private void GivenThereIsAFakeKubeServiceDiscoveryProvider(string url, string serviceName, string namespaces) - { - _fakeKubeBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Path.Value == $"/api/v1/namespaces/{namespaces}/endpoints/{serviceName}") - { - if (context.Request.Headers.TryGetValue("Authorization", out var values)) - { - _receivedToken = values.First(); - } - - var json = JsonConvert.SerializeObject(_endpointEntries); - context.Response.Headers.Add("Content-Type", "application/json"); - await context.Response.WriteAsync(json); - } - }); - }) - .Build(); - - _fakeKubeBuilder.Start(); - } - - public void Dispose() - { - _fakeKubeBuilder?.Dispose(); - } - } -} + _receivedToken.ShouldBe(token); + } + + private void ThenTheCountIs(int count) + { + _services.Count.ShouldBe(count); + } + + private void WhenIGetTheServices() + { + _services = _provider.Get().GetAwaiter().GetResult(); + } + + private void GivenTheServicesAreRegisteredWithKube(EndpointsV1 endpointEntries) + { + _endpointEntries = endpointEntries; + } + + private void GivenThereIsAFakeKubeServiceDiscoveryProvider(string url, string serviceName, string namespaces) + { + _fakeKubeBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Path.Value == $"/api/v1/namespaces/{namespaces}/endpoints/{serviceName}") + { + if (context.Request.Headers.TryGetValue("Authorization", out var values)) + { + _receivedToken = values.First(); + } + + var json = JsonConvert.SerializeObject(_endpointEntries); + context.Response.Headers.Add("Content-Type", "application/json"); + await context.Response.WriteAsync(json); + } + }); + }) + .Build(); + + _fakeKubeBuilder.Start(); + } + + public void Dispose() + { + _fakeKubeBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index b2a031d49..959d23315 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -1,97 +1,95 @@ - - - - 0.0.0-dev - netcoreapp3.1 - Ocelot.UnitTests - Ocelot.UnitTests - Exe - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - ..\..\codeanalysis.ruleset - - - - full - True - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - - - - - - - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - \ No newline at end of file + + + + 0.0.0-dev + net5.0 + Ocelot.UnitTests + Ocelot.UnitTests + Exe + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + ..\..\codeanalysis.ruleset + + + + full + True + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs deleted file mode 100644 index 212d92931..000000000 --- a/test/Ocelot.UnitTests/Rafty/OcelotAdministrationBuilderExtensionsTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace Ocelot.UnitTests.Rafty -{ - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Administration; - using Ocelot.DependencyInjection; - using Provider.Rafty; - using Shouldly; - using System; - using System.Collections.Generic; - using System.Reflection; - using TestStack.BDDfy; - using Xunit; - - public class OcelotAdministrationBuilderExtensionsTests - { - private readonly IServiceCollection _services; - private IServiceProvider _serviceProvider; - private readonly IConfiguration _configRoot; - private IOcelotBuilder _ocelotBuilder; - private Exception _ex; - - public OcelotAdministrationBuilderExtensionsTests() - { - _configRoot = new ConfigurationRoot(new List()); - _services = new ServiceCollection(); - _services.AddSingleton(GetHostingEnvironment()); - _services.AddSingleton(_configRoot); - } - - private IWebHostEnvironment GetHostingEnvironment() - { - var environment = new Mock(); - environment - .Setup(e => e.ApplicationName) - .Returns(typeof(OcelotAdministrationBuilderExtensionsTests).GetTypeInfo().Assembly.GetName().Name); - - return environment.Object; - } - - [Fact] - public void should_set_up_rafty() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpRafty()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - private void WhenISetUpRafty() - { - try - { - _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpOcelotServices() - { - try - { - _ocelotBuilder = _services.AddOcelot(_configRoot); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnExceptionIsntThrown() - { - _ex.ShouldBeNull(); - } - - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - } -} diff --git a/test/Ocelot.UnitTests/Rafty/OcelotFiniteStateMachineTests.cs b/test/Ocelot.UnitTests/Rafty/OcelotFiniteStateMachineTests.cs deleted file mode 100644 index 53dc3476c..000000000 --- a/test/Ocelot.UnitTests/Rafty/OcelotFiniteStateMachineTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Ocelot.UnitTests.Rafty -{ - using Moq; - using Ocelot.Configuration.Setter; - using Provider.Rafty; - using TestStack.BDDfy; - using Xunit; - - public class OcelotFiniteStateMachineTests - { - private UpdateFileConfiguration _command; - private readonly OcelotFiniteStateMachine _fsm; - private readonly Mock _setter; - - public OcelotFiniteStateMachineTests() - { - _setter = new Mock(); - _fsm = new OcelotFiniteStateMachine(_setter.Object); - } - - [Fact] - public void should_handle_update_file_configuration_command() - { - this.Given(x => GivenACommand(new UpdateFileConfiguration(new Ocelot.Configuration.File.FileConfiguration()))) - .When(x => WhenTheCommandIsHandled()) - .Then(x => ThenTheStateIsUpdated()) - .BDDfy(); - } - - private void GivenACommand(UpdateFileConfiguration command) - { - _command = command; - } - - private void WhenTheCommandIsHandled() - { - _fsm.Handle(new global::Rafty.Log.LogEntry(_command, _command.GetType(), 0)).Wait(); - } - - private void ThenTheStateIsUpdated() - { - _setter.Verify(x => x.Set(_command.Configuration), Times.Once); - } - } -} diff --git a/test/Ocelot.UnitTests/Rafty/RaftyFileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Rafty/RaftyFileConfigurationSetterTests.cs deleted file mode 100644 index fac7d2fa9..000000000 --- a/test/Ocelot.UnitTests/Rafty/RaftyFileConfigurationSetterTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Ocelot.UnitTests.Rafty -{ - using global::Rafty.Concensus.Node; - using global::Rafty.Infrastructure; - using Moq; - using Ocelot.Configuration.File; - using Provider.Rafty; - using Shouldly; - using System.Threading.Tasks; - using Xunit; - - public class RaftyFileConfigurationSetterTests - { - private readonly RaftyFileConfigurationSetter _setter; - private readonly Mock _node; - - public RaftyFileConfigurationSetterTests() - { - _node = new Mock(); - _setter = new RaftyFileConfigurationSetter(_node.Object); - } - - [Fact] - public async Task should_return_ok() - { - var fileConfig = new FileConfiguration(); - - var response = new OkResponse(new UpdateFileConfiguration(fileConfig)); - - _node.Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(response); - - var result = await _setter.Set(fileConfig); - result.IsError.ShouldBeFalse(); - } - - [Fact] - public async Task should_return_not_ok() - { - var fileConfig = new FileConfiguration(); - - var response = new ErrorResponse("error", new UpdateFileConfiguration(fileConfig)); - - _node.Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(response); - - var result = await _setter.Set(fileConfig); - - result.IsError.ShouldBeTrue(); - } - } -} diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index ad930730f..ddb475bcf 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -452,7 +452,7 @@ private void ThenTheMappedRequestHasEachHeader() foreach (var header in _mappedRequest.Data.Headers) { var inputHeader = _inputHeaders.First(h => h.Key == header.Key); - inputHeader.ShouldNotBeNull(); + inputHeader.ShouldNotBe(default(KeyValuePair)); inputHeader.Value.Count().ShouldBe(header.Value.Count()); foreach (var inputHeaderValue in inputHeader.Value) { diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 04a4496fe..543a1cb92 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -29,10 +29,10 @@ public void should_return_unauthorized(OcelotErrorCode errorCode) [Theory] [InlineData(OcelotErrorCode.CannotFindClaimError)] - [InlineData(OcelotErrorCode.ClaimValueNotAuthorisedError)] - [InlineData(OcelotErrorCode.ScopeNotAuthorisedError)] + [InlineData(OcelotErrorCode.ClaimValueNotAuthorizedError)] + [InlineData(OcelotErrorCode.ScopeNotAuthorizedError)] [InlineData(OcelotErrorCode.UnauthorizedError)] - [InlineData(OcelotErrorCode.UserDoesNotHaveClaimError)] + [InlineData(OcelotErrorCode.UserDoesNotHaveClaimError)] public void should_return_forbidden(OcelotErrorCode errorCode) { ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.Forbidden); @@ -52,8 +52,8 @@ public void should_return_service_unavailable(OcelotErrorCode errorCode) public void should_return_internal_server_error(OcelotErrorCode errorCode) { ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.InternalServerError); - } - + } + [Theory] [InlineData(OcelotErrorCode.ConnectionToDownstreamServiceError)] public void should_return_bad_gateway_error(OcelotErrorCode errorCode) @@ -104,7 +104,7 @@ public void AuthenticationErrorsHaveHighestPriority() } [Fact] - public void AuthorisationErrorsHaveSecondHighestPriority() + public void AuthorizationErrorsHaveSecondHighestPriority() { var errors = new List { @@ -177,4 +177,4 @@ private void ThenTheResponseIsStatusCodeIs(HttpStatusCode expectedCode) _result.ShouldBe((int)expectedCode); } } -} +} diff --git a/test/Ocelot.UnitTests/UnitTests.runsettings b/test/Ocelot.UnitTests/UnitTests.runsettings index 1cba21cc9..80c9b34f5 100644 --- a/test/Ocelot.UnitTests/UnitTests.runsettings +++ b/test/Ocelot.UnitTests/UnitTests.runsettings @@ -1,23 +1,23 @@ - - - - - - - opencover - false - true - - - - - - - - - - + + + + + + + opencover + false + true + + + + + + + + + + \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config deleted file mode 100644 index 238f21ca0..000000000 --- a/tools/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 9abd47b0df8c1fe2441f2fdf82b93abdd573478b Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 11 Dec 2020 10:25:03 +0000 Subject: [PATCH 2/4] +semver: breaking bump to version 17.0.0 (#1392) * upgrade csproj to net5.0 * add make and build tools to image * fix code broken after net5.0 upgrade * update circle build image * removed rafty and updated more packages * all packages upgraded and tests passing * bring back develop * rename authorisation to authorization --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9ff5a61f..d5b0b8b68 100644 --- a/README.md +++ b/README.md @@ -81,4 +81,4 @@ If you think this project is worth supporting financially please make a contribu ## Things that are currently annoying me -[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) +[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) \ No newline at end of file From 47a024b844eb34271b1252357c5ec24d8311e504 Mon Sep 17 00:00:00 2001 From: TGP Date: Mon, 20 Dec 2021 21:35:53 +0000 Subject: [PATCH 3/4] fix cake.json version --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index efd9e76b1..9a0be22c0 100644 --- a/build.cake +++ b/build.cake @@ -1,5 +1,5 @@ #tool "nuget:?package=GitVersion.CommandLine&version=5.0.1" -#addin nuget:?package=Cake.Json +#addin nuget:?package=Cake.Json&version=4.0.0 #addin nuget:?package=Newtonsoft.Json #addin nuget:?package=System.Net.Http&version=4.3.4 #addin nuget:?package=System.Text.Encodings.Web&version=4.7.1 From ba4b767292f35f876e3e71db6515bfffe0a21ec3 Mon Sep 17 00:00:00 2001 From: TGP Date: Mon, 20 Dec 2021 21:38:16 +0000 Subject: [PATCH 4/4] use new deletedir api --- build.cake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 9a0be22c0..2052f340f 100644 --- a/build.cake +++ b/build.cake @@ -95,7 +95,10 @@ Task("Clean") { if (DirectoryExists(artifactsDir)) { - DeleteDirectory(artifactsDir, recursive:true); + DeleteDirectory(artifactsDir, new DeleteDirectorySettings { + Recursive = true, + Force = true + }); } CreateDirectory(artifactsDir); });