[New feature] Support claims to path transformation (#968)

* Add the option to change DownstreamPath based on Claims

* Add tests for Claims to downstream path
This commit is contained in:
Víctor Martos
2019-08-12 19:03:20 +02:00
committed by Thiago Loureiro
parent b32850a804
commit 8117366313
13 changed files with 644 additions and 1 deletions

View File

@ -19,6 +19,7 @@ namespace Ocelot.Configuration.Builder
private Dictionary<string, string> _routeClaimRequirement;
private bool _isAuthorised;
private List<ClaimToThing> _claimToQueries;
private List<ClaimToThing> _claimToDownstreamPath;
private string _requestIdHeaderKey;
private bool _isCached;
private CacheOptions _fileCacheOptions;
@ -127,6 +128,12 @@ namespace Ocelot.Configuration.Builder
return this;
}
public DownstreamReRouteBuilder WithClaimsToDownstreamPath(List<ClaimToThing> input)
{
_claimToDownstreamPath = input;
return this;
}
public DownstreamReRouteBuilder WithIsCached(bool input)
{
_isCached = input;
@ -186,7 +193,7 @@ namespace Ocelot.Configuration.Builder
_serviceName = serviceName;
return this;
}
public DownstreamReRouteBuilder WithServiceNamespace(string serviceNamespace)
{
_serviceNamespace = serviceNamespace;
@ -265,6 +272,7 @@ namespace Ocelot.Configuration.Builder
_claimToQueries,
_claimsToHeaders,
_claimToClaims,
_claimToDownstreamPath,
_isAuthenticated,
_isAuthorised,
_authenticationOptions,

View File

@ -86,6 +86,8 @@ namespace Ocelot.Configuration.Creator
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var claimsToDownstreamPath = _claimsToThingCreator.Create(fileReRoute.ChangeDownstreamPathTemplate);
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod);
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration);
@ -114,6 +116,7 @@ namespace Ocelot.Configuration.Creator
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(fileReRouteOptions.IsAuthorised)
.WithClaimsToQueries(claimsToQueries)
.WithClaimsToDownstreamPath(claimsToDownstreamPath)
.WithRequestIdKey(requestIdKey)
.WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))

View File

@ -28,6 +28,7 @@ namespace Ocelot.Configuration
List<ClaimToThing> claimsToQueries,
List<ClaimToThing> claimsToHeaders,
List<ClaimToThing> claimsToClaims,
List<ClaimToThing> claimsToPath,
bool isAuthenticated,
bool isAuthorised,
AuthenticationOptions authenticationOptions,
@ -63,6 +64,7 @@ namespace Ocelot.Configuration
ClaimsToQueries = claimsToQueries ?? new List<ClaimToThing>();
ClaimsToHeaders = claimsToHeaders ?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims ?? new List<ClaimToThing>();
ClaimsToPath = claimsToPath ?? new List<ClaimToThing>();
IsAuthenticated = isAuthenticated;
IsAuthorised = isAuthorised;
AuthenticationOptions = authenticationOptions;
@ -93,6 +95,7 @@ namespace Ocelot.Configuration
public List<ClaimToThing> ClaimsToQueries { get; }
public List<ClaimToThing> ClaimsToHeaders { get; }
public List<ClaimToThing> ClaimsToClaims { get; }
public List<ClaimToThing> ClaimsToPath { get; }
public bool IsAuthenticated { get; }
public bool IsAuthorised { get; }
public AuthenticationOptions AuthenticationOptions { get; }

View File

@ -11,6 +11,7 @@ namespace Ocelot.Configuration.File
AddClaimsToRequest = new Dictionary<string, string>();
RouteClaimsRequirement = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
ChangeDownstreamPathTemplate = new Dictionary<string, string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
FileCacheOptions = new FileCacheOptions();
QoSOptions = new FileQoSOptions();
@ -34,6 +35,7 @@ namespace Ocelot.Configuration.File
public Dictionary<string, string> AddClaimsToRequest { get; set; }
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string> AddQueriesToRequest { get; set; }
public Dictionary<string, string> ChangeDownstreamPathTemplate { get; set; }
public string RequestIdKey { get; set; }
public FileCacheOptions FileCacheOptions { get; set; }
public bool ReRouteIsCaseSensitive { get; set; }

View File

@ -25,6 +25,7 @@ namespace Ocelot.DependencyInjection
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.PathManipulation;
using Ocelot.QueryStrings;
using Ocelot.RateLimit;
using Ocelot.Request.Creator;
@ -92,6 +93,7 @@ namespace Ocelot.DependencyInjection
Services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
Services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
Services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();
Services.TryAddSingleton<IChangeDownstreamPathTemplate, ChangeDownstreamPathTemplate>();
Services.TryAddSingleton<IClaimsParser, ClaimsParser>();
Services.TryAddSingleton<IUrlPathToUrlTemplateMatcher, RegExUrlMatcher>();
Services.TryAddSingleton<IPlaceholderNameAndValueFinder, UrlPathPlaceholderNameAndValueFinder>();

View File

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using Ocelot.Values;
namespace Ocelot.PathManipulation
{
public class ChangeDownstreamPathTemplate : IChangeDownstreamPathTemplate
{
private readonly IClaimsParser _claimsParser;
public ChangeDownstreamPathTemplate(IClaimsParser claimsParser)
{
_claimsParser = claimsParser;
}
public Response ChangeDownstreamPath(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims,
DownstreamPathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> placeholders)
{
foreach (var config in claimsToThings)
{
var value = _claimsParser.GetValue(claims, config.NewKey, config.Delimiter, config.Index);
if (value.IsError)
{
return new ErrorResponse(value.Errors);
}
var placeholderName = $"{{{config.ExistingKey}}}";
if (!downstreamPathTemplate.Value.Contains(placeholderName))
{
return new ErrorResponse(new CouldNotFindPlaceholderError(placeholderName));
}
if (placeholders.Any(ph => ph.Name == placeholderName))
{
placeholders.RemoveAll(ph => ph.Name == placeholderName);
}
placeholders.Add(new PlaceholderNameAndValue(placeholderName, value.Data));
}
return new OkResponse();
}
}
}

View File

@ -0,0 +1,18 @@
using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Request.Middleware;
using Ocelot.Responses;
using Ocelot.Values;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
namespace Ocelot.PathManipulation
{
public interface IChangeDownstreamPathTemplate
{
Response ChangeDownstreamPath(List<ClaimToThing> claimsToThings, IEnumerable<Claim> claims,
DownstreamPathTemplate downstreamPathTemplate, List<PlaceholderNameAndValue> placeholders);
}
}

View File

@ -0,0 +1,42 @@
using Ocelot.Logging;
using Ocelot.Middleware;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.PathManipulation.Middleware
{
public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate;
public ClaimsToDownstreamPathMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IChangeDownstreamPathTemplate changeDownstreamPathTemplate)
: base(loggerFactory.CreateLogger<ClaimsToDownstreamPathMiddleware>())
{
_next = next;
_changeDownstreamPathTemplate = changeDownstreamPathTemplate;
}
public async Task Invoke(DownstreamContext context)
{
if (context.DownstreamReRoute.ClaimsToPath.Any())
{
Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path");
var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(context.DownstreamReRoute.ClaimsToPath, context.HttpContext.User.Claims,
context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues);
if (response.IsError)
{
Logger.LogWarning("there was an error setting queries on context, setting pipeline error");
SetPipelineError(context, response.Errors);
return;
}
}
await _next.Invoke(context);
}
}
}

View File

@ -0,0 +1,12 @@
using Ocelot.Middleware.Pipeline;
namespace Ocelot.PathManipulation.Middleware
{
public static class ClaimsToDownstreamPathMiddlewareExtensions
{
public static IOcelotPipelineBuilder UseClaimsToDownstreamPathMiddleware(this IOcelotPipelineBuilder builder)
{
return builder.UseMiddleware<ClaimsToDownstreamPathMiddleware>();
}
}
}

View File

@ -7,6 +7,7 @@ using Ocelot.DownstreamUrlCreator.Middleware;
using Ocelot.Errors.Middleware;
using Ocelot.Headers.Middleware;
using Ocelot.LoadBalancer.Middleware;
using Ocelot.PathManipulation.Middleware;
using Ocelot.QueryStrings.Middleware;
using Ocelot.RateLimit.Middleware;
using Ocelot.Request.Middleware;
@ -118,6 +119,8 @@ namespace Ocelot.Middleware.Pipeline
// Now we can run any claims to query string transformation middleware
builder.UseClaimsToQueryStringMiddleware();
builder.UseClaimsToDownstreamPathMiddleware();
// Get the load balancer for this request
builder.UseLoadBalancingMiddleware();