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} | 823 ++++---- .../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, 4766 insertions(+), 6210 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 3325b02d..65fe3691 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 54fec4f0..43f01f6d 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 834ba760..a83aa708 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 511c7983..ade57b25 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 a18d15fe..c9ff5a61 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 063de67d..efd9e76b 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 02d91f53..cc6798c3 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 0478da41..4b99c053 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 5d63816d..0ce0e95e 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 8bb4e206..d2d63934 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 43cb5f61..7a89a1f0 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 274e7299..6449f583 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 f935cbac..e41c0033 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 ab1bc1b5..c890c14c 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 d58801e5..c82657a4 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 aeea7b63..d07eb3a4 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 4b663658..7fccc599 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 69b80404..00000000 --- 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 519cc21a..d3673402 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 8a1acfdb..ad185f29 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 1acbd712..983d2471 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 810fa13c..7c5f80ec 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 86b12ab0..dad54b20 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 26b7a441..be6fd6da 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 bc5318be..f7f4c81e 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 209fa6d2..67a861a8 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 df8f3ec7..f205c25e 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 a1824057..ef23d98d 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) - }; + var responseBody = await _writer.WriteToStringAsync(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(); - }); - - 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 ed6c1660..7f16ed98 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 8cf74f8d..a96c220e 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 3947a3de..732c6594 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 295fc461..fad9470e 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 186b43f3..f0fd14d4 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 236cb179..b7df220e 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 88375a18..e449ce9f 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 da25beaa..cf14fc1e 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; 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 @@ namespace Ocelot.Administration 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 @@ namespace Ocelot.Administration 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 @@ namespace Ocelot.Administration 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 @@ namespace Ocelot.Administration .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; + o.EmitStaticAudienceClaim = true; }) + .AddInMemoryApiScopes(ApiScopes(identityServerConfiguration)) .AddInMemoryApiResources(Resources(identityServerConfiguration)) .AddInMemoryClients(Client(identityServerConfiguration)); @@ -68,14 +73,17 @@ namespace Ocelot.Administration 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 @@ namespace Ocelot.Administration } } + 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 @@ namespace Ocelot.Administration { new Secret { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } + Value = identityServerConfiguration.ApiSecret.Sha256(), + }, + }, }, }; } @@ -117,8 +130,8 @@ namespace Ocelot.Administration 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 dcc97dd6..43baa332 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 eaae221f..13472b02 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 8a064549..9da8decd 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 550a83a3..13e6fc8f 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 e808bf93..3581464d 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 8f342865..c8d1ebde 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 e082c606..8764b012 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 c006aaf3..00000000 --- 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 de611da7..00000000 --- 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 4bb57548..00000000 --- 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 4d5f9e39..00000000 --- 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 ddca1ac2..00000000 --- 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 c3ed9e65..00000000 --- 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 058fb0cf..00000000 --- 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 a0487659..00000000 --- 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 b054852e..00000000 --- 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 dd8b7610..00000000 --- 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 a8006cdc..00000000 --- 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 21fc97a8..00000000 --- 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 62e9711d..00000000 --- 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 1be1645e..00000000 --- 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 961cd6ed..00000000 --- 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 894a758a..00000000 --- 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 f2401e02..1182a364 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 9b6d5aba..e341adba 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 98f8309f..00000000 --- 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 0bd1579a..00000000 --- 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 c7f403de..00000000 --- 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 00000000..c4d4e814 --- /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 d6c99440..15da73d7 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 @@ { // 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 @@ // 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 @@ 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 5e4b9c59..dc89f010 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 57047ce7..e0041cd2 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 0d02c62e..3a9c5d80 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 @@ { 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 @@ 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 00000000..260cc86e --- /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 00000000..dc5823a3 --- /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 8344d80b..7fd7e2aa 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 fc32ea83..689f9e2f 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 6f9aa3eb..08c19d9f 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 b51c33db..5ee5ddf7 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -18,7 +18,7 @@ namespace Ocelot.Configuration.Builder 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 @@ namespace Ocelot.Configuration.Builder return this; } - public DownstreamRouteBuilder WithIsAuthorised(bool input) + public DownstreamRouteBuilder WithIsAuthorized(bool input) { - _isAuthorised = input; + _isAuthorized = input; return this; } @@ -289,7 +289,7 @@ namespace Ocelot.Configuration.Builder _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 ef1cdcd3..7906dad3 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 @@ namespace Ocelot.Configuration.Builder return this; } - public RouteOptionsBuilder WithIsAuthorised(bool isAuthorised) + public RouteOptionsBuilder WithIsAuthorized(bool isAuthorized) { - _isAuthorised = isAuthorised; + _isAuthorized = isAuthorized; return this; } @@ -40,7 +40,7 @@ namespace Ocelot.Configuration.Builder 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 506bd32a..703cb71f 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 @@ namespace Ocelot.Configuration.Creator 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 @@ namespace Ocelot.Configuration.Creator 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 a5b34862..48845ce0 100644 --- a/src/Ocelot/Configuration/Creator/RoutesCreator.cs +++ b/src/Ocelot/Configuration/Creator/RoutesCreator.cs @@ -119,7 +119,7 @@ namespace Ocelot.Configuration.Creator .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 8dd9f30d..b2bde0ed 100644 --- a/src/Ocelot/Configuration/DownstreamRoute.cs +++ b/src/Ocelot/Configuration/DownstreamRoute.cs @@ -31,7 +31,7 @@ namespace Ocelot.Configuration List claimsToClaims, List claimsToPath, bool isAuthenticated, - bool isAuthorised, + bool isAuthorized, AuthenticationOptions authenticationOptions, DownstreamPathTemplate downstreamPathTemplate, string loadBalancerKey, @@ -69,7 +69,7 @@ namespace Ocelot.Configuration ClaimsToClaims = claimsToClaims ?? new List(); ClaimsToPath = claimsToPath ?? new List(); IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; + IsAuthorized = isAuthorized; AuthenticationOptions = authenticationOptions; DownstreamPathTemplate = downstreamPathTemplate; LoadBalancerKey = loadBalancerKey; @@ -102,7 +102,7 @@ namespace Ocelot.Configuration 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 9b5ba8d9..41123598 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 f4fbf844..20c4e4bb 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 @@ namespace Ocelot.DependencyInjection 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 46864181..9063e714 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -16,8 +16,8 @@ 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 b1f1bf99..f24efa2d 100644 --- a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -39,22 +39,22 @@ 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 6158c72b..58ab855d 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 @@ 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 8f4f981d..4cd8640f 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 39b5e749..0f74a7ad 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -14,8 +14,8 @@ namespace Ocelot.Responder } 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 a10d480b..7158f010 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(); } - public List Errors { get; } - - public bool IsError => Errors.Count > 0; - } + protected Response(List errors) + { + Errors = errors ?? new List(); + } + + 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 f019c12f..5c541ac6 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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 4f8a28df..394c76bc 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() - { - int port = RandomPortFinder.GetRandomPort(); - - var configuration = new FileConfiguration + _identityServerRootUrl = $"http://localhost:{identityServerPort}"; + _options = o => { - 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"} - } - } - } + 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 + { + 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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 08f07f8a..4c998ef9 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 647948ec..3bb37848 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 2712e2b7..71145092 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 @@ namespace Ocelot.AcceptanceTests .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 cfdbdbb4..22285e15 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 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 8137bc8a..1c5aefd8 100644 --- a/test/Ocelot.AcceptanceTests/ContentTests.cs +++ b/test/Ocelot.AcceptanceTests/ContentTests.cs @@ -147,7 +147,7 @@ namespace Ocelot.AcceptanceTests 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 f420af6a..e155010d 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 @@ { 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 @@ } [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 @@ _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -172,8 +172,8 @@ _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -208,17 +208,17 @@ } [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 @@ _counter++; await next.Invoke(); } - }; - + }; + var port = RandomPortFinder.GetRandomPort(); var fileConfiguration = new FileConfiguration @@ -295,8 +295,8 @@ .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 @@ 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 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 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 dedb32bd..5f984a27 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 @@ 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 @@ { 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 { - 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" - } - } + 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", + }, + }, }; apps.Add(a); @@ -138,8 +138,8 @@ { 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 f5d69d13..728d455e 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -24,8 +24,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_transform_upstream_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -65,8 +65,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_transform_downstream_header() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -105,8 +105,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_fix_issue_190() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -149,8 +149,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_fix_issue_205() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -193,8 +193,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_fix_issue_417() - { - var port = RandomPortFinder.GetRandomPort(); + { + var port = RandomPortFinder.GetRandomPort(); var configuration = new FileConfiguration { @@ -241,8 +241,8 @@ namespace Ocelot.AcceptanceTests [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 @@ namespace Ocelot.AcceptanceTests [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 @@ namespace Ocelot.AcceptanceTests [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 @@ namespace Ocelot.AcceptanceTests [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 @@ namespace Ocelot.AcceptanceTests 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" || headerValue == "test=1; path=/") + if (cookieValue == "0") + { + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + } + } + + if (context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + { + 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 129935d4..ab9f7dff 100644 --- a/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs @@ -62,7 +62,7 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests .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 4ddf06a3..e09db82d 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 d93f9c1b..8adf54e8 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 @@ namespace Ocelot.AcceptanceTests 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 329c410e..f56e3d28 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -322,7 +322,7 @@ .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 @@ .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 45330311..233be3c1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -814,7 +814,7 @@ namespace Ocelot.AcceptanceTests 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 @@ namespace Ocelot.AcceptanceTests 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 048484b6..80e9b5f0 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -57,7 +57,7 @@ namespace Ocelot.AcceptanceTests .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 @@ namespace Ocelot.AcceptanceTests { 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 0cbe4307..99d9b0c6 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 d45bac23..8b39c9cb 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 59b45dc4..53a899c1 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 359aa6f5..00000000 --- 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 40e36ebc..3eae2086 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 e19025d1..c4f675cf 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 4c72849e..f1b14b0a 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 @@ namespace Ocelot.UnitTests.Authorization private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise( + .Setup(x => x.Authorize( It.IsAny(), It.IsAny>(), It.IsAny>())) @@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.Authorization 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 b22e7ffc..34ed4afa 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 @@ namespace Ocelot.UnitTests.Authorization { {"UserType", "registered"} })) - .When(x => x.WhenICallTheAuthoriser()) - .Then(x => x.ThenTheUserIsAuthorised()) + .When(x => x.WhenICallTheAuthorizer()) + .Then(x => x.ThenTheUserIsAuthorized()) .BDDfy(); } @@ -55,8 +55,8 @@ namespace Ocelot.UnitTests.Authorization { 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 @@ namespace Ocelot.UnitTests.Authorization { 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 @@ namespace Ocelot.UnitTests.Authorization { {"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 @@ namespace Ocelot.UnitTests.Authorization _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 c5e0d75e..c450abf5 100644 --- a/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RouteOptionsCreatorTests.cs @@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration var expected = new RouteOptionsBuilder() .WithIsAuthenticated(true) - .WithIsAuthorised(true) + .WithIsAuthorized(true) .WithIsCached(true) .WithRateLimiting(true) .WithUseServiceDiscovery(true) @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.Configuration 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 81500e51..1e168cff 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -218,7 +218,7 @@ { _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 8e06f111..6e46ba76 100644 --- a/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/Consul/ConsulServiceDiscoveryProviderTests.cs @@ -87,7 +87,7 @@ Address = "localhost", Port = 50881, ID = Guid.NewGuid().ToString(), - Tags = new string[0] + Tags = new string[0], }, }; @@ -95,7 +95,7 @@ .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) .When(_ => WhenIGetTheServices()) .Then(_ => ThenTheCountIs(1)) - .And(_ => _receivedToken.ShouldBe(token)) + .And(_ => ThenTheTokenIs(token)) .BDDfy(); } @@ -253,6 +253,11 @@ _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 5a6a5306..01e2ee30 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 @@ 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 @@ 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 9e1fd3e2..15735940 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 ec332605..2d473ce6 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 cc2916a2..db2d17b6 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 65aaf096..788b4cc1 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 @@ namespace Ocelot.UnitTests.Headers _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 0effbed5..6eb08d94 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 08aa78a1..ccc17f19 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 @@ using Xunit; 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 @@ namespace Ocelot.UnitTests.Infrastructure { this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) .And(_ => GivenTheFollowing(new List())) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) .BDDfy(); } @@ -40,7 +40,7 @@ namespace Ocelot.UnitTests.Infrastructure { this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) .And(_ => GivenTheFollowing((List)null)) - .When(_ => WhenIAuthorise()) + .When(_ => WhenIAuthorize()) .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) .BDDfy(); } @@ -52,7 +52,7 @@ namespace Ocelot.UnitTests.Infrastructure 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 @@ namespace Ocelot.UnitTests.Infrastructure 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 @@ namespace Ocelot.UnitTests.Infrastructure 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 @@ namespace Ocelot.UnitTests.Infrastructure _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 b3f0193c..855388b1 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); +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(); } - [Fact] - public void should_return_service_from_k8s() + private void ThenTheTokenIs(string token) { - 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); - } - - 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(); - } - } -} + _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 b2a031d4..959d2331 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 212d9293..00000000 --- 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 53dc3476..00000000 --- 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 fac7d2fa..00000000 --- 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 ad930730..ddb475bc 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -452,7 +452,7 @@ 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 04a4496f..543a1cb9 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -29,10 +29,10 @@ namespace Ocelot.UnitTests.Responder [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 @@ namespace Ocelot.UnitTests.Responder 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 @@ namespace Ocelot.UnitTests.Responder } [Fact] - public void AuthorisationErrorsHaveSecondHighestPriority() + public void AuthorizationErrorsHaveSecondHighestPriority() { var errors = new List { @@ -177,4 +177,4 @@ namespace Ocelot.UnitTests.Responder _result.ShouldBe((int)expectedCode); } } -} +} diff --git a/test/Ocelot.UnitTests/UnitTests.runsettings b/test/Ocelot.UnitTests/UnitTests.runsettings index 1cba21cc..80c9b34f 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 238f21ca..00000000 --- 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 c9ff5a61..d5b0b8b6 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 efd9e76b..9a0be22c 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 9a0be22c..2052f340 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); });