Merge pull request #104 from juancash/checking-identity-server

Identity Server - Check's issues with Audience and AllowedScopes
This commit is contained in:
Tom Pallister 2017-06-10 15:16:15 +01:00 committed by GitHub
commit 6ff976f964
20 changed files with 363 additions and 38 deletions

View File

@ -7,7 +7,7 @@ namespace Ocelot.Authorisation
{ {
using Infrastructure.Claims.Parser; using Infrastructure.Claims.Parser;
public class ClaimsAuthoriser : IAuthoriser public class ClaimsAuthoriser : IClaimsAuthoriser
{ {
private readonly IClaimsParser _claimsParser; private readonly IClaimsParser _claimsParser;

View File

@ -5,9 +5,8 @@ namespace Ocelot.Authorisation
{ {
using System.Collections.Generic; using System.Collections.Generic;
public interface IAuthoriser public interface IClaimsAuthoriser
{ {
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Dictionary<string, string> routeClaimsRequirement);
Dictionary<string, string> routeClaimsRequirement);
} }
} }

View File

@ -0,0 +1,12 @@
using System.Security.Claims;
using Ocelot.Responses;
namespace Ocelot.Authorisation
{
using System.Collections.Generic;
public interface IScopesAuthoriser
{
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes);
}
}

View File

@ -1,6 +1,7 @@
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Configuration;
namespace Ocelot.Authorisation.Middleware namespace Ocelot.Authorisation.Middleware
{ {
@ -13,17 +14,20 @@ namespace Ocelot.Authorisation.Middleware
public class AuthorisationMiddleware : OcelotMiddleware public class AuthorisationMiddleware : OcelotMiddleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IAuthoriser _authoriser; private readonly IClaimsAuthoriser _claimsAuthoriser;
private readonly IScopesAuthoriser _scopesAuthoriser;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
public AuthorisationMiddleware(RequestDelegate next, public AuthorisationMiddleware(RequestDelegate next,
IRequestScopedDataRepository requestScopedDataRepository, IRequestScopedDataRepository requestScopedDataRepository,
IAuthoriser authoriser, IClaimsAuthoriser claimsAuthoriser,
IScopesAuthoriser scopesAuthoriser,
IOcelotLoggerFactory loggerFactory) IOcelotLoggerFactory loggerFactory)
: base(requestScopedDataRepository) : base(requestScopedDataRepository)
{ {
_next = next; _next = next;
_authoriser = authoriser; _claimsAuthoriser = claimsAuthoriser;
_scopesAuthoriser = scopesAuthoriser;
_logger = loggerFactory.CreateLogger<AuthorisationMiddleware>(); _logger = loggerFactory.CreateLogger<AuthorisationMiddleware>();
} }
@ -31,11 +35,41 @@ namespace Ocelot.Authorisation.Middleware
{ {
_logger.LogDebug("started authorisation"); _logger.LogDebug("started authorisation");
if (DownstreamRoute.ReRoute.IsAuthorised) if (IsAuthenticatedRoute(DownstreamRoute.ReRoute))
{
_logger.LogDebug("route is authenticated scopes must be checked");
var authorised = _scopesAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.AuthenticationOptions.AllowedScopes);
if (authorised.IsError)
{
_logger.LogDebug("error authorising user scopes");
SetPipelineError(authorised.Errors);
return;
}
if (IsAuthorised(authorised))
{
_logger.LogDebug("user scopes is authorised calling next authorisation checks");
}
else
{
_logger.LogDebug("user scopes is not authorised setting pipeline error");
SetPipelineError(new List<Error>
{
new UnauthorisedError(
$"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}")
});
}
}
if (IsAuthorisedRoute(DownstreamRoute.ReRoute))
{ {
_logger.LogDebug("route is authorised"); _logger.LogDebug("route is authorised");
var authorised = _authoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement); var authorised = _claimsAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement);
if (authorised.IsError) if (authorised.IsError)
{ {
@ -78,5 +112,15 @@ namespace Ocelot.Authorisation.Middleware
{ {
return authorised.Data; return authorised.Data;
} }
private static bool IsAuthenticatedRoute(ReRoute reRoute)
{
return reRoute.IsAuthenticated;
}
private static bool IsAuthorisedRoute(ReRoute reRoute)
{
return reRoute.IsAuthorised;
}
} }
} }

View File

@ -0,0 +1,12 @@
using Ocelot.Errors;
namespace Ocelot.Authorisation
{
public class ScopeNotAuthorisedError : Error
{
public ScopeNotAuthorisedError(string message)
: base(message, OcelotErrorCode.ScopeNotAuthorisedError)
{
}
}
}

View File

@ -0,0 +1,51 @@
using IdentityModel;
using Ocelot.Errors;
using Ocelot.Responses;
using System.Collections.Generic;
using System.Security.Claims;
using System.Linq;
namespace Ocelot.Authorisation
{
using Infrastructure.Claims.Parser;
public class ScopesAuthoriser : IScopesAuthoriser
{
private readonly IClaimsParser _claimsParser;
public ScopesAuthoriser(IClaimsParser claimsParser)
{
_claimsParser = claimsParser;
}
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
{
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
{
return new OkResponse<bool>(true);
}
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope);
if (values.IsError)
{
return new ErrorResponse<bool>(values.Errors);
}
var userScopes = values.Data;
List<string> matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList();
if (matchesScopes == null || matchesScopes.Count == 0)
{
return new ErrorResponse<bool>(new List<Error>
{
new ScopeNotAuthorisedError(
$"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")
});
}
return new OkResponse<bool>(true);
}
}
}

View File

@ -144,7 +144,8 @@ namespace Ocelot.DependencyInjection
services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>(); services.TryAddSingleton<IRemoveOutputHeaders, RemoveOutputHeaders>();
services.TryAddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>(); services.TryAddSingleton<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>(); services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
services.TryAddSingleton<IAuthoriser, ClaimsAuthoriser>(); services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>(); services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>(); services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>(); services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();

View File

@ -17,6 +17,7 @@
InstructionNotForClaimsError, InstructionNotForClaimsError,
UnauthorizedError, UnauthorizedError,
ClaimValueNotAuthorisedError, ClaimValueNotAuthorisedError,
ScopeNotAuthorisedError,
UserDoesNotHaveClaimError, UserDoesNotHaveClaimError,
DownstreamPathTemplateContainsSchemeError, DownstreamPathTemplateContainsSchemeError,
DownstreamPathNullOrEmptyError, DownstreamPathNullOrEmptyError,

View File

@ -1,10 +1,10 @@
namespace Ocelot.Infrastructure.Claims.Parser namespace Ocelot.Infrastructure.Claims.Parser
{ {
using Errors;
using Responses;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using Errors;
using Responses;
public class ClaimsParser : IClaimsParser public class ClaimsParser : IClaimsParser
{ {
@ -37,6 +37,17 @@
return new OkResponse<string>(value); return new OkResponse<string>(value);
} }
public Response<List<string>> GetValuesByClaimType(IEnumerable<Claim> claims, string claimType)
{
List<string> values = new List<string>();
values.AddRange(claims.Where(x => x.Type == claimType).Select(x => x.Value).ToList());
return new OkResponse<List<string>>(values);
}
private Response<string> GetValue(IEnumerable<Claim> claims, string key) private Response<string> GetValue(IEnumerable<Claim> claims, string key)
{ {
var claim = claims.FirstOrDefault(c => c.Type == key); var claim = claims.FirstOrDefault(c => c.Type == key);

View File

@ -7,5 +7,6 @@
public interface IClaimsParser public interface IClaimsParser
{ {
Response<string> GetValue(IEnumerable<Claim> claims, string key, string delimiter, int index); Response<string> GetValue(IEnumerable<Claim> claims, string key, string delimiter, int index);
Response<List<string>> GetValuesByClaimType(IEnumerable<Claim> claims, string claimType);
} }
} }

View File

@ -40,7 +40,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="1.1.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.2" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
<PackageReference Include="CacheManager.Core" Version="0.9.2" /> <PackageReference Include="CacheManager.Core" Version="0.9.2" />
@ -48,7 +48,7 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="0.9.2" /> <PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="0.9.2" />
<PackageReference Include="Consul" Version="0.7.2.1" /> <PackageReference Include="Consul" Version="0.7.2.1" />
<PackageReference Include="Polly" Version="5.0.3" /> <PackageReference Include="Polly" Version="5.0.3" />
<PackageReference Include="IdentityServer4" Version="1.0.1" /> <PackageReference Include="IdentityServer4" Version="1.5.1" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="1.1.1" />
</ItemGroup> </ItemGroup>

View File

@ -15,6 +15,7 @@ namespace Ocelot.Responder
if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError
|| e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError
|| e.Code == OcelotErrorCode.ScopeNotAuthorisedError
|| e.Code == OcelotErrorCode.UserDoesNotHaveClaimError || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError
|| e.Code == OcelotErrorCode.CannotFindClaimError)) || e.Code == OcelotErrorCode.CannotFindClaimError))
{ {

View File

@ -62,7 +62,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
@ -100,7 +100,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenOcelotIsRunning())
@ -138,7 +138,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
@ -150,6 +150,45 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_response_401_using_identity_server_with_token_requested_for_other_api()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = _downstreamServicePath,
DownstreamPort = _downstreamServicePort,
DownstreamHost = _downstreamServiceHost,
DownstreamScheme = _downstreamServiceScheme,
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
AuthenticationOptions = new FileAuthenticationOptions
{
AllowedScopes = new List<string>(),
Provider = "IdentityServer",
ProviderRootUrl = _identityServerRootUrl,
RequireHttps = false,
ApiName = "api",
ApiSecret = "secret"
}
}
}
};
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApi2(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIHaveAddedATokenToMyRequest())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized))
.BDDfy();
}
[Fact] [Fact]
public void should_return_201_using_identity_server_access_token() public void should_return_201_using_identity_server_access_token()
{ {
@ -179,7 +218,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
@ -219,7 +258,7 @@ namespace Ocelot.AcceptanceTests
} }
}; };
this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", "api2", AccessTokenType.Reference))
.And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty))
.And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
@ -252,7 +291,7 @@ namespace Ocelot.AcceptanceTests
_servicebuilder.Start(); _servicebuilder.Start();
} }
private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType)
{ {
_identityServerBuilder = new WebHostBuilder() _identityServerBuilder = new WebHostBuilder()
.UseUrls(url) .UseUrls(url)
@ -276,6 +315,32 @@ namespace Ocelot.AcceptanceTests
Scopes = new List<Scope>() Scopes = new List<Scope>()
{ {
new Scope("api"), 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"
}
},
new ApiResource
{
Name = api2Name,
Description = "My second API",
Enabled = true,
DisplayName = "second test",
Scopes = new List<Scope>()
{
new Scope("api2"),
new Scope("api2.readOnly"),
new Scope("openid"), new Scope("openid"),
new Scope("offline_access") new Scope("offline_access")
}, },
@ -299,7 +364,7 @@ namespace Ocelot.AcceptanceTests
ClientId = "client", ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = new List<Secret> {new Secret("secret".Sha256())}, ClientSecrets = new List<Secret> {new Secret("secret".Sha256())},
AllowedScopes = new List<string> { apiName, "openid", "offline_access" }, AllowedScopes = new List<string> { apiName, api2Name, "api.readOnly", "openid", "offline_access" },
AccessTokenType = tokenType, AccessTokenType = tokenType,
Enabled = true, Enabled = true,
RequireClientSecret = false RequireClientSecret = false

View File

@ -140,6 +140,84 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_response_200_using_identity_server_with_allowed_scope()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamPort = 51876,
DownstreamHost = "localhost",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
AuthenticationOptions = new FileAuthenticationOptions
{
AllowedScopes = new List<string>{ "api", "api.readOnly", "openid", "offline_access" },
Provider = "IdentityServer",
ProviderRootUrl = "http://localhost:51888",
RequireHttps = false,
ApiName = "api",
ApiSecret = "secret"
}
}
}
};
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIHaveAddedATokenToMyRequest())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
[Fact]
public void should_return_response_403_using_identity_server_with_scope_not_allowed()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamPort = 51876,
DownstreamHost = "localhost",
DownstreamScheme = "http",
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
AuthenticationOptions = new FileAuthenticationOptions
{
AllowedScopes = new List<string>{ "api", "openid", "offline_access" },
Provider = "IdentityServer",
ProviderRootUrl = "http://localhost:51888",
RequireHttps = false,
ApiName = "api",
ApiSecret = "secret"
}
}
}
};
this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt))
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 200, "Hello from Laura"))
.And(x => _steps.GivenIHaveATokenForApiReadOnlyScope("http://localhost:51888"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunning())
.And(x => _steps.GivenIHaveAddedATokenToMyRequest())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Forbidden))
.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()
@ -185,6 +263,7 @@ namespace Ocelot.AcceptanceTests
Scopes = new List<Scope>() Scopes = new List<Scope>()
{ {
new Scope("api"), new Scope("api"),
new Scope("api.readOnly"),
new Scope("openid"), new Scope("openid"),
new Scope("offline_access") new Scope("offline_access")
}, },
@ -209,7 +288,7 @@ namespace Ocelot.AcceptanceTests
ClientId = "client", ClientId = "client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = new List<Secret> {new Secret("secret".Sha256())}, ClientSecrets = new List<Secret> {new Secret("secret".Sha256())},
AllowedScopes = new List<string> { apiName, "openid", "offline_access" }, AllowedScopes = new List<string> { apiName, "api.readOnly", "openid", "offline_access" },
AccessTokenType = tokenType, AccessTokenType = tokenType,
Enabled = true, Enabled = true,
RequireClientSecret = false RequireClientSecret = false

View File

@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests
{ {
AllowedScopes = new List<string> AllowedScopes = new List<string>
{ {
"openid", "offline_access" "openid", "offline_access", "api"
}, },
Provider = "IdentityServer", Provider = "IdentityServer",
ProviderRootUrl = "http://localhost:52888", ProviderRootUrl = "http://localhost:52888",

View File

@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests
{ {
AllowedScopes = new List<string> AllowedScopes = new List<string>
{ {
"openid", "offline_access" "openid", "offline_access", "api"
}, },
Provider = "IdentityServer", Provider = "IdentityServer",
ProviderRootUrl = "http://localhost:57888", ProviderRootUrl = "http://localhost:57888",

View File

@ -43,7 +43,6 @@
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.1" />
<PackageReference Include="IdentityServer4" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="1.1.1" />

View File

@ -199,6 +199,52 @@ namespace Ocelot.AcceptanceTests
} }
} }
public void GivenIHaveATokenForApiReadOnlyScope(string url)
{
var tokenUrl = $"{url}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "client"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "api.readOnly"),
new KeyValuePair<string, string>("username", "test"),
new KeyValuePair<string, string>("password", "test"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
using (var httpClient = new HttpClient())
{
var response = httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
}
public void GivenIHaveATokenForApi2(string url)
{
var tokenUrl = $"{url}/connect/token";
var formData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "client"),
new KeyValuePair<string, string>("client_secret", "secret"),
new KeyValuePair<string, string>("scope", "api2"),
new KeyValuePair<string, string>("username", "test"),
new KeyValuePair<string, string>("password", "test"),
new KeyValuePair<string, string>("grant_type", "password")
};
var content = new FormUrlEncodedContent(formData);
using (var httpClient = new HttpClient())
{
var response = httpClient.PostAsync(tokenUrl, content).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
_token = JsonConvert.DeserializeObject<BearerToken>(responseContent);
}
}
public void GivenIHaveAnOcelotToken(string adminPath) public void GivenIHaveAnOcelotToken(string adminPath)
{ {
var tokenUrl = $"{adminPath}/connect/token"; var tokenUrl = $"{adminPath}/connect/token";

View File

@ -44,7 +44,7 @@
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" /> <PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" /> <PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.1" />
<PackageReference Include="IdentityServer4" Version="1.0.1" /> <PackageReference Include="IdentityServer4" Version="1.5.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
<PackageReference Include="Shouldly" Version="2.8.2" /> <PackageReference Include="Shouldly" Version="2.8.2" />

View File

@ -25,7 +25,8 @@ namespace Ocelot.UnitTests.Authorization
public class AuthorisationMiddlewareTests : IDisposable public class AuthorisationMiddlewareTests : IDisposable
{ {
private readonly Mock<IRequestScopedDataRepository> _scopedRepository; private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
private readonly Mock<IAuthoriser> _authService; private readonly Mock<IClaimsAuthoriser> _authService;
private readonly Mock<IScopesAuthoriser> _authScopesService;
private readonly string _url; private readonly string _url;
private readonly TestServer _server; private readonly TestServer _server;
private readonly HttpClient _client; private readonly HttpClient _client;
@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Authorization
{ {
_url = "http://localhost:51879"; _url = "http://localhost:51879";
_scopedRepository = new Mock<IRequestScopedDataRepository>(); _scopedRepository = new Mock<IRequestScopedDataRepository>();
_authService = new Mock<IAuthoriser>(); _authService = new Mock<IClaimsAuthoriser>();
_authScopesService = new Mock<IScopesAuthoriser>();
var builder = new WebHostBuilder() var builder = new WebHostBuilder()
.ConfigureServices(x => .ConfigureServices(x =>
@ -44,6 +46,7 @@ namespace Ocelot.UnitTests.Authorization
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
x.AddLogging(); x.AddLogging();
x.AddSingleton(_authService.Object); x.AddSingleton(_authService.Object);
x.AddSingleton(_authScopesService.Object);
x.AddSingleton(_scopedRepository.Object); x.AddSingleton(_scopedRepository.Object);
}) })
.UseUrls(_url) .UseUrls(_url)