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
20 changed files with 363 additions and 38 deletions

View File

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

View File

@ -5,9 +5,8 @@ namespace Ocelot.Authorisation
{
using System.Collections.Generic;
public interface IAuthoriser
public interface IClaimsAuthoriser
{
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement);
Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, 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.Logging;
using Ocelot.Responses;
using Ocelot.Configuration;
namespace Ocelot.Authorisation.Middleware
{
@ -13,17 +14,20 @@ namespace Ocelot.Authorisation.Middleware
public class AuthorisationMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IAuthoriser _authoriser;
private readonly IClaimsAuthoriser _claimsAuthoriser;
private readonly IScopesAuthoriser _scopesAuthoriser;
private readonly IOcelotLogger _logger;
public AuthorisationMiddleware(RequestDelegate next,
IRequestScopedDataRepository requestScopedDataRepository,
IAuthoriser authoriser,
IClaimsAuthoriser claimsAuthoriser,
IScopesAuthoriser scopesAuthoriser,
IOcelotLoggerFactory loggerFactory)
: base(requestScopedDataRepository)
{
_next = next;
_authoriser = authoriser;
_claimsAuthoriser = claimsAuthoriser;
_scopesAuthoriser = scopesAuthoriser;
_logger = loggerFactory.CreateLogger<AuthorisationMiddleware>();
}
@ -31,11 +35,41 @@ namespace Ocelot.Authorisation.Middleware
{
_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");
var authorised = _authoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement);
var authorised = _claimsAuthoriser.Authorise(context.User, DownstreamRoute.ReRoute.RouteClaimsRequirement);
if (authorised.IsError)
{
@ -78,5 +112,15 @@ namespace Ocelot.Authorisation.Middleware
{
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<IOcelotConfigurationProvider, OcelotConfigurationProvider>();
services.TryAddSingleton<IClaimToThingConfigurationParser, ClaimToThingConfigurationParser>();
services.TryAddSingleton<IAuthoriser, ClaimsAuthoriser>();
services.TryAddSingleton<IClaimsAuthoriser, ClaimsAuthoriser>();
services.TryAddSingleton<IScopesAuthoriser, ScopesAuthoriser>();
services.TryAddSingleton<IAddClaimsToRequest, AddClaimsToRequest>();
services.TryAddSingleton<IAddHeadersToRequest, AddHeadersToRequest>();
services.TryAddSingleton<IAddQueriesToRequest, AddQueriesToRequest>();

View File

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

View File

@ -1,10 +1,10 @@
namespace Ocelot.Infrastructure.Claims.Parser
{
using Errors;
using Responses;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using Errors;
using Responses;
public class ClaimsParser : IClaimsParser
{
@ -37,6 +37,17 @@
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)
{
var claim = claims.FirstOrDefault(c => c.Type == key);

View File

@ -7,5 +7,6 @@
public interface IClaimsParser
{
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.MicrosoftAccount" 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.Server.Kestrel" Version="1.1.1" />
<PackageReference Include="CacheManager.Core" Version="0.9.2" />
@ -48,7 +48,7 @@
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="0.9.2" />
<PackageReference Include="Consul" Version="0.7.2.1" />
<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" />
</ItemGroup>

View File

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