started adding authentication stack, thing that decides if we should be authenticated is in

This commit is contained in:
tom.pallister 2016-10-12 13:40:46 +01:00
parent 1fddcf0836
commit 58393f07ec
15 changed files with 241 additions and 13 deletions

View File

@ -0,0 +1,11 @@
namespace Ocelot.Library.Infrastructure.Authentication
{
using Responses;
public class CouldNotFindConfigurationError : Error
{
public CouldNotFindConfigurationError(string message)
: base(message)
{
}
}
}

View File

@ -0,0 +1,10 @@
namespace Ocelot.Library.Infrastructure.Authentication
{
using DownstreamRouteFinder;
using Responses;
public interface IRouteRequiresAuthentication
{
Response<bool> IsAuthenticated(DownstreamRoute downstreamRoute, string httpMethod);
}
}

View File

@ -0,0 +1,35 @@
namespace Ocelot.Library.Infrastructure.Authentication
{
using System;
using System.Collections.Generic;
using System.Linq;
using Configuration;
using DownstreamRouteFinder;
using Responses;
public class RouteRequiresAuthentication : IRouteRequiresAuthentication
{
private readonly IOcelotConfiguration _configuration;
public RouteRequiresAuthentication(IOcelotConfiguration configuration)
{
_configuration = configuration;
}
public Response<bool> IsAuthenticated(DownstreamRoute downstreamRoute, string httpMethod)
{
var reRoute =
_configuration.ReRoutes.FirstOrDefault(
x =>
x.DownstreamTemplate == downstreamRoute.DownstreamUrlTemplate &&
string.Equals(x.UpstreamHttpMethod, httpMethod, StringComparison.CurrentCultureIgnoreCase));
if (reRoute == null)
{
return new ErrorResponse<bool>(new List<Error> {new CouldNotFindConfigurationError($"Could not find configuration for {downstreamRoute.DownstreamUrlTemplate} using method {httpMethod}")});
}
return new OkResponse<bool>(reRoute.IsAuthenticated);
}
}
}

View File

@ -43,7 +43,9 @@ namespace Ocelot.Library.Infrastructure.Configuration
upstreamTemplate = $"{upstreamTemplate}{RegExMatchEndString}"; upstreamTemplate = $"{upstreamTemplate}{RegExMatchEndString}";
_reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate)); var isAuthenticated = !string.IsNullOrEmpty(reRoute.Authentication);
_reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated));
} }
} }

View File

@ -2,17 +2,19 @@
{ {
public class ReRoute public class ReRoute
{ {
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern) public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated)
{ {
DownstreamTemplate = downstreamTemplate; DownstreamTemplate = downstreamTemplate;
UpstreamTemplate = upstreamTemplate; UpstreamTemplate = upstreamTemplate;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern; UpstreamTemplatePattern = upstreamTemplatePattern;
IsAuthenticated = isAuthenticated;
} }
public string DownstreamTemplate { get; private set; } public string DownstreamTemplate { get; private set; }
public string UpstreamTemplate { get; private set; } public string UpstreamTemplate { get; private set; }
public string UpstreamTemplatePattern { get; private set; } public string UpstreamTemplatePattern { get; private set; }
public string UpstreamHttpMethod { get; private set; } public string UpstreamHttpMethod { get; private set; }
public bool IsAuthenticated { get; private set; }
} }
} }

View File

@ -5,5 +5,6 @@
public string DownstreamTemplate { get; set; } public string DownstreamTemplate { get; set; }
public string UpstreamTemplate { get; set; } public string UpstreamTemplate { get; set; }
public string UpstreamHttpMethod { get; set; } public string UpstreamHttpMethod { get; set; }
public string Authentication { get; set; }
} }
} }

View File

@ -0,0 +1,54 @@
namespace Ocelot.Library.Middleware
{
using System.Threading.Tasks;
using Infrastructure.Authentication;
using Infrastructure.DownstreamRouteFinder;
using Infrastructure.Repository;
using Infrastructure.Responses;
using Microsoft.AspNetCore.Http;
public class AuthenticationMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IScopedRequestDataRepository _scopedRequestDataRepository;
private readonly IRouteRequiresAuthentication _requiresAuthentication;
public AuthenticationMiddleware(RequestDelegate next,
IScopedRequestDataRepository scopedRequestDataRepository,
IRouteRequiresAuthentication requiresAuthentication)
: base(scopedRequestDataRepository)
{
_next = next;
_scopedRequestDataRepository = scopedRequestDataRepository;
_requiresAuthentication = requiresAuthentication;
}
public async Task Invoke(HttpContext context)
{
var downstreamRoute = _scopedRequestDataRepository.Get<DownstreamRoute>("DownstreamRoute");
var isAuthenticated = _requiresAuthentication.IsAuthenticated(downstreamRoute.Data, context.Request.Method);
if (isAuthenticated.IsError)
{
SetPipelineError(downstreamRoute.Errors);
return;
}
if (IsAuthenticatedRoute(isAuthenticated))
{
//todo - build auth pipeline and then call normal pipeline if all good?
await _next.Invoke(context);
}
else
{
await _next.Invoke(context);
}
}
private static bool IsAuthenticatedRoute(Response<bool> isAuthenticated)
{
return isAuthenticated.Data;
}
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Builder;
namespace Ocelot.Library.Middleware
{
public static class AuthenticationMiddlewareMiddlewareExtensions
{
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuthenticationMiddleware>();
}
}
}

View File

@ -2,7 +2,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
using Ocelot.Library.Infrastructure.Repository; using Ocelot.Library.Infrastructure.Repository;
using Ocelot.Library.Infrastructure.Responder;
using Ocelot.Library.Infrastructure.UrlTemplateReplacer; using Ocelot.Library.Infrastructure.UrlTemplateReplacer;
namespace Ocelot.Library.Middleware namespace Ocelot.Library.Middleware

View File

@ -4,7 +4,7 @@ namespace Ocelot.Library.Middleware
{ {
public static class DownstreamUrlCreatorMiddlewareExtensions public static class DownstreamUrlCreatorMiddlewareExtensions
{ {
public static IApplicationBuilder UserDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
{ {
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>(); return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
} }

View File

@ -20,6 +20,8 @@ using Ocelot.Library.Middleware;
namespace Ocelot namespace Ocelot
{ {
using Library.Infrastructure.Authentication;
public class Startup public class Startup
{ {
public Startup(IHostingEnvironment env) public Startup(IHostingEnvironment env)
@ -52,6 +54,7 @@ namespace Ocelot
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>(); services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
services.AddSingleton<IHttpResponder, HttpContextResponder>(); services.AddSingleton<IHttpResponder, HttpContextResponder>();
services.AddSingleton<IRequestBuilder, HttpRequestBuilder>(); services.AddSingleton<IRequestBuilder, HttpRequestBuilder>();
services.AddSingleton<IRouteRequiresAuthentication, RouteRequiresAuthentication>();
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -69,7 +72,9 @@ namespace Ocelot
app.UseDownstreamRouteFinderMiddleware(); app.UseDownstreamRouteFinderMiddleware();
app.UserDownstreamUrlCreatorMiddleware(); app.UseAuthenticationMiddleware();
app.UseDownstreamUrlCreatorMiddleware();
app.UseHttpRequestBuilderMiddleware(); app.UseHttpRequestBuilderMiddleware();

View File

@ -2,3 +2,4 @@ ReRoutes:
- DownstreamTemplate: http://localhost:51879/ - DownstreamTemplate: http://localhost:51879/
UpstreamTemplate: / UpstreamTemplate: /
UpstreamHttpMethod: Get UpstreamHttpMethod: Get
Authentication: IdentityServer

View File

@ -0,0 +1,96 @@
namespace Ocelot.UnitTests.Authentication
{
using System.Collections.Generic;
using Library.Infrastructure.Authentication;
using Library.Infrastructure.Configuration;
using Library.Infrastructure.DownstreamRouteFinder;
using Library.Infrastructure.Responses;
using Library.Infrastructure.UrlMatcher;
using Moq;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
public class RequiresAuthenticationTests
{
private readonly RouteRequiresAuthentication _routeRequiresAuthentication;
private string _url;
private readonly Mock<IOcelotConfiguration> _config;
private Response<bool> _result;
private string _httpMethod;
public RequiresAuthenticationTests()
{
_config = new Mock<IOcelotConfiguration>();
_routeRequiresAuthentication = new RouteRequiresAuthentication(_config.Object);
}
[Fact]
public void should_return_true_if_route_requires_authentication()
{
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
.And(
x =>
x.GivenTheConfigurationForTheRouteIs(new ReRoute("http://www.bbc.co.uk", "/api/poo", "get",
"/api/poo$", true)))
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
.Then(x => x.ThenTheResultIs(true))
.BDDfy();
}
[Fact]
public void should_return_false_if_route_requires_authentication()
{
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
.And(
x =>
x.GivenTheConfigurationForTheRouteIs(new ReRoute("http://www.bbc.co.uk", "/api/poo", "get",
"/api/poo$", false)))
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
.Then(x => x.ThenTheResultIs(false))
.BDDfy();
}
[Fact]
public void should_return_error_if_no_matching_config()
{
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
.And(x => x.GivenTheConfigurationForTheRouteIs(new ReRoute(string.Empty, string.Empty, string.Empty, string.Empty,false)))
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
.Then(x => x.ThenAnErrorIsReturned())
.BDDfy();
}
private void ThenAnErrorIsReturned()
{
_result.IsError.ShouldBeTrue();
}
public void GivenIHaveADownstreamUrl(string url)
{
_url = url;
}
private void GivenTheConfigurationForTheRouteIs(ReRoute reRoute)
{
_httpMethod = reRoute.UpstreamHttpMethod;
_config
.Setup(x => x.ReRoutes)
.Returns(new List<ReRoute>
{
reRoute
});
}
private void WhenICheckToSeeIfTheRouteShouldBeAuthenticated()
{
_result = _routeRequiresAuthentication.IsAuthenticated(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), _url), _httpMethod);
}
private void ThenTheResultIs(bool expected)
{
_result.Data.ShouldBe(expected);
}
}
}

View File

@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenIInstanciateTheOcelotConfig()) .When(x => x.WhenIInstanciateTheOcelotConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
new ReRoute("/products/{productId}","/api/products/{productId}", "Get", "/api/products/.*$") new ReRoute("/products/{productId}","/api/products/{productId}", "Get", "/api/products/.*$", false)
})) }))
.BDDfy(); .BDDfy();
} }
@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenIInstanciateTheOcelotConfig()) .When(x => x.WhenIInstanciateTheOcelotConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}", "Get", "/api/products/.*/variants/.*$") new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}", "Get", "/api/products/.*/variants/.*$", false)
})) }))
.BDDfy(); .BDDfy();
} }
@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenIInstanciateTheOcelotConfig()) .When(x => x.WhenIInstanciateTheOcelotConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}/", "Get", "/api/products/.*/variants/.*/$") new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}/", "Get", "/api/products/.*/variants/.*/$", false)
})) }))
.BDDfy(); .BDDfy();
} }
@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.Configuration
.When(x => x.WhenIInstanciateTheOcelotConfig()) .When(x => x.WhenIInstanciateTheOcelotConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute> .Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{ {
new ReRoute("/api/products/","/", "Get", "/$") new ReRoute("/api/products/","/", "Get", "/$", false)
})) }))
.BDDfy(); .BDDfy();
} }

View File

@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>()))) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute> .And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{ {
new ReRoute("someDownstreamPath","someUpstreamPath", "Get", "someUpstreamPath") new ReRoute("someDownstreamPath","someUpstreamPath", "Get", "someUpstreamPath", false)
} }
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
@ -57,8 +57,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>()))) .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>())))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute> .And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{ {
new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty), new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty, false),
new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty) new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty, false)
} }
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
@ -75,7 +75,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath"))
.And(x => x.GivenTheConfigurationIs(new List<ReRoute> .And(x => x.GivenTheConfigurationIs(new List<ReRoute>
{ {
new ReRoute("somPath", "somePath", "Get", "somePath") new ReRoute("somPath", "somePath", "Get", "somePath", false)
} }
)) ))
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(false)))) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(false))))