diff --git a/Applications/.gitkeep b/Applications/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Applications/Proxy/.dockerignore b/Applications/Proxy/.dockerignore new file mode 100644 index 0000000..9f620df --- /dev/null +++ b/Applications/Proxy/.dockerignore @@ -0,0 +1,23 @@ +**/.vs +**/.git + +**/bin +**/obj +**/.vscode +**/TestResults + +**/appsettings.Development.json + +.env +.env.* +.env.example + +*.user +*.suo +*.userosscache +*.sln.docstates +.gitattributes + +Dockerfile +README.md +LICENSE diff --git a/Applications/Proxy/.env.example b/Applications/Proxy/.env.example new file mode 100644 index 0000000..26e088a --- /dev/null +++ b/Applications/Proxy/.env.example @@ -0,0 +1,3 @@ +Routes__0__DownstreamScheme=http +Routes__0__DownstreamHostAndPorts__0__Host=localhost +Routes__0__DownstreamHostAndPorts__0__Port=5286 \ No newline at end of file diff --git a/Applications/Proxy/Dockerfile b/Applications/Proxy/Dockerfile new file mode 100644 index 0000000..287a66c --- /dev/null +++ b/Applications/Proxy/Dockerfile @@ -0,0 +1,42 @@ +# use ASP.NET Core 9.0 runtime image (alpine) as base +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS base +WORKDIR /artifacts +EXPOSE 8080 + +# use SDK image (Alpine) for build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +WORKDIR /workdir + +ARG BUILD_CONFIGURATION=Release +ARG SOLUTION=HttpsRichardy.Federation.Proxy + +# copy only csproj and sln to restore as early as possible +COPY ["Source/${SOLUTION}.csproj", "Source/"] +COPY ["${SOLUTION}.sln", "./"] + +# restore dependencies +RUN dotnet restore "Source/${SOLUTION}.csproj" + +# copy the rest of the source code +COPY Source/ ./Source/ + +WORKDIR "/workdir/Source" + +# build in Release mode +RUN dotnet build "${SOLUTION}.csproj" -c $BUILD_CONFIGURATION -o /artifacts/build + +# publish the project for production +RUN dotnet publish "${SOLUTION}.csproj" -c $BUILD_CONFIGURATION -o /artifacts/publish /p:UseAppHost=false + +# final image to run the app +FROM base AS final +WORKDIR /artifacts + +ENV ASPNETCORE_URLS=http://+:8080 +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true +ENV DOTNET_RUNNING_IN_CONTAINER=true + +# copy published files from the build stage +COPY --from=build /artifacts/publish . + +ENTRYPOINT ["dotnet", "HttpsRichardy.Federation.Proxy.dll"] diff --git a/Applications/Proxy/HttpsRichardy.Federation.Proxy.sln b/Applications/Proxy/HttpsRichardy.Federation.Proxy.sln new file mode 100644 index 0000000..f7ee75b --- /dev/null +++ b/Applications/Proxy/HttpsRichardy.Federation.Proxy.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{B8EFCA5F-814F-285C-A8CB-F00F14650265}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpsRichardy.Federation.Proxy", "Source\HttpsRichardy.Federation.Proxy.csproj", "{5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|x64.ActiveCfg = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|x64.Build.0 = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|x86.ActiveCfg = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Debug|x86.Build.0 = Debug|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|Any CPU.Build.0 = Release|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|x64.ActiveCfg = Release|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|x64.Build.0 = Release|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|x86.ActiveCfg = Release|Any CPU + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5D5AAD1B-A3F6-46D9-B7A3-A4627DED1E64} = {B8EFCA5F-814F-285C-A8CB-F00F14650265} + EndGlobalSection +EndGlobal diff --git a/Applications/Proxy/Source/HttpsRichardy.Federation.Proxy.csproj b/Applications/Proxy/Source/HttpsRichardy.Federation.Proxy.csproj new file mode 100644 index 0000000..a1cf3e7 --- /dev/null +++ b/Applications/Proxy/Source/HttpsRichardy.Federation.Proxy.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/Applications/Proxy/Source/Program.cs b/Applications/Proxy/Source/Program.cs new file mode 100644 index 0000000..7fa466e --- /dev/null +++ b/Applications/Proxy/Source/Program.cs @@ -0,0 +1,23 @@ +namespace HttpsRichardy.Federation.Proxy; + +internal static class Program +{ + private static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); + builder.Configuration.AddEnvironmentVariables(); + + builder.Services + .AddOcelot(builder.Configuration) + .AddPolly(); + + var app = builder.Build(); + + app.UseHttpsRedirection(); + + await app.UseOcelot(); + await app.RunAsync(); + } +} diff --git a/Applications/Proxy/Source/Properties/launchSettings.json b/Applications/Proxy/Source/Properties/launchSettings.json new file mode 100644 index 0000000..14d34bc --- /dev/null +++ b/Applications/Proxy/Source/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5239", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7292;http://localhost:5239", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Applications/Proxy/Source/Usings.cs b/Applications/Proxy/Source/Usings.cs new file mode 100644 index 0000000..c9e4fae --- /dev/null +++ b/Applications/Proxy/Source/Usings.cs @@ -0,0 +1,3 @@ +global using Ocelot.DependencyInjection; +global using Ocelot.Middleware; +global using Ocelot.Provider.Polly; diff --git a/Applications/Proxy/Source/appsettings.json b/Applications/Proxy/Source/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Applications/Proxy/Source/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Applications/Proxy/Source/ocelot.json b/Applications/Proxy/Source/ocelot.json new file mode 100644 index 0000000..d3fa057 --- /dev/null +++ b/Applications/Proxy/Source/ocelot.json @@ -0,0 +1,579 @@ +{ + "Routes": [ + { + "UpstreamHttpMethod": [ "GET" ], + "UpstreamPathTemplate": "/.well-known/openid-configuration", + "DownstreamPathTemplate": "/.well-known/openid-configuration", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET" ], + "UpstreamPathTemplate": "/.well-known/jwks.json", + "DownstreamPathTemplate": "/.well-known/jwks.json", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/protocol/open-id/connect/token", + "DownstreamPathTemplate": "/api/v1/protocol/open-id/connect/token", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/identity", + "DownstreamPathTemplate": "/api/v1/identity", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET" ], + "UpstreamPathTemplate": "/api/v1/identity/principal", + "DownstreamPathTemplate": "/api/v1/identity/principal", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/identity/authenticate", + "DownstreamPathTemplate": "/api/v1/identity/authenticate", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/identity/refresh-token", + "DownstreamPathTemplate": "/api/v1/identity/refresh-token", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/identity/invalidate-session", + "DownstreamPathTemplate": "/api/v1/identity/invalidate-session", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/groups", + "DownstreamPathTemplate": "/api/v1/groups", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "PUT", "DELETE" ], + "UpstreamPathTemplate": "/api/v1/groups/{id}", + "DownstreamPathTemplate": "/api/v1/groups/{id}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/groups/{id}/permissions", + "DownstreamPathTemplate": "/api/v1/groups/{id}/permissions", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "DELETE" ], + "UpstreamPathTemplate": "/api/v1/groups/{id}/permissions/{permissionId}", + "DownstreamPathTemplate": "/api/v1/groups/{id}/permissions/{permissionId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/permissions", + "DownstreamPathTemplate": "/api/v1/permissions", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "PUT", "DELETE" ], + "UpstreamPathTemplate": "/api/v1/permissions/{id}", + "DownstreamPathTemplate": "/api/v1/permissions/{id}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/realms", + "DownstreamPathTemplate": "/api/v1/realms", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "PUT", "DELETE" ], + "UpstreamPathTemplate": "/api/v1/realms/{id}", + "DownstreamPathTemplate": "/api/v1/realms/{id}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/realms/{id}/permissions", + "DownstreamPathTemplate": "/api/v1/realms/{id}/permissions", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "DELETE" ], + "UpstreamPathTemplate": "/api/v1/realms/{id}/permissions/{permissionId}", + "DownstreamPathTemplate": "/api/v1/realms/{id}/permissions/{permissionId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "POST" ], + "UpstreamPathTemplate": "/api/v1/scopes", + "DownstreamPathTemplate": "/api/v1/scopes", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET" ], + "UpstreamPathTemplate": "/api/v1/users", + "DownstreamPathTemplate": "/api/v1/users", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "DELETE" ], + "UpstreamPathTemplate": "/api/v1/users/{id}", + "DownstreamPathTemplate": "/api/v1/users/{id}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/users/{id}/permissions", + "DownstreamPathTemplate": "/api/v1/users/{id}/permissions", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "DELETE" ], + "UpstreamPathTemplate": "/api/v1/users/{id}/permissions/{permissionId}", + "DownstreamPathTemplate": "/api/v1/users/{id}/permissions/{permissionId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "GET", "POST" ], + "UpstreamPathTemplate": "/api/v1/users/{id}/groups", + "DownstreamPathTemplate": "/api/v1/users/{id}/groups", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + }, + { + "UpstreamHttpMethod": [ "DELETE" ], + "UpstreamPathTemplate": "/api/v1/users/{id}/groups/{groupId}", + "DownstreamPathTemplate": "/api/v1/users/{id}/groups/{groupId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5286 + } + ], + "RateLimitOptions": { + "ClientIdHeader": "Client", + "EnableRateLimiting": true, + "Period": "1s", + "Limit": 5 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 5000, + "TimeoutValue": 2000 + } + } + ] +} \ No newline at end of file