mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
Merge pull request #104 from juancash/checking-identity-server
Identity Server - Check's issues with Audience and AllowedScopes
This commit is contained in:
commit
6ff976f964
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
12
src/Ocelot/Authorisation/IScopesAuthoriser.cs
Normal file
12
src/Ocelot/Authorisation/IScopesAuthoriser.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs
Normal file
12
src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Authorisation
|
||||||
|
{
|
||||||
|
public class ScopeNotAuthorisedError : Error
|
||||||
|
{
|
||||||
|
public ScopeNotAuthorisedError(string message)
|
||||||
|
: base(message, OcelotErrorCode.ScopeNotAuthorisedError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/Ocelot/Authorisation/ScopesAuthoriser.cs
Normal file
51
src/Ocelot/Authorisation/ScopesAuthoriser.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>();
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
InstructionNotForClaimsError,
|
InstructionNotForClaimsError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
ClaimValueNotAuthorisedError,
|
ClaimValueNotAuthorisedError,
|
||||||
|
ScopeNotAuthorisedError,
|
||||||
UserDoesNotHaveClaimError,
|
UserDoesNotHaveClaimError,
|
||||||
DownstreamPathTemplateContainsSchemeError,
|
DownstreamPathTemplateContainsSchemeError,
|
||||||
DownstreamPathNullOrEmptyError,
|
DownstreamPathNullOrEmptyError,
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>
|
||||||
|
|
||||||
|
@ -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))
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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" />
|
||||||
|
@ -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";
|
||||||
|
@ -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" />
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user