Merge branch 'release-3.1.6'

This commit is contained in:
Tom Gardham-Pallister 2018-02-21 20:54:55 +00:00
commit 3754a8ae36
6 changed files with 268 additions and 16 deletions

View File

@ -20,22 +20,22 @@ namespace Ocelot.Authorisation
{ {
foreach (var required in routeClaimsRequirement) foreach (var required in routeClaimsRequirement)
{ {
var value = _claimsParser.GetValue(claimsPrincipal.Claims, required.Key, string.Empty, 0); var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key);
if (value.IsError) if (values.IsError)
{ {
return new ErrorResponse<bool>(value.Errors); return new ErrorResponse<bool>(values.Errors);
} }
if (value.Data != null) if (values.Data != null)
{ {
var authorised = value.Data == required.Value; var authorised = values.Data.Contains(required.Value);
if (!authorised) if (!authorised)
{ {
return new ErrorResponse<bool>(new List<Error> return new ErrorResponse<bool>(new List<Error>
{ {
new ClaimValueNotAuthorisedError( new ClaimValueNotAuthorisedError(
$"claim value: {value.Data} is not the same as required value: {required.Value} for type: {required.Key}") $"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}")
}); });
} }
} }

View File

@ -42,7 +42,6 @@ namespace Ocelot.Errors.Middleware
_logger.LogDebug("ocelot pipeline started"); _logger.LogDebug("ocelot pipeline started");
await _next.Invoke(context); await _next.Invoke(context);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -235,6 +235,66 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_fix_issue_240()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51876,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
AuthenticationOptions = new FileAuthenticationOptions
{
AuthenticationProviderKey = "Test"
},
RouteClaimsRequirement =
{
{"Role", "User"}
}
}
}
};
var users = new List<TestUser>
{
new TestUser
{
Username = "test",
Password = "test",
SubjectId = "registered|1231231",
Claims = new List<Claim>
{
new Claim("Role", "AdminUser"),
new Claim("Role", "User")
},
}
};
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt, users))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning(_options, "Test"))
.And(x => _steps.GivenIHaveAddedATokenToMyRequest())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody)
{ {
_servicebuilder = new WebHostBuilder() _servicebuilder = new WebHostBuilder()
@ -335,7 +395,75 @@ namespace Ocelot.AcceptanceTests
_identityServerBuilder.Start(); _identityServerBuilder.Start();
_steps.VerifyIdentiryServerStarted(url); _steps.VerifyIdentiryServerStarted(url);
}
private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, List<TestUser> users)
{
_identityServerBuilder = new WebHostBuilder()
.UseUrls(url)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls(url)
.ConfigureServices(services =>
{
services.AddLogging();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource
{
Name = apiName,
Description = "My API",
Enabled = true,
DisplayName = "test",
Scopes = new List<Scope>()
{
new Scope("api"),
new Scope("api.readOnly"),
new Scope("openid"),
new Scope("offline_access"),
},
ApiSecrets = new List<Secret>()
{
new Secret
{
Value = "secret".Sha256()
}
},
UserClaims = new List<string>()
{
"CustomerId", "LocationId", "UserType", "UserId", "Role"
}
},
})
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = new List<Secret> {new Secret("secret".Sha256())},
AllowedScopes = new List<string> { apiName, "api.readOnly", "openid", "offline_access" },
AccessTokenType = tokenType,
Enabled = true,
RequireClientSecret = false,
}
})
.AddTestUsers(users);
})
.Configure(app =>
{
app.UseIdentityServer();
})
.Build();
_identityServerBuilder.Start();
_steps.VerifyIdentiryServerStarted(url);
} }
public void Dispose() public void Dispose()

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Middleware; using Ocelot.Middleware;
using Shouldly; using Shouldly;
@ -61,7 +63,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -104,7 +106,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -147,7 +149,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -190,7 +192,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -233,7 +235,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -276,7 +278,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200, ""))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunning(configuration)) .And(x => _steps.GivenOcelotIsRunning(configuration))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
@ -285,12 +287,59 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact(Skip = "This is just an example to show how you could hook into Ocelot pipeline with your own middleware. At the moment you must use Response.OnCompleted callback and cannot change the response :( I will see if this can be changed one day!")]
public void should_fix_issue_237()
{
Func<object, Task> callback = state =>
{
var httpContext = (HttpContext)state;
if (httpContext.Response.StatusCode > 400)
{
Debug.WriteLine("COUNT CALLED");
Console.WriteLine("COUNT CALLED");
}
return Task.CompletedTask;
};
var fileConfiguration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/west",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 41880,
}
},
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41880", 200, "/test"))
.And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath))
.And(x => _steps.GivenOcelotIsRunningWithMiddleareBeforePipeline<FakeMiddleware>(callback))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound))
.BDDfy();
}
private void ThenTheCounterIs(int expected) private void ThenTheCounterIs(int expected)
{ {
_counter.ShouldBe(expected); _counter.ShouldBe(expected);
} }
private void GivenThereIsAServiceRunningOn(string url, int statusCode) private void GivenThereIsAServiceRunningOn(string url, int statusCode, string basePath)
{ {
_builder = new WebHostBuilder() _builder = new WebHostBuilder()
.UseUrls(url) .UseUrls(url)
@ -300,9 +349,19 @@ namespace Ocelot.AcceptanceTests
.UseUrls(url) .UseUrls(url)
.Configure(app => .Configure(app =>
{ {
app.UsePathBase(basePath);
app.Run(context => app.Run(context =>
{
if(string.IsNullOrEmpty(basePath))
{ {
context.Response.StatusCode = statusCode; context.Response.StatusCode = statusCode;
}
else if(context.Request.Path.Value != basePath)
{
context.Response.StatusCode = 404;;
}
return Task.CompletedTask; return Task.CompletedTask;
}); });
}) })
@ -316,5 +375,24 @@ namespace Ocelot.AcceptanceTests
_builder?.Dispose(); _builder?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
public class FakeMiddleware
{
private readonly RequestDelegate _next;
private readonly Func<object, Task> _callback;
public FakeMiddleware(RequestDelegate next, Func<object, Task> callback)
{
_next = next;
_callback = callback;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
context.Response.OnCompleted(_callback, context);
}
}
} }
} }

View File

@ -103,6 +103,35 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
{
_webHostBuilder = new WebHostBuilder();
_webHostBuilder
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("configuration.json");
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddOcelot();
})
.Configure(app =>
{
app.UseMiddleware<T>(callback);
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
/// <summary> /// <summary>
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
/// </summary> /// </summary>

View File

@ -27,7 +27,25 @@ namespace Ocelot.UnitTests.Authorization
{ {
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{ {
new Claim("UserType", "registered") new Claim("UserType", "registered"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
{
{"UserType", "registered"}
}))
.When(x => x.WhenICallTheAuthoriser())
.Then(x => x.ThenTheUserIsAuthorised())
.BDDfy();
}
[Fact]
public void should_authorise_user_multiple_claims_of_same_type()
{
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim("UserType", "guest"),
new Claim("UserType", "registered"),
})))) }))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string> .And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
{ {