diff --git a/src/Ocelot/Authorisation/AddClaims.cs b/src/Ocelot/Authorisation/AddClaims.cs deleted file mode 100644 index 4e7f9227..00000000 --- a/src/Ocelot/Authorisation/AddClaims.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Claims.Parser; -using Ocelot.Configuration; -using Ocelot.HeaderBuilder; -using Ocelot.Responses; - -namespace Ocelot.Authorisation -{ - public class AddClaims : IAddHeadersToRequest - { - private readonly IClaimsParser _claimsParser; - - public AddClaims(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response SetHeadersOnContext(List configurationHeaderExtractorProperties, HttpContext context) - { - foreach (var config in configurationHeaderExtractorProperties) - { - var value = _claimsParser.GetValue(context.User.Claims, config.ClaimKey, config.Delimiter, config.Index); - - if (value.IsError) - { - return new ErrorResponse(value.Errors); - } - - var exists = context.Request.Headers.FirstOrDefault(x => x.Key == config.HeaderKey); - - if (!string.IsNullOrEmpty(exists.Key)) - { - context.Request.Headers.Remove(exists); - } - - context.Request.Headers.Add(config.HeaderKey, new StringValues(value.Data)); - } - - return new OkResponse(); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Authorisation/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/AuthorisationMiddleware.cs deleted file mode 100644 index 53ddb436..00000000 --- a/src/Ocelot/Authorisation/AuthorisationMiddleware.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Errors; -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.ScopedData; - -namespace Ocelot.Authorisation -{ - public class AuthorisationMiddleware : OcelotMiddleware - { - private readonly RequestDelegate _next; - private readonly IScopedRequestDataRepository _scopedRequestDataRepository; - private readonly IAuthoriser _authoriser; - - public AuthorisationMiddleware(RequestDelegate next, - IScopedRequestDataRepository scopedRequestDataRepository, - IAuthoriser authoriser) - : base(scopedRequestDataRepository) - { - _next = next; - _scopedRequestDataRepository = scopedRequestDataRepository; - _authoriser = authoriser; - } - - public async Task Invoke(HttpContext context) - { - var downstreamRoute = _scopedRequestDataRepository.Get("DownstreamRoute"); - - if (downstreamRoute.IsError) - { - SetPipelineError(downstreamRoute.Errors); - return; - } - - //todo - call authoriser - var authorised = new OkResponse(true); //_authoriser.Authorise(context.User, new RouteClaimsRequirement(new Dictionary())); - - if (authorised.IsError) - { - SetPipelineError(authorised.Errors); - return; - } - - if (authorised.Data) - { - await _next.Invoke(context); - } - else - { - SetPipelineError(new List - { - new UnauthorisedError($"{context.User.Identity.Name} unable to access {downstreamRoute.Data.ReRoute.UpstreamTemplate}") - }); - } - } - } -} diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 52409c82..cb7849e9 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using Ocelot.Claims.Parser; using Ocelot.Errors; using Ocelot.Responses; namespace Ocelot.Authorisation { + using Infrastructure.Claims.Parser; + public class ClaimsAuthoriser : IAuthoriser { private readonly IClaimsParser _claimsParser; @@ -16,9 +17,9 @@ namespace Ocelot.Authorisation _claimsParser = claimsParser; } - public Response Authorise(ClaimsPrincipal claimsPrincipal, RouteClaimsRequirement routeClaimsRequirement) + public Response Authorise(ClaimsPrincipal claimsPrincipal, Dictionary routeClaimsRequirement) { - foreach (var required in routeClaimsRequirement.RequiredClaimsAndValues) + foreach (var required in routeClaimsRequirement) { var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0); diff --git a/src/Ocelot/Authorisation/IAddClaims.cs b/src/Ocelot/Authorisation/IAddClaims.cs deleted file mode 100644 index d68b0750..00000000 --- a/src/Ocelot/Authorisation/IAddClaims.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Authorisation -{ - public interface IAddClaims - { - Response SetHeadersOnContext(List configurationHeaderExtractorProperties, - HttpContext context); - } -} diff --git a/src/Ocelot/Authorisation/IAuthoriser.cs b/src/Ocelot/Authorisation/IAuthoriser.cs index 96b1291f..08a7307f 100644 --- a/src/Ocelot/Authorisation/IAuthoriser.cs +++ b/src/Ocelot/Authorisation/IAuthoriser.cs @@ -3,9 +3,11 @@ using Ocelot.Responses; namespace Ocelot.Authorisation { + using System.Collections.Generic; + public interface IAuthoriser { - Response Authorise(ClaimsPrincipal claimsPrincipal, - RouteClaimsRequirement routeClaimsRequirement); + Response Authorise(ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs new file mode 100644 index 00000000..23c005b6 --- /dev/null +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -0,0 +1,66 @@ +namespace Ocelot.Authorisation.Middleware +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using DownstreamRouteFinder; + using Errors; + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using ScopedData; + + public class AuthorisationMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IScopedRequestDataRepository _scopedRequestDataRepository; + private readonly IAuthoriser _authoriser; + + public AuthorisationMiddleware(RequestDelegate next, + IScopedRequestDataRepository scopedRequestDataRepository, + IAuthoriser authoriser) + : base(scopedRequestDataRepository) + { + _next = next; + _scopedRequestDataRepository = scopedRequestDataRepository; + _authoriser = authoriser; + } + + public async Task Invoke(HttpContext context) + { + var downstreamRoute = _scopedRequestDataRepository.Get("DownstreamRoute"); + + if (downstreamRoute.IsError) + { + SetPipelineError(downstreamRoute.Errors); + return; + } + + if (downstreamRoute.Data.ReRoute.IsAuthorised) + { + var authorised = _authoriser.Authorise(context.User, downstreamRoute.Data.ReRoute.RouteClaimsRequirement); + + if (authorised.IsError) + { + SetPipelineError(authorised.Errors); + return; + } + + if (authorised.Data) + { + await _next.Invoke(context); + } + else + { + SetPipelineError(new List + { + new UnauthorisedError( + $"{context.User.Identity.Name} unable to access {downstreamRoute.Data.ReRoute.UpstreamTemplate}") + }); + } + } + else + { + await _next.Invoke(context); + } + } + } +} diff --git a/src/Ocelot/Authorisation/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs similarity index 77% rename from src/Ocelot/Authorisation/AuthorisationMiddlewareMiddlewareExtensions.cs rename to src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index 291a47ad..e2c8af2d 100644 --- a/src/Ocelot/Authorisation/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Builder; - -namespace Ocelot.Authorisation +namespace Ocelot.Authorisation.Middleware { + using Microsoft.AspNetCore.Builder; + public static class AuthorisationMiddlewareMiddlewareExtensions { public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) diff --git a/src/Ocelot/Authorisation/RouteClaimsRequirement.cs b/src/Ocelot/Authorisation/RouteClaimsRequirement.cs deleted file mode 100644 index 5cc0e412..00000000 --- a/src/Ocelot/Authorisation/RouteClaimsRequirement.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Ocelot.Authorisation -{ - public class RouteClaimsRequirement - { - public RouteClaimsRequirement(Dictionary requiredClaimsAndValues) - { - RequiredClaimsAndValues = requiredClaimsAndValues; - } - - public Dictionary RequiredClaimsAndValues { get; private set; } - } -} diff --git a/src/Ocelot/ClaimsBuilder/AddClaimsToRequest.cs b/src/Ocelot/ClaimsBuilder/AddClaimsToRequest.cs new file mode 100644 index 00000000..f5be6a9f --- /dev/null +++ b/src/Ocelot/ClaimsBuilder/AddClaimsToRequest.cs @@ -0,0 +1,46 @@ +namespace Ocelot.ClaimsBuilder +{ + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using Configuration; + using Infrastructure.Claims.Parser; + using Microsoft.AspNetCore.Http; + using Responses; + + public class AddClaimsToRequest : IAddClaimsToRequest + { + private readonly IClaimsParser _claimsParser; + + public AddClaimsToRequest(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response SetClaimsOnContext(List claimsToThings, HttpContext context) + { + foreach (var config in claimsToThings) + { + var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); + + if (value.IsError) + { + return new ErrorResponse(value.Errors); + } + + var exists = context.User.Claims.FirstOrDefault(x => x.Type == config.ExistingKey); + + var identity = context.User.Identity as ClaimsIdentity; + + if (exists != null) + { + identity?.RemoveClaim(exists); + } + + identity?.AddClaim(new Claim(config.ExistingKey, value.Data)); + } + + return new OkResponse(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ClaimsBuilder/IAddClaimsToRequest.cs b/src/Ocelot/ClaimsBuilder/IAddClaimsToRequest.cs new file mode 100644 index 00000000..9069bfe3 --- /dev/null +++ b/src/Ocelot/ClaimsBuilder/IAddClaimsToRequest.cs @@ -0,0 +1,13 @@ +namespace Ocelot.ClaimsBuilder +{ + using System.Collections.Generic; + using Configuration; + using Microsoft.AspNetCore.Http; + using Responses; + + public interface IAddClaimsToRequest + { + Response SetClaimsOnContext(List claimsToThings, + HttpContext context); + } +} diff --git a/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddleware.cs new file mode 100644 index 00000000..570706fa --- /dev/null +++ b/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddleware.cs @@ -0,0 +1,44 @@ +namespace Ocelot.ClaimsBuilder.Middleware +{ + using System.Linq; + using System.Threading.Tasks; + using DownstreamRouteFinder; + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using ScopedData; + + public class ClaimsBuilderMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddClaimsToRequest _addClaimsToRequest; + private readonly IScopedRequestDataRepository _scopedRequestDataRepository; + + public ClaimsBuilderMiddleware(RequestDelegate next, + IScopedRequestDataRepository scopedRequestDataRepository, + IAddClaimsToRequest addClaimsToRequest) + : base(scopedRequestDataRepository) + { + _next = next; + _addClaimsToRequest = addClaimsToRequest; + _scopedRequestDataRepository = scopedRequestDataRepository; + } + + public async Task Invoke(HttpContext context) + { + var downstreamRoute = _scopedRequestDataRepository.Get("DownstreamRoute"); + + if (downstreamRoute.Data.ReRoute.ClaimsToClaims.Any()) + { + var result = _addClaimsToRequest.SetClaimsOnContext(downstreamRoute.Data.ReRoute.ClaimsToClaims, context); + + if (result.IsError) + { + SetPipelineError(result.Errors); + return; + } + } + + await _next.Invoke(context); + } + } +} diff --git a/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddlewareExtensions.cs new file mode 100644 index 00000000..4da29e83 --- /dev/null +++ b/src/Ocelot/ClaimsBuilder/Middleware/ClaimsBuilderMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +namespace Ocelot.ClaimsBuilder.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class ClaimsBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseClaimsBuilderMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index e369d78d..db3aa617 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -15,7 +15,10 @@ namespace Ocelot.Configuration.Builder private List _additionalScopes; private bool _requireHttps; private string _scopeSecret; - private List _configHeaderExtractorProperties; + private List _configHeaderExtractorProperties; + private List _claimToClaims; + private Dictionary _routeClaimRequirement; + private bool _isAuthorised; public ReRouteBuilder() { @@ -48,8 +51,14 @@ namespace Ocelot.Configuration.Builder { _isAuthenticated = input; return this; - } + + public ReRouteBuilder WithIsAuthorised(bool input) + { + _isAuthorised = input; + return this; + } + public ReRouteBuilder WithAuthenticationProvider(string input) { _authenticationProvider = input; @@ -86,15 +95,27 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithConfigurationHeaderExtractorProperties(List input) + public ReRouteBuilder WithClaimsToHeaders(List input) { _configHeaderExtractorProperties = input; return this; } + public ReRouteBuilder WithClaimsToClaims(List input) + { + _claimToClaims = input; + return this; + } + + public ReRouteBuilder WithRouteClaimsRequirement(Dictionary input) + { + _routeClaimRequirement = input; + return this; + } + public ReRoute Build() { - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties); + return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised); } } } diff --git a/src/Ocelot/Configuration/ClaimToHeader.cs b/src/Ocelot/Configuration/ClaimToHeader.cs deleted file mode 100644 index dce96268..00000000 --- a/src/Ocelot/Configuration/ClaimToHeader.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ocelot.Configuration -{ - public class ClaimToHeader - { - public ClaimToHeader(string headerKey, string claimKey, string delimiter, int index) - { - ClaimKey = claimKey; - Delimiter = delimiter; - Index = index; - HeaderKey = headerKey; - } - - public string HeaderKey { get; private set; } - public string ClaimKey { get; private set; } - public string Delimiter { get; private set; } - public int Index { get; private set; } - } -} diff --git a/src/Ocelot/Configuration/ClaimToThing.cs b/src/Ocelot/Configuration/ClaimToThing.cs new file mode 100644 index 00000000..5a677667 --- /dev/null +++ b/src/Ocelot/Configuration/ClaimToThing.cs @@ -0,0 +1,18 @@ +namespace Ocelot.Configuration +{ + public class ClaimToThing + { + public ClaimToThing(string existingKey, string newKey, string delimiter, int index) + { + NewKey = newKey; + Delimiter = delimiter; + Index = index; + ExistingKey = existingKey; + } + + public string ExistingKey { get; private set; } + public string NewKey { get; private set; } + public string Delimiter { get; private set; } + public int Index { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Creator/YamlOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/YamlOcelotConfigurationCreator.cs index b4ff9d15..e0efd611 100644 --- a/src/Ocelot/Configuration/Creator/YamlOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/YamlOcelotConfigurationCreator.cs @@ -18,18 +18,18 @@ namespace Ocelot.Configuration.Creator private readonly IConfigurationValidator _configurationValidator; private const string RegExMatchEverything = ".*"; private const string RegExMatchEndString = "$"; - private readonly IClaimToHeaderConfigurationParser _claimToHeaderConfigurationParser; + private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly ILogger _logger; public YamlOcelotConfigurationCreator( IOptions options, IConfigurationValidator configurationValidator, - IClaimToHeaderConfigurationParser claimToHeaderConfigurationParser, + IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger logger) { _options = options; _configurationValidator = configurationValidator; - _claimToHeaderConfigurationParser = claimToHeaderConfigurationParser; + _claimToThingConfigurationParser = claimToThingConfigurationParser; _logger = logger; } @@ -89,6 +89,8 @@ namespace Ocelot.Configuration.Creator var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider); + var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0; + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, @@ -96,37 +98,38 @@ namespace Ocelot.Configuration.Creator reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes, reRoute.AuthenticationOptions.ScopeSecret); - var configHeaders = GetHeadersToAddToRequest(reRoute); + var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest); + var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, configHeaders + authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised ); } return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, - upstreamTemplate, isAuthenticated, null, new List()); + upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised); } - private List GetHeadersToAddToRequest(YamlReRoute reRoute) + private List GetAddThingsToRequest(Dictionary thingBeingAdded) { - var configHeaders = new List(); + var claimsToTHings = new List(); - foreach (var add in reRoute.AddHeadersToRequest) + foreach (var add in thingBeingAdded) { - var configurationHeader = _claimToHeaderConfigurationParser.Extract(add.Key, add.Value); + var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value); - if (configurationHeader.IsError) + if (claimToHeader.IsError) { _logger.LogCritical(new EventId(1, "Application Failed to start"), $"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect"); - throw new Exception(configurationHeader.Errors[0].Message); + throw new Exception(claimToHeader.Errors[0].Message); } - configHeaders.Add(configurationHeader.Data); + claimsToTHings.Add(claimToHeader.Data); } - return configHeaders; + return claimsToTHings; } private bool IsPlaceHolder(string upstreamTemplate, int i) diff --git a/src/Ocelot/Configuration/Parser/ClaimToHeaderConfigurationParser.cs b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs similarity index 78% rename from src/Ocelot/Configuration/Parser/ClaimToHeaderConfigurationParser.cs rename to src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs index 2d81ec1b..45dfe62d 100644 --- a/src/Ocelot/Configuration/Parser/ClaimToHeaderConfigurationParser.cs +++ b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs @@ -6,13 +6,13 @@ using Ocelot.Responses; namespace Ocelot.Configuration.Parser { - public class ClaimToHeaderConfigurationParser : IClaimToHeaderConfigurationParser + public class ClaimToThingConfigurationParser : IClaimToThingConfigurationParser { private readonly Regex _claimRegex = new Regex("Claims\\[.*\\]"); private readonly Regex _indexRegex = new Regex("value\\[.*\\]"); private const string SplitToken = ">"; - public Response Extract(string headerKey, string value) + public Response Extract(string existingKey, string value) { try { @@ -20,7 +20,7 @@ namespace Ocelot.Configuration.Parser if (instructions.Length <= 1) { - return new ErrorResponse( + return new ErrorResponse( new List { new NoInstructionsError(SplitToken) @@ -31,14 +31,14 @@ namespace Ocelot.Configuration.Parser if (!claimMatch) { - return new ErrorResponse( + return new ErrorResponse( new List { new InstructionNotForClaimsError() }); } - var claimKey = GetIndexValue(instructions[0]); + var newKey = GetIndexValue(instructions[0]); var index = 0; var delimiter = string.Empty; @@ -48,12 +48,12 @@ namespace Ocelot.Configuration.Parser delimiter = instructions[2].Trim(); } - return new OkResponse( - new ClaimToHeader(headerKey, claimKey, delimiter, index)); + return new OkResponse( + new ClaimToThing(existingKey, newKey, delimiter, index)); } catch (Exception exception) { - return new ErrorResponse( + return new ErrorResponse( new List { new ParsingConfigurationHeaderError(exception) diff --git a/src/Ocelot/Configuration/Parser/IClaimToHeaderConfigurationParser.cs b/src/Ocelot/Configuration/Parser/IClaimToHeaderConfigurationParser.cs deleted file mode 100644 index d45cd337..00000000 --- a/src/Ocelot/Configuration/Parser/IClaimToHeaderConfigurationParser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Ocelot.Responses; - -namespace Ocelot.Configuration.Parser -{ - public interface IClaimToHeaderConfigurationParser - { - Response Extract(string headerKey, string value); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs new file mode 100644 index 00000000..a2712994 --- /dev/null +++ b/src/Ocelot/Configuration/Parser/IClaimToThingConfigurationParser.cs @@ -0,0 +1,9 @@ +using Ocelot.Responses; + +namespace Ocelot.Configuration.Parser +{ + public interface IClaimToThingConfigurationParser + { + Response Extract(string existingKey, string value); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 8a67de84..0a8b7ce5 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -4,7 +4,7 @@ namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties) + public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised) { DownstreamTemplate = downstreamTemplate; UpstreamTemplate = upstreamTemplate; @@ -12,8 +12,12 @@ namespace Ocelot.Configuration UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; AuthenticationOptions = authenticationOptions; + RouteClaimsRequirement = routeClaimsRequirement; + IsAuthorised = isAuthorised; + ClaimsToClaims = claimsToClaims + ?? new List(); ClaimsToHeaders = configurationHeaderExtractorProperties - ?? new List(); + ?? new List(); } public string DownstreamTemplate { get; private set; } @@ -21,7 +25,11 @@ namespace Ocelot.Configuration public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } public AuthenticationOptions AuthenticationOptions { get; private set; } - public List ClaimsToHeaders { get; private set; } + public List ClaimsToHeaders { get; private set; } + public List ClaimsToClaims { get; private set; } + public Dictionary RouteClaimsRequirement { get; private set; } + } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Yaml/YamlReRoute.cs b/src/Ocelot/Configuration/Yaml/YamlReRoute.cs index 94daa4a8..f3297c4c 100644 --- a/src/Ocelot/Configuration/Yaml/YamlReRoute.cs +++ b/src/Ocelot/Configuration/Yaml/YamlReRoute.cs @@ -7,7 +7,8 @@ namespace Ocelot.Configuration.Yaml public YamlReRoute() { AddHeadersToRequest = new Dictionary(); - AddClaims = new Dictionary(); + AddClaimsToRequest = new Dictionary(); + RouteClaimsRequirement = new Dictionary(); } public string DownstreamTemplate { get; set; } @@ -15,6 +16,7 @@ namespace Ocelot.Configuration.Yaml public string UpstreamHttpMethod { get; set; } public YamlAuthenticationOptions AuthenticationOptions { get; set; } public Dictionary AddHeadersToRequest { get; set; } - public Dictionary AddClaims { get; set; } + public Dictionary AddClaimsToRequest { get; set; } + public Dictionary RouteClaimsRequirement { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 87c5bb79..6ed8a88d 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; -using Ocelot.Claims.Parser; using Ocelot.Configuration.Creator; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Provider; @@ -23,6 +22,9 @@ using Ocelot.ScopedData; namespace Ocelot.DependencyInjection { + using ClaimsBuilder; + using Infrastructure.Claims.Parser; + public static class ServiceCollectionExtensions { public static IServiceCollection AddOcelotYamlConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) @@ -34,7 +36,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); return services; @@ -48,6 +50,7 @@ namespace Ocelot.DependencyInjection // ocelot services. services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/HeaderBuilder/AddHeadersToRequest.cs b/src/Ocelot/HeaderBuilder/AddHeadersToRequest.cs index cd96dccf..57ff4103 100644 --- a/src/Ocelot/HeaderBuilder/AddHeadersToRequest.cs +++ b/src/Ocelot/HeaderBuilder/AddHeadersToRequest.cs @@ -2,12 +2,13 @@ using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -using Ocelot.Claims.Parser; using Ocelot.Configuration; using Ocelot.Responses; namespace Ocelot.HeaderBuilder { + using Infrastructure.Claims.Parser; + public class AddHeadersToRequest : IAddHeadersToRequest { private readonly IClaimsParser _claimsParser; @@ -17,25 +18,25 @@ namespace Ocelot.HeaderBuilder _claimsParser = claimsParser; } - public Response SetHeadersOnContext(List configurationHeaderExtractorProperties, HttpContext context) + public Response SetHeadersOnContext(List claimsToThings, HttpContext context) { - foreach (var config in configurationHeaderExtractorProperties) + foreach (var config in claimsToThings) { - var value = _claimsParser.GetValue(context.User.Claims, config.ClaimKey, config.Delimiter, config.Index); + var value = _claimsParser.GetValue(context.User.Claims, config.NewKey, config.Delimiter, config.Index); if (value.IsError) { return new ErrorResponse(value.Errors); } - var exists = context.Request.Headers.FirstOrDefault(x => x.Key == config.HeaderKey); + var exists = context.Request.Headers.FirstOrDefault(x => x.Key == config.ExistingKey); if (!string.IsNullOrEmpty(exists.Key)) { context.Request.Headers.Remove(exists); } - context.Request.Headers.Add(config.HeaderKey, new StringValues(value.Data)); + context.Request.Headers.Add(config.ExistingKey, new StringValues(value.Data)); } return new OkResponse(); diff --git a/src/Ocelot/HeaderBuilder/IAddHeadersToRequest.cs b/src/Ocelot/HeaderBuilder/IAddHeadersToRequest.cs index 3c5cf49c..f12709ca 100644 --- a/src/Ocelot/HeaderBuilder/IAddHeadersToRequest.cs +++ b/src/Ocelot/HeaderBuilder/IAddHeadersToRequest.cs @@ -7,7 +7,7 @@ namespace Ocelot.HeaderBuilder { public interface IAddHeadersToRequest { - Response SetHeadersOnContext(List configurationHeaderExtractorProperties, + Response SetHeadersOnContext(List claimsToThings, HttpContext context); } } diff --git a/src/Ocelot/Claims/Parser/CannotFindClaimError.cs b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs similarity index 75% rename from src/Ocelot/Claims/Parser/CannotFindClaimError.cs rename to src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs index 37e926e1..64a91cb8 100644 --- a/src/Ocelot/Claims/Parser/CannotFindClaimError.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs @@ -1,7 +1,7 @@ -using Ocelot.Errors; - -namespace Ocelot.Claims.Parser +namespace Ocelot.Infrastructure.Claims.Parser { + using Errors; + public class CannotFindClaimError : Error { public CannotFindClaimError(string message) diff --git a/src/Ocelot/Claims/Parser/ClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs similarity index 88% rename from src/Ocelot/Claims/Parser/ClaimsParser.cs rename to src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs index a6c8d010..e37f48fa 100644 --- a/src/Ocelot/Claims/Parser/ClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Claims.Parser +namespace Ocelot.Infrastructure.Claims.Parser { + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using Errors; + using Responses; + public class ClaimsParser : IClaimsParser { public Response GetValue(IEnumerable claims, string key, string delimiter, int index) diff --git a/src/Ocelot/Claims/Parser/IClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs similarity index 52% rename from src/Ocelot/Claims/Parser/IClaimsParser.cs rename to src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs index 4a2020ec..fa94cd22 100644 --- a/src/Ocelot/Claims/Parser/IClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/IClaimsParser.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Ocelot.Responses; - -namespace Ocelot.Claims.Parser +namespace Ocelot.Infrastructure.Claims.Parser { + using System.Collections.Generic; + using System.Security.Claims; + using Responses; + public interface IClaimsParser { Response GetValue(IEnumerable claims, string key, string delimiter, int index); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9815388f..866ac4e8 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Builder; using Ocelot.Authentication.Middleware; -using Ocelot.Authorisation; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.HeaderBuilder.Middleware; @@ -10,6 +9,9 @@ using Ocelot.Responder.Middleware; namespace Ocelot.Middleware { + using Authorisation.Middleware; + using ClaimsBuilder.Middleware; + public static class OcelotMiddlewareExtensions { public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) @@ -20,6 +22,8 @@ namespace Ocelot.Middleware builder.UseAuthenticationMiddleware(); + builder.UseClaimsBuilderMiddleware(); + builder.UseAuthorisationMiddleware(); builder.UseHttpRequestHeadersBuilderMiddleware(); diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index d7ab7090..6ecc20eb 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -14,6 +14,14 @@ namespace Ocelot.Responder return new OkResponse(401); } + if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError + || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError + || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError + || e.Code == OcelotErrorCode.CannotFindClaimError)) + { + return new OkResponse(403); + } + return new OkResponse(404); } } diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 7a1ec440..2bb3a908 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -74,11 +74,15 @@ namespace Ocelot.AcceptanceTests {"UserType", "Claims[sub] > value[0] > |"}, {"UserId", "Claims[sub] > value[1] > |"} }, - AddClaims = + AddClaimsToRequest = { {"CustomerId", "Claims[CustomerId] > value"}, {"UserType", "Claims[sub] > value[0] > |"}, {"UserId", "Claims[sub] > value[1] > |"} + }, + RouteClaimsRequirement = + { + {"UserType", "registered"} } } } @@ -91,26 +95,66 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_403_authorising_route() + { + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura")) + .And(x => x.GivenIHaveAToken("http://localhost:51888")) + .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://localhost:51876/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + AuthenticationOptions = new YamlAuthenticationOptions + { + AdditionalScopes = new List(), + Provider = "IdentityServer", + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ScopeName = "api", + ScopeSecret = "secret" + }, + 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"} + } + } + } + })) + .And(x => x.GivenTheApiGatewayIsRunning()) + .And(x => x.GivenIHaveAddedATokenToMyRequest()) + .When(x => x.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden)) + .BDDfy(); + } + private void WhenIGetUrlOnTheApiGateway(string url) { _response = _ocelotClient.GetAsync(url).Result; } - private void WhenIPostUrlOnTheApiGateway(string url) - { - _response = _ocelotClient.PostAsync(url, _postContent).Result; - } - private void ThenTheResponseBodyShouldBe(string expectedBody) { _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); } - private void GivenThePostHasContent(string postcontent) - { - _postContent = new StringContent(postcontent); - } - /// /// This is annoying cos it should be in the constructor but we need to set up the yaml file before calling startup so its a step. /// @@ -184,7 +228,8 @@ namespace Ocelot.AcceptanceTests { Value = "secret".Sha256() } - } + }, + IncludeAllClaimsForUser = true }, StandardScopes.OpenId, diff --git a/test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs similarity index 89% rename from test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs rename to test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index d747a41c..134a33b8 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -18,7 +18,9 @@ using Xunit; namespace Ocelot.UnitTests.Authorization { - public class AuthorizationMiddlewareTests : IDisposable + using Authorisation.Middleware; + + public class AuthorisationMiddlewareTests : IDisposable { private readonly Mock _scopedRepository; private readonly Mock _authService; @@ -28,7 +30,7 @@ namespace Ocelot.UnitTests.Authorization private HttpResponseMessage _result; private OkResponse _downstreamRoute; - public AuthorizationMiddlewareTests() + public AuthorisationMiddlewareTests() { _url = "http://localhost:51879"; _scopedRepository = new Mock(); @@ -56,18 +58,17 @@ namespace Ocelot.UnitTests.Authorization [Fact] public void happy_path() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithIsAuthorised(true).Build()))) .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) .When(x => x.WhenICallTheMiddleware()) - //todo stick this back in - //.Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) + .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) .BDDfy(); } private void GivenTheAuthServiceReturns(Response expected) { _authService - .Setup(x => x.Authorise(It.IsAny(), It.IsAny())) + .Setup(x => x.Authorise(It.IsAny(), It.IsAny>())) .Returns(expected); } @@ -75,7 +76,7 @@ namespace Ocelot.UnitTests.Authorization { _authService .Verify(x => x.Authorise(It.IsAny(), - It.IsAny()), Times.Once); + It.IsAny>()), Times.Once); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) diff --git a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs index 15dde43c..7b3779a6 100644 --- a/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Authorization/ClaimsAuthoriserTests.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Security.Claims; using Ocelot.Authorisation; -using Ocelot.Claims.Parser; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -9,11 +8,13 @@ using Xunit; namespace Ocelot.UnitTests.Authorization { + using Ocelot.Infrastructure.Claims.Parser; + public class ClaimsAuthoriserTests { private readonly ClaimsAuthoriser _claimsAuthoriser; private ClaimsPrincipal _claimsPrincipal; - private RouteClaimsRequirement _requirement; + private Dictionary _requirement; private Response _result; public ClaimsAuthoriserTests() @@ -28,10 +29,10 @@ namespace Ocelot.UnitTests.Authorization { new Claim("UserType", "registered") })))) - .And(x => x.GivenARouteClaimsRequirement(new RouteClaimsRequirement(new Dictionary + .And(x => x.GivenARouteClaimsRequirement(new Dictionary { {"UserType", "registered"} - }))) + })) .When(x => x.WhenICallTheAuthoriser()) .Then(x => x.ThenTheUserIsAuthorised()) .BDDfy(); @@ -41,10 +42,10 @@ namespace Ocelot.UnitTests.Authorization public void should_not_authorise_user() { this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List())))) - .And(x => x.GivenARouteClaimsRequirement(new RouteClaimsRequirement(new Dictionary + .And(x => x.GivenARouteClaimsRequirement(new Dictionary { { "UserType", "registered" } - }))) + })) .When(x => x.WhenICallTheAuthoriser()) .Then(x => x.ThenTheUserIsntAuthorised()) .BDDfy(); @@ -55,7 +56,7 @@ namespace Ocelot.UnitTests.Authorization _claimsPrincipal = claimsPrincipal; } - private void GivenARouteClaimsRequirement(RouteClaimsRequirement requirement) + private void GivenARouteClaimsRequirement(Dictionary requirement) { _requirement = requirement; } diff --git a/test/Ocelot.UnitTests/ClaimsBuilder/AddClaimsToRequestTests.cs b/test/Ocelot.UnitTests/ClaimsBuilder/AddClaimsToRequestTests.cs new file mode 100644 index 00000000..6e4d5339 --- /dev/null +++ b/test/Ocelot.UnitTests/ClaimsBuilder/AddClaimsToRequestTests.cs @@ -0,0 +1,145 @@ +namespace Ocelot.UnitTests.ClaimsBuilder +{ + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using Errors; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.ClaimsBuilder; + using Ocelot.Configuration; + using Ocelot.Infrastructure.Claims.Parser; + using Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class AddClaimsToRequestTests + { + private readonly AddClaimsToRequest _addClaimsToRequest; + private readonly Mock _parser; + private List _claimsToThings; + private HttpContext _context; + private Response _result; + private Response _claimValue; + + public AddClaimsToRequestTests() + { + _parser = new Mock(); + _addClaimsToRequest = new AddClaimsToRequest(_parser.Object); + } + + [Fact] + public void should_add_claims_to_context() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("test", "data") + })) + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("claim-key", "", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void if_claims_exists_should_replace_it() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("existing-key", "data"), + new Claim("new-key", "data") + })), + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("existing-key", "new-key", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenHttpContext(new DefaultHttpContext())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + + private void GivenClaimsToThings(List configuration) + { + _claimsToThings = configuration; + } + + private void GivenHttpContext(HttpContext context) + { + _context = context; + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddClaimsToTheRequest() + { + _result = _addClaimsToRequest.SetClaimsOnContext(_claimsToThings, _context); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + + _result.IsError.ShouldBe(true); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/ClaimsBuilder/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/ClaimsBuilder/ClaimsBuilderMiddlewareTests.cs new file mode 100644 index 00000000..210bca83 --- /dev/null +++ b/test/Ocelot.UnitTests/ClaimsBuilder/ClaimsBuilderMiddlewareTests.cs @@ -0,0 +1,111 @@ +namespace Ocelot.UnitTests.ClaimsBuilder +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net.Http; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.ClaimsBuilder; + using Ocelot.ClaimsBuilder.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Responses; + using ScopedData; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsBuilderMiddlewareTests : IDisposable + { + private readonly Mock _scopedRepository; + private readonly Mock _addHeaders; + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private Response _downstreamRoute; + private HttpResponseMessage _result; + + public ClaimsBuilderMiddlewareTests() + { + _url = "http://localhost:51879"; + _scopedRepository = new Mock(); + _addHeaders = new Mock(); + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddSingleton(_addHeaders.Object); + x.AddSingleton(_scopedRepository.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseClaimsBuilderMiddleware(); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + [Fact] + public void happy_path() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamTemplate("any old string") + .WithClaimsToClaims(new List + { + new ClaimToThing("sub", "UserType", "|", 0) + }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddClaimsToRequestReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheAddClaimsToRequestReturns() + { + _addHeaders + .Setup(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheClaimsToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny()), Times.Once); + } + + private void WhenICallTheMiddleware() + { + _result = _client.GetAsync(_url).Result; + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + public void Dispose() + { + _client.Dispose(); + _server.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationHeadersExtractorTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs similarity index 77% rename from test/Ocelot.UnitTests/Configuration/ConfigurationHeadersExtractorTests.cs rename to test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs index 7b2f37c1..1f3ba9bc 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationHeadersExtractorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ClaimToThingConfigurationParserTests.cs @@ -10,15 +10,15 @@ using Xunit; namespace Ocelot.UnitTests.Configuration { - public class ConfigurationHeadersExtractorTests + public class ClaimToThingConfigurationParserTests { private Dictionary _dictionary; - private readonly IClaimToHeaderConfigurationParser _claimToHeaderConfigurationParser; - private Response _result; + private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; + private Response _result; - public ConfigurationHeadersExtractorTests() + public ClaimToThingConfigurationParserTests() { - _claimToHeaderConfigurationParser = new ClaimToHeaderConfigurationParser(); + _claimToThingConfigurationParser = new ClaimToThingConfigurationParser(); } [Fact] @@ -31,7 +31,7 @@ namespace Ocelot.UnitTests.Configuration .When(x => x.WhenICallTheExtractor()) .Then( x => - x.ThenAnErrorIsReturned(new ErrorResponse( + x.ThenAnErrorIsReturned(new ErrorResponse( new List { new NoInstructionsError(">") @@ -49,7 +49,7 @@ namespace Ocelot.UnitTests.Configuration .When(x => x.WhenICallTheExtractor()) .Then( x => - x.ThenAnErrorIsReturned(new ErrorResponse( + x.ThenAnErrorIsReturned(new ErrorResponse( new List { new InstructionNotForClaimsError() @@ -68,8 +68,8 @@ namespace Ocelot.UnitTests.Configuration .Then( x => x.ThenTheClaimParserPropertiesAreReturned( - new OkResponse( - new ClaimToHeader("CustomerId", "CustomerId", "", 0)))) + new OkResponse( + new ClaimToThing("CustomerId", "CustomerId", "", 0)))) .BDDfy(); } @@ -84,20 +84,20 @@ namespace Ocelot.UnitTests.Configuration .Then( x => x.ThenTheClaimParserPropertiesAreReturned( - new OkResponse( - new ClaimToHeader("UserId", "Subject", "|", 0)))) + new OkResponse( + new ClaimToThing("UserId", "Subject", "|", 0)))) .BDDfy(); } - private void ThenAnErrorIsReturned(Response expected) + private void ThenAnErrorIsReturned(Response expected) { _result.IsError.ShouldBe(expected.IsError); _result.Errors[0].ShouldBeOfType(expected.Errors[0].GetType()); } - private void ThenTheClaimParserPropertiesAreReturned(Response expected) + private void ThenTheClaimParserPropertiesAreReturned(Response expected) { - _result.Data.ClaimKey.ShouldBe(expected.Data.ClaimKey); + _result.Data.NewKey.ShouldBe(expected.Data.NewKey); _result.Data.Delimiter.ShouldBe(expected.Data.Delimiter); _result.Data.Index.ShouldBe(expected.Data.Index); _result.IsError.ShouldBe(expected.IsError); @@ -106,7 +106,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenICallTheExtractor() { var first = _dictionary.First(); - _result = _claimToHeaderConfigurationParser.Extract(first.Key, first.Value); + _result = _claimToThingConfigurationParser.Extract(first.Key, first.Value); } private void GivenTheDictionaryIs(Dictionary dictionary) diff --git a/test/Ocelot.UnitTests/Configuration/YamlConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/YamlConfigurationCreatorTests.cs index fd69ba5e..36acefc2 100644 --- a/test/Ocelot.UnitTests/Configuration/YamlConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/YamlConfigurationCreatorTests.cs @@ -21,14 +21,14 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _validator; private Response _config; private YamlConfiguration _yamlConfiguration; - private readonly Mock _configParser; + private readonly Mock _configParser; private readonly Mock> _logger; private readonly YamlOcelotConfigurationCreator _ocelotConfigurationCreator; public YamlConfigurationCreatorTests() { _logger = new Mock>(); - _configParser = new Mock(); + _configParser = new Mock(); _validator = new Mock(); _yamlConfig = new Mock>(); _ocelotConfigurationCreator = new YamlOcelotConfigurationCreator( @@ -79,9 +79,9 @@ namespace Ocelot.UnitTests.Configuration .WithRequireHttps(false) .WithScopeSecret("secret") .WithAuthenticationProviderScopeName("api") - .WithConfigurationHeaderExtractorProperties(new List + .WithClaimsToHeaders(new List { - new ClaimToHeader("CustomerId", "CustomerId", "", 0), + new ClaimToThing("CustomerId", "CustomerId", "", 0), }) .Build() }; @@ -112,18 +112,18 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheYamlConfigIsValid()) - .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToHeader("CustomerId", "CustomerId", "", 0))) + .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) .BDDfy(); } - private void GivenTheConfigHeaderExtractorReturns(ClaimToHeader expected) + private void GivenTheConfigHeaderExtractorReturns(ClaimToThing expected) { _configParser .Setup(x => x.Extract(It.IsAny(), It.IsAny())) - .Returns(new OkResponse(expected)); + .Returns(new OkResponse(expected)); } [Fact] diff --git a/test/Ocelot.UnitTests/HeaderBuilder/AddHeadersToRequestTests.cs b/test/Ocelot.UnitTests/HeaderBuilder/AddHeadersToRequestTests.cs index 59c111f2..fb12dba4 100644 --- a/test/Ocelot.UnitTests/HeaderBuilder/AddHeadersToRequestTests.cs +++ b/test/Ocelot.UnitTests/HeaderBuilder/AddHeadersToRequestTests.cs @@ -4,7 +4,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Moq; -using Ocelot.Claims.Parser; using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.HeaderBuilder; @@ -15,11 +14,13 @@ using Xunit; namespace Ocelot.UnitTests.HeaderBuilder { + using Ocelot.Infrastructure.Claims.Parser; + public class AddHeadersToRequestTests { private readonly AddHeadersToRequest _addHeadersToRequest; private readonly Mock _parser; - private List _configuration; + private List _configuration; private HttpContext _context; private Response _result; private Response _claimValue; @@ -42,9 +43,9 @@ namespace Ocelot.UnitTests.HeaderBuilder }; this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List + x => x.GivenConfigurationHeaderExtractorProperties(new List { - new ClaimToHeader("header-key", "", "", 0) + new ClaimToThing("header-key", "", "", 0) })) .Given(x => x.GivenHttpContext(context)) .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) @@ -68,9 +69,9 @@ namespace Ocelot.UnitTests.HeaderBuilder context.Request.Headers.Add("header-key", new StringValues("initial")); this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List + x => x.GivenConfigurationHeaderExtractorProperties(new List { - new ClaimToHeader("header-key", "", "", 0) + new ClaimToThing("header-key", "", "", 0) })) .Given(x => x.GivenHttpContext(context)) .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) @@ -84,9 +85,9 @@ namespace Ocelot.UnitTests.HeaderBuilder public void should_return_error() { this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List + x => x.GivenConfigurationHeaderExtractorProperties(new List { - new ClaimToHeader("", "", "", 0) + new ClaimToThing("", "", "", 0) })) .Given(x => x.GivenHttpContext(new DefaultHttpContext())) .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List @@ -104,7 +105,7 @@ namespace Ocelot.UnitTests.HeaderBuilder header.Value.First().ShouldBe(_claimValue.Data); } - private void GivenConfigurationHeaderExtractorProperties(List configuration) + private void GivenConfigurationHeaderExtractorProperties(List configuration) { _configuration = configuration; } diff --git a/test/Ocelot.UnitTests/HeaderBuilder/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/HeaderBuilder/HttpRequestHeadersBuilderMiddlewareTests.cs index 8fbd7993..6d28f64c 100644 --- a/test/Ocelot.UnitTests/HeaderBuilder/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/HeaderBuilder/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -61,9 +61,9 @@ namespace Ocelot.UnitTests.HeaderBuilder var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamTemplate("any old string") - .WithConfigurationHeaderExtractorProperties(new List + .WithClaimsToHeaders(new List { - new ClaimToHeader("UserId", "Subject", "", 0) + new ClaimToThing("UserId", "Subject", "", 0) }) .Build()); @@ -77,7 +77,7 @@ namespace Ocelot.UnitTests.HeaderBuilder private void GivenTheAddHeadersToRequestReturns(string claimValue) { _addHeaders - .Setup(x => x.SetHeadersOnContext(It.IsAny>(), + .Setup(x => x.SetHeadersOnContext(It.IsAny>(), It.IsAny())) .Returns(new OkResponse()); } @@ -85,7 +85,7 @@ namespace Ocelot.UnitTests.HeaderBuilder private void ThenTheAddHeadersToRequestIsCalledCorrectly() { _addHeaders - .Verify(x => x.SetHeadersOnContext(It.IsAny>(), + .Verify(x => x.SetHeadersOnContext(It.IsAny>(), It.IsAny()), Times.Once); } diff --git a/test/Ocelot.UnitTests/HeaderBuilder/ClaimParserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs similarity index 93% rename from test/Ocelot.UnitTests/HeaderBuilder/ClaimParserTests.cs rename to test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs index 189508d8..5683d636 100644 --- a/test/Ocelot.UnitTests/HeaderBuilder/ClaimParserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; -using System.Security.Claims; -using Ocelot.Claims.Parser; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.HeaderBuilder +namespace Ocelot.UnitTests.Infrastructure { + using System.Collections.Generic; + using System.Security.Claims; + using Errors; + using Ocelot.Infrastructure.Claims.Parser; + using Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class ClaimParserTests { private readonly IClaimsParser _claimsParser;