test passing with authentication being provided by the user and mapped to the re route in config

This commit is contained in:
Tom Pallister
2017-11-01 15:25:55 +00:00
parent 967f0f7128
commit 3f2af85969
32 changed files with 280 additions and 606 deletions

View File

@ -1,4 +1,11 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Validator;
using Ocelot.Responses;
@ -13,10 +20,12 @@ namespace Ocelot.UnitTests.Configuration
private readonly IConfigurationValidator _configurationValidator;
private FileConfiguration _fileConfiguration;
private Response<ConfigurationValidationResult> _result;
private Mock<IAuthenticationSchemeProvider> _provider;
public ConfigurationValidationTests()
{
_configurationValidator = new FileConfigurationValidator();
_provider = new Mock<IAuthenticationSchemeProvider>();
_configurationValidator = new FileConfigurationValidator(_provider.Object);
}
[Fact]
@ -62,50 +71,48 @@ namespace Ocelot.UnitTests.Configuration
{
this.Given(x => x.GivenAConfiguration(new FileConfiguration
{
AuthenticationOptions = new List<FileAuthenticationOptions>
{
new FileAuthenticationOptions
{
Provider = "IdentityServer",
AuthenticationProviderKey = "Test"
}
},
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/products/",
UpstreamPathTemplate = "http://asdf.com",
AuthenticationProviderKey = "Test"
AuthenticationOptions = new FileAuthenticationOptions()
{
AuthenticationProviderKey = "Test"
}
}
}
}))
.And(x => x.GivenTheAuthSchemeExists("Test"))
.When(x => x.WhenIValidateTheConfiguration())
.Then(x => x.ThenTheResultIsValid())
.BDDfy();
}
private void GivenTheAuthSchemeExists(string name)
{
_provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List<AuthenticationScheme>
{
new AuthenticationScheme(name, name, typeof(TestHandler))
});
}
[Fact]
public void configuration_is_invalid_with_invalid_authentication_provider()
{
this.Given(x => x.GivenAConfiguration(new FileConfiguration
{
AuthenticationOptions = new List<FileAuthenticationOptions>
{
new FileAuthenticationOptions
{
Provider = "BootyBootyBottyRockinEverywhere",
AuthenticationProviderKey = "Test"
}
},
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/api/products/",
UpstreamPathTemplate = "http://asdf.com",
AuthenticationProviderKey = "Test"
}
AuthenticationOptions = new FileAuthenticationOptions()
{
AuthenticationProviderKey = "Test"
} }
}
}))
.When(x => x.WhenIValidateTheConfiguration())
@ -146,7 +153,7 @@ namespace Ocelot.UnitTests.Configuration
private void WhenIValidateTheConfiguration()
{
_result = _configurationValidator.IsValid(_fileConfiguration);
_result = _configurationValidator.IsValid(_fileConfiguration).Result;
}
private void ThenTheResultIsValid()
@ -163,5 +170,23 @@ namespace Ocelot.UnitTests.Configuration
{
_result.Data.Errors[0].ShouldBeOfType<T>();
}
private class TestOptions : AuthenticationSchemeOptions
{
}
private class TestHandler : AuthenticationHandler<TestOptions>
{
public TestHandler(IOptionsMonitor<TestOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var principal = new ClaimsPrincipal();
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name)));
}
}
}
}

View File

@ -446,16 +446,14 @@ namespace Ocelot.UnitTests.Configuration
[Theory]
[MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))]
public void should_create_with_headers_to_extract(string provider, IAuthenticationConfig config, FileConfiguration fileConfig)
public void should_create_with_headers_to_extract(FileConfiguration fileConfig)
{
var reRouteOptions = new ReRouteOptionsBuilder()
.WithIsAuthenticated(true)
.Build();
var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider(provider)
.WithAllowedScopes(new List<string>())
.WithConfig(config)
.Build();
var expected = new List<ReRoute>
@ -480,23 +478,21 @@ namespace Ocelot.UnitTests.Configuration
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(provider, expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy();
}
[Theory]
[MemberData(nameof(AuthenticationConfigTestData.GetAuthenticationData), MemberType = typeof(AuthenticationConfigTestData))]
public void should_create_with_authentication_properties(string provider, IAuthenticationConfig config, FileConfiguration fileConfig)
public void should_create_with_authentication_properties(FileConfiguration fileConfig)
{
var reRouteOptions = new ReRouteOptionsBuilder()
.WithIsAuthenticated(true)
.Build();
var authenticationOptions = new AuthenticationOptionsBuilder()
.WithProvider(provider)
.WithAllowedScopes(new List<string>())
.WithConfig(config)
.Build();
var expected = new List<ReRoute>
@ -516,7 +512,7 @@ namespace Ocelot.UnitTests.Configuration
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(provider, expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy();
}
@ -538,7 +534,7 @@ namespace Ocelot.UnitTests.Configuration
{
_validator
.Setup(x => x.IsValid(It.IsAny<FileConfiguration>()))
.Returns(new OkResponse<ConfigurationValidationResult>(new ConfigurationValidationResult(false)));
.ReturnsAsync(new OkResponse<ConfigurationValidationResult>(new ConfigurationValidationResult(false)));
}
private void GivenTheConfigIs(FileConfiguration fileConfiguration)
@ -587,34 +583,13 @@ namespace Ocelot.UnitTests.Configuration
}
}
private void ThenTheAuthenticationOptionsAre(string provider, List<ReRoute> expectedReRoutes)
private void ThenTheAuthenticationOptionsAre(List<ReRoute> expectedReRoutes)
{
for (int i = 0; i < _config.Data.ReRoutes.Count; i++)
{
var result = _config.Data.ReRoutes[i].AuthenticationOptions;
var expected = expectedReRoutes[i].AuthenticationOptions;
result.AllowedScopes.ShouldBe(expected.AllowedScopes);
result.Provider.ShouldBe(expected.Provider);
if (provider.ToLower() == "identityserver")
{
var config = result.Config as IdentityServerConfig;
var expectedConfig = expected.Config as IdentityServerConfig;
config.ProviderRootUrl.ShouldBe(expectedConfig.ProviderRootUrl);
config.RequireHttps.ShouldBe(expectedConfig.RequireHttps);
config.ApiName.ShouldBe(expectedConfig.ApiName);
config.ApiSecret.ShouldBe(expectedConfig.ApiSecret);
}
else
{
var config = result.Config as JwtConfig;
var expectedConfig = expected.Config as JwtConfig;
config.Audience.ShouldBe(expectedConfig.Audience);
config.Authority.ShouldBe(expectedConfig.Authority);
}
}
}
@ -666,14 +641,14 @@ namespace Ocelot.UnitTests.Configuration
private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions)
{
_authOptionsCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<List<FileAuthenticationOptions>>()))
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(authOptions);
}
private void ThenTheAuthOptionsCreatorIsCalledCorrectly()
{
_authOptionsCreator
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.AuthenticationOptions), Times.Once);
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once);
}
private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern)

View File

@ -34,7 +34,10 @@ namespace Ocelot.UnitTests.Configuration
ExceptionsAllowedBeforeBreaking = 1,
TimeoutValue = 1
},
AuthenticationProviderKey = "Test",
AuthenticationOptions = new FileAuthenticationOptions()
{
AuthenticationProviderKey = "Test"
},
RouteClaimsRequirement = new Dictionary<string, string>()
{
{"",""}

View File

@ -1,121 +0,0 @@
// 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<TestOptions>("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<TestOptions>
{
public TestHandler(IOptionsMonitor<TestOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> 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<IServiceCollection> 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<IAuthenticationSchemeProvider>();
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<IAuthenticationSchemeProvider>();
auth.RemoveScheme(name);
}
else
{
await next();
}
});
})
.ConfigureServices(services =>
{
configureServices?.Invoke(services);
services.AddAuthentication();
});
return new TestServer(builder);
}
}
}

View File

@ -34,9 +34,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20171031-01" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
@ -44,10 +44,10 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Shouldly" Version="2.8.3" />
<PackageReference Include="Moq" Version="4.7.142" />
<PackageReference Include="Shouldly" Version="3.0.0-beta0003" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="xunit" Version="2.3.0-beta2-build3683" />
<PackageReference Include="xunit" Version="2.3.1" />
</ItemGroup>
</Project>

View File

@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Responder
[InlineData(OcelotErrorCode.RequestTimedOutError)]
public void should_return_service_unavailable(OcelotErrorCode errorCode)
{
ShouldMapErrorToStatusCode(errorCode, HttpStatusCode.ServiceUnavailable);
ShouldMapErrorToStatusCode(OcelotErrorCode.RequestTimedOutError, HttpStatusCode.ServiceUnavailable);
}

View File

@ -1,8 +1,6 @@
namespace Ocelot.UnitTests.TestData
{
using System.Collections.Generic;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
public class AuthenticationConfigTestData
@ -11,31 +9,8 @@
{
yield return new object[]
{
"IdentityServer",
new IdentityServerConfigBuilder()
.WithRequireHttps(true)
.WithApiName("test")
.WithApiSecret("test")
.WithProviderRootUrl("test")
.Build(),
new FileConfiguration
{
AuthenticationOptions = new List<FileAuthenticationOptions>
{
new FileAuthenticationOptions
{
AllowedScopes = new List<string>(),
Provider = "IdentityServer",
IdentityServerConfig = new FileIdentityServerConfig
{
ProviderRootUrl = "http://localhost:51888",
RequireHttps = false,
ApiName = "api",
ApiSecret = "secret"
} ,
AuthenticationProviderKey = "Test"
}
},
ReRoutes = new List<FileReRoute>
{
new FileReRoute
@ -44,11 +19,15 @@
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = new List<string> { "Get" },
ReRouteIsCaseSensitive = true,
AuthenticationProviderKey = "Test",
AuthenticationOptions = new FileAuthenticationOptions
{
AuthenticationProviderKey = "Test",
AllowedScopes = new List<string>(),
},
AddHeadersToRequest =
{
{ "CustomerId", "Claims[CustomerId] > value" },
}
{
{ "CustomerId", "Claims[CustomerId] > value" },
}
}
}
}
@ -56,27 +35,8 @@
yield return new object[]
{
"Jwt",
new JwtConfigBuilder()
.WithAudience("a")
.WithAuthority("au")
.Build(),
new FileConfiguration
{
AuthenticationOptions = new List<FileAuthenticationOptions>
{
new FileAuthenticationOptions
{
AllowedScopes = new List<string>(),
Provider = "IdentityServer",
JwtConfig = new FileJwtConfig
{
Audience = "a",
Authority = "au"
},
AuthenticationProviderKey = "Test"
}
},
ReRoutes = new List<FileReRoute>
{
new FileReRoute
@ -85,7 +45,11 @@
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = new List<string> { "Get" },
ReRouteIsCaseSensitive = true,
AuthenticationProviderKey = "Test",
AuthenticationOptions = new FileAuthenticationOptions
{
AuthenticationProviderKey = "Test",
AllowedScopes = new List<string>(),
},
AddHeadersToRequest =
{
{ "CustomerId", "Claims[CustomerId] > value" },