Skip to content

Commit a23ffc1

Browse files
committed
OpenIddict implementation, role section, override scope and role key
"ScopeKey": "oi_scp" to enable OpenIddict "RequiredRole": [ "User" ]
1 parent 3ef6abd commit a23ffc1

12 files changed

+274
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using System.Security.Claims;
3+
using Ocelot.Responses;
4+
5+
namespace Ocelot.Authorization
6+
{
7+
public interface IRolesAuthorizer
8+
{
9+
Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeRequiredRole, string roleKey);
10+
}
11+
}

src/Ocelot/Authorization/IScopesAuthorizer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ namespace Ocelot.Authorization
77

88
public interface IScopesAuthorizer
99
{
10-
Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes);
10+
Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes, string scopeKey);
1111
}
1212
}

src/Ocelot/Authorization/Middleware/AuthorizationMiddleware.cs

+33-2
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ public class AuthorizationMiddleware : OcelotMiddleware
1313
private readonly RequestDelegate _next;
1414
private readonly IClaimsAuthorizer _claimsAuthorizer;
1515
private readonly IScopesAuthorizer _scopesAuthorizer;
16+
private readonly IRolesAuthorizer _rolesAuthorizer;
1617

1718
public AuthorizationMiddleware(RequestDelegate next,
1819
IClaimsAuthorizer claimsAuthorizer,
1920
IScopesAuthorizer scopesAuthorizer,
21+
IRolesAuthorizer rolesAuthorizer,
2022
IOcelotLoggerFactory loggerFactory)
2123
: base(loggerFactory.CreateLogger<AuthorizationMiddleware>())
2224
{
2325
_next = next;
2426
_claimsAuthorizer = claimsAuthorizer;
2527
_scopesAuthorizer = scopesAuthorizer;
2628
}
27-
29+
// Note roles is a duplicate of scopes - should refactor based on type
30+
// Note scopes and roles are processed as OR
31+
// todo create logic to process policies that we use in the API
2832
public async Task Invoke(HttpContext httpContext)
2933
{
3034
var downstreamRoute = httpContext.Items.DownstreamRoute();
@@ -33,7 +37,7 @@ public async Task Invoke(HttpContext httpContext)
3337
{
3438
Logger.LogInformation("route is authenticated scopes must be checked");
3539

36-
var authorized = _scopesAuthorizer.Authorize(httpContext.User, downstreamRoute.AuthenticationOptions.AllowedScopes);
40+
var authorized = _scopesAuthorizer.Authorize(httpContext.User, downstreamRoute.AuthenticationOptions.AllowedScopes, downstreamRoute.AuthenticationOptions.ScopeKey);
3741

3842
if (authorized.IsError)
3943
{
@@ -56,6 +60,33 @@ public async Task Invoke(HttpContext httpContext)
5660
}
5761
}
5862

63+
if (!IsOptionsHttpMethod(httpContext) && IsAuthenticatedRoute(downstreamRoute))
64+
{
65+
Logger.LogInformation("route and scope is authenticated role must be checked");
66+
67+
var authorizedRole = _rolesAuthorizer.Authorize(httpContext.User, downstreamRoute.AuthenticationOptions.RequiredRole, downstreamRoute.AuthenticationOptions.RoleKey);
68+
69+
if (authorizedRole.IsError)
70+
{
71+
Logger.LogWarning("error authorizing user roles");
72+
73+
httpContext.Items.UpsertErrors(authorizedRole.Errors);
74+
return;
75+
}
76+
77+
if (IsAuthorized(authorizedRole))
78+
{
79+
Logger.LogInformation("user has the required role and is authorized calling next authorization checks");
80+
}
81+
else
82+
{
83+
Logger.LogWarning("user does not have the required role and is not authorized setting pipeline error");
84+
85+
httpContext.Items.SetError(new UnauthorizedError(
86+
$"{httpContext.User.Identity.Name} unable to access {downstreamRoute.UpstreamPathTemplate.OriginalValue}"));
87+
}
88+
}
89+
5990
if (!IsOptionsHttpMethod(httpContext) && IsAuthorizedRoute(downstreamRoute))
6091
{
6192
Logger.LogInformation("route is authorized");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Security.Claims;
4+
using Ocelot.Infrastructure.Claims.Parser;
5+
using Ocelot.Responses;
6+
7+
namespace Ocelot.Authorization
8+
{
9+
public class RolesAuthorizer : IRolesAuthorizer
10+
{
11+
private readonly IClaimsParser _claimsParser;
12+
13+
public RolesAuthorizer(IClaimsParser claimsParser)
14+
{
15+
_claimsParser = claimsParser;
16+
}
17+
18+
public Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeRequiredRole, string roleKey)
19+
{
20+
if (routeRequiredRole == null || routeRequiredRole.Count == 0)
21+
{
22+
return new OkResponse<bool>(true);
23+
}
24+
25+
roleKey ??= "role";
26+
27+
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, roleKey);
28+
29+
if (values.IsError)
30+
{
31+
return new ErrorResponse<bool>(values.Errors);
32+
}
33+
34+
var userRoles = values.Data;
35+
36+
var matchedRoles = routeRequiredRole.Intersect(userRoles).ToList(); // Note this is an OR
37+
38+
if (matchedRoles.Count == 0)
39+
{
40+
return new ErrorResponse<bool>(
41+
new ScopeNotAuthorizedError($"no one user role: '{string.Join(",", userRoles)}' match with some allowed role: '{string.Join(",", routeRequiredRole)}'"));
42+
}
43+
44+
return new OkResponse<bool>(true);
45+
}
46+
}
47+
}

src/Ocelot/Authorization/ScopesAuthorizer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ namespace Ocelot.Authorization
1010
public class ScopesAuthorizer : IScopesAuthorizer
1111
{
1212
private readonly IClaimsParser _claimsParser;
13-
private readonly string _scope = "scope";
1413

1514
public ScopesAuthorizer(IClaimsParser claimsParser)
1615
{
1716
_claimsParser = claimsParser;
1817
}
1918

20-
public Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes)
19+
public Response<bool> Authorize(ClaimsPrincipal claimsPrincipal, List<string> routeAllowedScopes, string scopeKey)
2120
{
2221
if (routeAllowedScopes == null || routeAllowedScopes.Count == 0)
2322
{
2423
return new OkResponse<bool>(true);
2524
}
2625

27-
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope);
26+
scopeKey ??= "scope";
27+
var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, scopeKey);
2828

2929
if (values.IsError)
3030
{

src/Ocelot/Configuration/AuthenticationOptions.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ namespace Ocelot.Configuration
44
{
55
public class AuthenticationOptions
66
{
7-
public AuthenticationOptions(List<string> allowedScopes, string authenticationProviderKey)
7+
public AuthenticationOptions(List<string> allowedScopes, List<string> requiredRole, string authenticationProviderKey, string scopeKey, string roleKey, string policyName)
88
{
9+
PolicyName = policyName;
910
AllowedScopes = allowedScopes;
11+
RequiredRole = requiredRole;
1012
AuthenticationProviderKey = authenticationProviderKey;
13+
ScopeKey = scopeKey;
14+
RoleKey = roleKey;
1115
}
1216

1317
public List<string> AllowedScopes { get; private set; }
1418
public string AuthenticationProviderKey { get; private set; }
19+
public List<string> RequiredRole { get; private set; }
20+
public string ScopeKey { get; private set; }
21+
public string RoleKey { get; private set; }
22+
public string PolicyName { get; private set; }
1523
}
16-
}
24+
}

src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs

+29-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ namespace Ocelot.Configuration.Builder
55
public class AuthenticationOptionsBuilder
66
{
77
private List<string> _allowedScopes = new List<string>();
8+
private List<string> _requiredRole = new List<string>();
89
private string _authenticationProviderKey;
10+
private string _roleKey;
11+
private string _scopeKey;
12+
private string _policyName;
13+
914

1015
public AuthenticationOptionsBuilder WithAllowedScopes(List<string> allowedScopes)
1116
{
1217
_allowedScopes = allowedScopes;
1318
return this;
19+
}
20+
21+
public AuthenticationOptionsBuilder WithRequiredRole(List<string> requiredRole)
22+
{
23+
_requiredRole = requiredRole;
24+
return this;
1425
}
1526

1627
public AuthenticationOptionsBuilder WithAuthenticationProviderKey(string authenticationProviderKey)
@@ -19,9 +30,25 @@ public AuthenticationOptionsBuilder WithAuthenticationProviderKey(string authent
1930
return this;
2031
}
2132

33+
public AuthenticationOptionsBuilder WithRoleKey(string roleKey)
34+
{
35+
_roleKey = roleKey;
36+
return this;
37+
}
38+
39+
public AuthenticationOptionsBuilder WithScopeKey(string scopeKey)
40+
{
41+
_scopeKey = scopeKey;
42+
return this;
43+
}
44+
public AuthenticationOptionsBuilder WithPolicyName(string policyName)
45+
{
46+
_policyName = policyName;
47+
return this;
48+
}
2249
public AuthenticationOptions Build()
2350
{
24-
return new AuthenticationOptions(_allowedScopes, _authenticationProviderKey);
51+
return new AuthenticationOptions(_allowedScopes, _requiredRole, _authenticationProviderKey, _scopeKey, _roleKey, _policyName);
2552
}
2653
}
27-
}
54+
}

src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator
66
{
77
public AuthenticationOptions Create(FileRoute route)
88
{
9-
return new AuthenticationOptions(route.AuthenticationOptions.AllowedScopes, route.AuthenticationOptions.AuthenticationProviderKey);
9+
return new AuthenticationOptions(route.AuthenticationOptions.AllowedScopes, route.AuthenticationOptions.RequiredRole, route.AuthenticationOptions.AuthenticationProviderKey, route.AuthenticationOptions.ScopeKey, route.AuthenticationOptions.RoleKey, route.AuthenticationOptions.PolicyName);
1010
}
1111
}
1212
}

src/Ocelot/Configuration/File/FileAuthenticationOptions.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,35 @@ public class FileAuthenticationOptions
99
public FileAuthenticationOptions()
1010
{
1111
AllowedScopes = new List<string>();
12+
RequiredRole = new List<string>();
1213
}
1314

1415
public string AuthenticationProviderKey { get; set; }
1516
public List<string> AllowedScopes { get; set; }
17+
public List<string> RequiredRole { get; set; }
18+
public string ScopeKey { get; set; }
19+
public string RoleKey { get; set; }
20+
public string PolicyName { get; set; }
1621

1722
public override string ToString()
1823
{
1924
var sb = new StringBuilder();
2025
sb.Append($"{nameof(AuthenticationProviderKey)}:{AuthenticationProviderKey},{nameof(AllowedScopes)}:[");
2126
sb.AppendJoin(',', AllowedScopes);
2227
sb.Append("]");
28+
sb.Append($",{nameof(RequiredRole)}:[");
29+
sb.AppendJoin(',', RequiredRole);
30+
sb.Append("]");
31+
sb.Append($",{nameof(ScopeKey)}:[");
32+
sb.AppendJoin(',', ScopeKey);
33+
sb.Append("]");
34+
sb.Append($",{nameof(RoleKey)}:[");
35+
sb.AppendJoin(',', RoleKey);
36+
sb.Append("]");
37+
sb.Append($",{nameof(PolicyName)}:[");
38+
sb.AppendJoin(',', PolicyName);
39+
sb.Append("]");
2340
return sb.ToString();
2441
}
2542
}
26-
}
43+
}

test/Ocelot.UnitTests/Authorization/AuthorizationMiddlewareTests.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ public class AuthorizationMiddlewareTests
2727
private readonly AuthorizationMiddleware _middleware;
2828
private RequestDelegate _next;
2929
private HttpContext _httpContext;
30+
private readonly Mock<IRolesAuthorizer> _authRolesService;
3031

3132
public AuthorizationMiddlewareTests()
3233
{
3334
_httpContext = new DefaultHttpContext();
3435
_authService = new Mock<IClaimsAuthorizer>();
3536
_authScopesService = new Mock<IScopesAuthorizer>();
37+
_authRolesService = new Mock<IRolesAuthorizer>();
3638
_loggerFactory = new Mock<IOcelotLoggerFactory>();
3739
_logger = new Mock<IOcelotLogger>();
3840
_loggerFactory.Setup(x => x.CreateLogger<AuthorizationMiddleware>()).Returns(_logger.Object);
3941
_next = context => Task.CompletedTask;
40-
_middleware = new AuthorizationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object);
42+
_middleware = new AuthorizationMiddleware(_next, _authService.Object, _authScopesService.Object,_authRolesService.Object, _loggerFactory.Object);
4143
}
4244

4345
[Fact]

0 commit comments

Comments
 (0)