mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-10-31 10:19:26 +08:00 
			
		
		
		
	started adding authentication stack, thing that decides if we should be authenticated is in
This commit is contained in:
		| @@ -0,0 +1,11 @@ | ||||
| namespace Ocelot.Library.Infrastructure.Authentication | ||||
| { | ||||
|     using Responses; | ||||
|     public class CouldNotFindConfigurationError : Error | ||||
|     { | ||||
|         public CouldNotFindConfigurationError(string message)  | ||||
|             : base(message) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,10 @@ | ||||
| namespace Ocelot.Library.Infrastructure.Authentication | ||||
| { | ||||
|     using DownstreamRouteFinder; | ||||
|     using Responses; | ||||
|  | ||||
|     public interface IRouteRequiresAuthentication | ||||
|     { | ||||
|         Response<bool> IsAuthenticated(DownstreamRoute downstreamRoute, string httpMethod); | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -43,7 +43,9 @@ namespace Ocelot.Library.Infrastructure.Configuration | ||||
|  | ||||
|                 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)); | ||||
|             }    | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,17 +2,19 @@ | ||||
| { | ||||
|     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; | ||||
|             UpstreamTemplate = upstreamTemplate; | ||||
|             UpstreamHttpMethod = upstreamHttpMethod; | ||||
|             UpstreamTemplatePattern = upstreamTemplatePattern; | ||||
|             IsAuthenticated = isAuthenticated; | ||||
|         } | ||||
|  | ||||
|         public string DownstreamTemplate { get; private set; } | ||||
|         public string UpstreamTemplate { get; private set; } | ||||
|         public string UpstreamTemplatePattern { get; private set; } | ||||
|         public string UpstreamHttpMethod { get; private set; } | ||||
|         public bool IsAuthenticated { get; private set; } | ||||
|     } | ||||
| } | ||||
| @@ -5,5 +5,6 @@ | ||||
|         public string DownstreamTemplate { get; set; } | ||||
|         public string UpstreamTemplate { get; set; } | ||||
|         public string UpstreamHttpMethod { get; set; } | ||||
|         public string Authentication { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/Ocelot.Library/Middleware/AuthenticationMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/Ocelot.Library/Middleware/AuthenticationMiddleware.cs
									
									
									
									
									
										Normal 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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>(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,6 @@ using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Ocelot.Library.Infrastructure.DownstreamRouteFinder; | ||||
| using Ocelot.Library.Infrastructure.Repository; | ||||
| using Ocelot.Library.Infrastructure.Responder; | ||||
| using Ocelot.Library.Infrastructure.UrlTemplateReplacer; | ||||
|  | ||||
| namespace Ocelot.Library.Middleware | ||||
|   | ||||
| @@ -4,7 +4,7 @@ namespace Ocelot.Library.Middleware | ||||
| { | ||||
|     public static class DownstreamUrlCreatorMiddlewareExtensions | ||||
|     { | ||||
|         public static IApplicationBuilder UserDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) | ||||
|         public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) | ||||
|         { | ||||
|             return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>(); | ||||
|         } | ||||
|   | ||||
| @@ -20,6 +20,8 @@ using Ocelot.Library.Middleware; | ||||
|  | ||||
| namespace Ocelot | ||||
| { | ||||
|     using Library.Infrastructure.Authentication; | ||||
|  | ||||
|     public class Startup | ||||
|     { | ||||
|         public Startup(IHostingEnvironment env) | ||||
| @@ -52,6 +54,7 @@ namespace Ocelot | ||||
|             services.AddSingleton<IHttpRequester, HttpClientHttpRequester>(); | ||||
|             services.AddSingleton<IHttpResponder, HttpContextResponder>(); | ||||
|             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 | ||||
|             services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | ||||
| @@ -69,7 +72,9 @@ namespace Ocelot | ||||
|  | ||||
|             app.UseDownstreamRouteFinderMiddleware(); | ||||
|  | ||||
|             app.UserDownstreamUrlCreatorMiddleware(); | ||||
|             app.UseAuthenticationMiddleware(); | ||||
|  | ||||
|             app.UseDownstreamUrlCreatorMiddleware(); | ||||
|  | ||||
|             app.UseHttpRequestBuilderMiddleware(); | ||||
|  | ||||
|   | ||||
| @@ -2,3 +2,4 @@ ReRoutes: | ||||
| - DownstreamTemplate: http://localhost:51879/ | ||||
|   UpstreamTemplate: / | ||||
|   UpstreamHttpMethod: Get | ||||
|   Authentication: IdentityServer | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                 .When(x => x.WhenIInstanciateTheOcelotConfig()) | ||||
|                 .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(); | ||||
|         } | ||||
| @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                 .When(x => x.WhenIInstanciateTheOcelotConfig()) | ||||
|                 .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(); | ||||
|         } | ||||
| @@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                 .When(x => x.WhenIInstanciateTheOcelotConfig()) | ||||
|                 .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(); | ||||
|         } | ||||
| @@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                 .When(x => x.WhenIInstanciateTheOcelotConfig()) | ||||
|                 .Then(x => x.ThenTheReRoutesAre(new List<ReRoute> | ||||
|                 { | ||||
|                     new ReRoute("/api/products/","/", "Get", "/$") | ||||
|                     new ReRoute("/api/products/","/", "Get", "/$", false) | ||||
|                 })) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|   | ||||
| @@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|                 .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>()))) | ||||
|                 .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)))) | ||||
| @@ -57,8 +57,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|                 .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>()))) | ||||
|                 .And(x => x.GivenTheConfigurationIs(new List<ReRoute> | ||||
|                     { | ||||
|                         new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty), | ||||
|                         new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty) | ||||
|                         new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty, false), | ||||
|                         new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty, false) | ||||
|                     } | ||||
|                 )) | ||||
|                 .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) | ||||
| @@ -75,7 +75,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) | ||||
|                  .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)))) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 tom.pallister
					tom.pallister