diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 1c36d522..ac7a5964 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Extensions; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; @@ -71,17 +73,37 @@ namespace Ocelot.Authentication.Middleware { _logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions); + //var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions); - if (authenticationHandler.IsError) - { - _logger.LogError($"Error getting authentication handler for {context.Request.Path}. {authenticationHandler.Errors.ToErrorString()}"); - SetPipelineError(authenticationHandler.Errors); - return; - } + /* if (authenticationHandler.IsError) + { + _logger.LogError($"Error getting authentication handler for {context.Request.Path}. {authenticationHandler.Errors.ToErrorString()}"); + SetPipelineError(authenticationHandler.Errors); + return; + } - await authenticationHandler.Data.Handler.Handle(context); + await authenticationHandler.Data.Handler.Handle(context);*/ + //todo - add the scheme for this route?? + var auth = context.RequestServices.GetRequiredService(); + + /* Action configureOptions = o => + { + o.Authority = ""; + o.ApiName = ""; + o.RequireHttpsMetadata = true; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = ""; + }; + */ + + + //var scheme = new AuthenticationScheme(DownstreamRoute.ReRoute.AuthenticationOptions.Provider, DownstreamRoute.ReRoute.AuthenticationOptions.Provider, typeof(IdentityServerAuthenticationHandler)); + //auth.AddScheme(scheme); + + //todo - call the next middleware to authenticate? Does this need to be on a different branch so it doesnt call any further middlewares? + var result = await context.AuthenticateAsync(DownstreamRoute.ReRoute.AuthenticationOptions.Provider); + context.User = result.Principal; if (context.User.Identity.IsAuthenticated) { @@ -98,8 +120,10 @@ namespace Ocelot.Authentication.Middleware _logger.LogError($"Client has NOT been authenticated for {context.Request.Path} and pipeline error set. {error.ToErrorString()}"); SetPipelineError(error); - return; } + + //todo - remove the scheme or do we leave it? + auth.RemoveScheme(DownstreamRoute.ReRoute.AuthenticationOptions.Provider); } else { @@ -124,7 +148,7 @@ namespace Ocelot.Authentication.Middleware protected override Task HandleAuthenticateAsync() { var principal = new ClaimsPrincipal(); - var id = new ClaimsIdentity(); + var id = new ClaimsIdentity("Ocelot"); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, Scheme.Name, ClaimValueTypes.String, Scheme.Name)); if (Options.Instance != null) { diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index d3f2ce2f..0a64beee 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -44,6 +44,7 @@ using System.Net.Http; using System.Reflection; using System.Security.Cryptography.X509Certificates; using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.IdentityModel.Tokens; @@ -149,6 +150,27 @@ namespace Ocelot.DependencyInjection { services.AddIdentityServer(identityServerConfiguration, configurationRoot); } + services.AddSingleton(services); + + + Action builder = schemeBuilder => + { + + } + + services.AddAuthentication(x => + { + x.AddScheme("", ); + }) + .AddIdentityServerAuthentication(o => + { + o.Authority = "http://localhost:51888"; + o.ApiName = "api"; + o.RequireHttpsMetadata = false; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }); + return services; } diff --git a/test/Ocelot.UnitTests/DynamicSchemeTests.cs b/test/Ocelot.UnitTests/DynamicSchemeTests.cs new file mode 100644 index 00000000..b699161a --- /dev/null +++ b/test/Ocelot.UnitTests/DynamicSchemeTests.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class DynamicSchemeTests + { + [Fact] + public async Task OptionsAreConfiguredOnce() + { + var server = CreateServer(s => + { + s.Configure("One", o => o.Instance = new Singleton()); + }); + // Add One scheme + var response = await server.CreateClient().GetAsync("http://example.com/add/One"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var transaction = await server.CreateClient().GetAsync("http://example.com/auth/One"); + var result = await transaction.Content.ReadAsStringAsync(); + result.ShouldBe("True"); + } + + public class TestOptions : AuthenticationSchemeOptions + { + public Singleton Instance { get; set; } + } + + public class Singleton + { + public static int _count; + + public Singleton() + { + _count++; + Count = _count; + } + + public int Count { get; } + } + + private class TestHandler : AuthenticationHandler + { + public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected override Task HandleAuthenticateAsync() + { + var principal = new ClaimsPrincipal(); + var id = new ClaimsIdentity("Ocelot"); + id.AddClaim(new Claim(ClaimTypes.NameIdentifier, Scheme.Name, ClaimValueTypes.String, Scheme.Name)); + if (Options.Instance != null) + { + id.AddClaim(new Claim("Count", Options.Instance.Count.ToString())); + } + principal.AddIdentity(id); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); + } + } + + private static TestServer CreateServer(Action configureServices = null) + { + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseAuthentication(); + app.Use(async (context, next) => + { + var req = context.Request; + var res = context.Response; + if (req.Path.StartsWithSegments(new PathString("/add"), out var remainder)) + { + var name = remainder.Value.Substring(1); + var auth = context.RequestServices.GetRequiredService(); + var scheme = new AuthenticationScheme(name, name, typeof(TestHandler)); + auth.AddScheme(scheme); + } + else if (req.Path.StartsWithSegments(new PathString("/auth"), out remainder)) + { + var name = (remainder.Value.Length > 0) ? remainder.Value.Substring(1) : null; + var result = await context.AuthenticateAsync(name); + context.User = result.Principal; + await res.WriteJsonAsync(context.User.Identity.IsAuthenticated.ToString()); + } + else if (req.Path.StartsWithSegments(new PathString("/remove"), out remainder)) + { + var name = remainder.Value.Substring(1); + var auth = context.RequestServices.GetRequiredService(); + auth.RemoveScheme(name); + } + else + { + await next(); + } + }); + }) + .ConfigureServices(services => + { + configureServices?.Invoke(services); + services.AddAuthentication(); + }); + return new TestServer(builder); + } + } +} \ No newline at end of file