Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#746 Multiple values in static claims #2131

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Ocelot/Authorization/ClaimsAuthorizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ClaimsAuthorizer(IClaimsParser claimsParser)

public Response<bool> Authorize(
ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
Dictionary<string, string[]> routeClaimsRequirement,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Dictionary<string, string[]> routeClaimsRequirement,
IDictionary<string, string[]> routeClaimsRequirement,

List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
)
{
Expand All @@ -32,7 +32,7 @@ List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
if (values.Data != null)
{
// dynamic claim
var match = Regex.Match(required.Value, @"^{(?<variable>.+)}$");
var match = Regex.Match(required.Value!.FirstOrDefault() ?? string.Empty, "^{(?<variable>.+)}$");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not your code, I agree, Maybe we could use a compiled Regex and enhance security by specifying a timeout, 10 seconds...

[GeneratedRegex("^{(?<variable>.+)}$", RegexOptions.Compiled, 10000)]
private static partial Regex DynamicClaimRegex();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in GenerateRegex, RegexOptions.Compiled is ignored

Copy link
Member

@raman-m raman-m Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have well known open PR by Mohsen who followed Regex best practices, see #1348

if (match.Success)
{
var variableName = match.Captures[0].Value;
Expand Down Expand Up @@ -67,7 +67,7 @@ List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
else
{
// static claim
var authorized = values.Data.Contains(required.Value);
var authorized = required.Value.Any(x=> values.Data.Contains(x));
if (!authorized)
{
return new ErrorResponse<bool>(new ClaimValueNotAuthorizedError(
Expand Down
4 changes: 2 additions & 2 deletions src/Ocelot/Authorization/IClaimsAuthorizer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Responses;
using Ocelot.Responses;
using System.Security.Claims;

namespace Ocelot.Authorization
Expand All @@ -8,7 +8,7 @@ public interface IClaimsAuthorizer
{
Response<bool> Authorize(
ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
Dictionary<string, string[]> routeClaimsRequirement,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Dictionary<string, string[]> routeClaimsRequirement,
IDictionary<string, string[]> routeClaimsRequirement,

List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class DownstreamRouteBuilder
private bool _isAuthenticated;
private List<ClaimToThing> _claimsToHeaders;
private List<ClaimToThing> _claimToClaims;
private Dictionary<string, string> _routeClaimRequirement;
private Dictionary<string, string[]> _routeClaimRequirement;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private Dictionary<string, string[]> _routeClaimRequirement;
private IDictionary<string, string[]> _routeClaimRequirement;

private bool _isAuthorized;
private List<ClaimToThing> _claimToQueries;
private List<ClaimToThing> _claimToDownstreamPath;
Expand Down Expand Up @@ -126,7 +126,7 @@ public DownstreamRouteBuilder WithClaimsToClaims(List<ClaimToThing> input)
return this;
}

public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary<string, string> input)
public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary<string, string[]> input)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public DownstreamRouteBuilder WithRouteClaimsRequirement(Dictionary<string, string[]> input)
public DownstreamRouteBuilder WithRouteClaimsRequirement(IDictionary<string, string[]> input)

{
_routeClaimRequirement = input;
return this;
Expand Down
6 changes: 3 additions & 3 deletions src/Ocelot/Configuration/DownstreamRoute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.Creator;
using Ocelot.Values;

namespace Ocelot.Configuration
Expand All @@ -23,7 +23,7 @@ public DownstreamRoute(
CacheOptions cacheOptions,
LoadBalancerOptions loadBalancerOptions,
RateLimitOptions rateLimitOptions,
Dictionary<string, string> routeClaimsRequirement,
Dictionary<string, string[]> routeClaimsRequirement,
List<ClaimToThing> claimsToQueries,
List<ClaimToThing> claimsToHeaders,
List<ClaimToThing> claimsToClaims,
Expand Down Expand Up @@ -99,7 +99,7 @@ public DownstreamRoute(
public CacheOptions CacheOptions { get; }
public LoadBalancerOptions LoadBalancerOptions { get; }
public RateLimitOptions RateLimitOptions { get; }
public Dictionary<string, string> RouteClaimsRequirement { get; }
public Dictionary<string, string[]> RouteClaimsRequirement { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public Dictionary<string, string[]> RouteClaimsRequirement { get; }
public IDictionary<string, string[]> RouteClaimsRequirement { get; }

public List<ClaimToThing> ClaimsToQueries { get; }
public List<ClaimToThing> ClaimsToHeaders { get; }
public List<ClaimToThing> ClaimsToClaims { get; }
Expand Down
90 changes: 45 additions & 45 deletions src/Ocelot/Configuration/File/FileRoute.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
using Ocelot.Configuration.Creator;
namespace Ocelot.Configuration.File
using Ocelot.Configuration.Creator;

namespace Ocelot.Configuration.File
{
public class FileRoute : IRoute, ICloneable
{
public FileRoute()
{
AddClaimsToRequest = new Dictionary<string, string>();
AddHeadersToRequest = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
AuthenticationOptions = new FileAuthenticationOptions();
ChangeDownstreamPathTemplate = new Dictionary<string, string>();
DelegatingHandlers = new List<string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
DownstreamHostAndPorts = new List<FileHostAndPort>();
FileCacheOptions = new FileCacheOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
LoadBalancerOptions = new FileLoadBalancerOptions();
Metadata = new Dictionary<string, string>();
Priority = 1;
QoSOptions = new FileQoSOptions();
RateLimitOptions = new FileRateLimitRule();
RouteClaimsRequirement = new Dictionary<string, string>();
SecurityOptions = new FileSecurityOptions();
public class FileRoute : IRoute, ICloneable
{
public FileRoute()
{
AddClaimsToRequest = new Dictionary<string, string>();
AddHeadersToRequest = new Dictionary<string, string>();
AddQueriesToRequest = new Dictionary<string, string>();
AuthenticationOptions = new FileAuthenticationOptions();
ChangeDownstreamPathTemplate = new Dictionary<string, string>();
DelegatingHandlers = new List<string>();
DownstreamHeaderTransform = new Dictionary<string, string>();
DownstreamHostAndPorts = new List<FileHostAndPort>();
FileCacheOptions = new FileCacheOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
LoadBalancerOptions = new FileLoadBalancerOptions();
Metadata = new Dictionary<string, string>();
Priority = 1;
QoSOptions = new FileQoSOptions();
RateLimitOptions = new FileRateLimitRule();
RouteClaimsRequirement = new Dictionary<string, string[]>();
SecurityOptions = new FileSecurityOptions();
UpstreamHeaderTemplates = new Dictionary<string, string>();
UpstreamHeaderTransform = new Dictionary<string, string>();
UpstreamHttpMethod = new List<string>();
UpstreamHeaderTransform = new Dictionary<string, string>();
UpstreamHttpMethod = new List<string>();
}

public FileRoute(FileRoute from)
Expand All @@ -44,30 +44,30 @@ public FileRoute(FileRoute from)
public List<FileHostAndPort> DownstreamHostAndPorts { get; set; }
public string DownstreamHttpMethod { get; set; }
public string DownstreamHttpVersion { get; set; }
/// <summary>The <see cref="HttpVersionPolicy"/> enum specifies behaviors for selecting and negotiating the HTTP version for a request.</summary>
/// <value>A <see langword="string" /> value of defined <see cref="VersionPolicies"/> constants.</value>
/// <remarks>
/// Related to the <see cref="DownstreamHttpVersion"/> property.
/// <list type="bullet">
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpversionpolicy">HttpVersionPolicy Enum</see></item>
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.httpversion">HttpVersion Class</see></item>
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httprequestmessage.versionpolicy">HttpRequestMessage.VersionPolicy Property</see></item>
/// </list>
/// </remarks>

/// <summary>The <see cref="HttpVersionPolicy"/> enum specifies behaviors for selecting and negotiating the HTTP version for a request.</summary>
/// <value>A <see langword="string" /> value of defined <see cref="VersionPolicies"/> constants.</value>
/// <remarks>
/// Related to the <see cref="DownstreamHttpVersion"/> property.
/// <list type="bullet">
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpversionpolicy">HttpVersionPolicy Enum</see></item>
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.httpversion">HttpVersion Class</see></item>
/// <item><see href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httprequestmessage.versionpolicy">HttpRequestMessage.VersionPolicy Property</see></item>
/// </list>
/// </remarks>
public string DownstreamHttpVersionPolicy { get; set; }
public string DownstreamPathTemplate { get; set; }
public string DownstreamScheme { get; set; }
public string DownstreamScheme { get; set; }
public FileCacheOptions FileCacheOptions { get; set; }
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }
public string Key { get; set; }
public FileLoadBalancerOptions LoadBalancerOptions { get; set; }
public IDictionary<string, string> Metadata { get; set; }
public IDictionary<string, string> Metadata { get; set; }
public int Priority { get; set; }
public FileQoSOptions QoSOptions { get; set; }
public FileRateLimitRule RateLimitOptions { get; set; }
public string RequestIdKey { get; set; }
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
public Dictionary<string, string[]> RouteClaimsRequirement { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public Dictionary<string, string[]> RouteClaimsRequirement { get; set; }
public IDictionary<string, string[]> RouteClaimsRequirement { get; set; }

public bool RouteIsCaseSensitive { get; set; }
public FileSecurityOptions SecurityOptions { get; set; }
public string ServiceName { get; set; }
Expand Down Expand Up @@ -103,14 +103,14 @@ public static void DeepCopy(FileRoute from, FileRoute to)
to.DownstreamHostAndPorts = from.DownstreamHostAndPorts.Select(x => new FileHostAndPort(x)).ToList();
to.DownstreamHttpMethod = from.DownstreamHttpMethod;
to.DownstreamHttpVersion = from.DownstreamHttpVersion;
to.DownstreamHttpVersionPolicy = from.DownstreamHttpVersionPolicy;
to.DownstreamHttpVersionPolicy = from.DownstreamHttpVersionPolicy;
to.DownstreamPathTemplate = from.DownstreamPathTemplate;
to.DownstreamScheme = from.DownstreamScheme;
to.DownstreamScheme = from.DownstreamScheme;
to.FileCacheOptions = new(from.FileCacheOptions);
to.HttpHandlerOptions = new(from.HttpHandlerOptions);
to.Key = from.Key;
to.LoadBalancerOptions = new(from.LoadBalancerOptions);
to.Metadata = new Dictionary<string, string>(from.Metadata);
to.Metadata = new Dictionary<string, string>(from.Metadata);
to.Priority = from.Priority;
to.QoSOptions = new(from.QoSOptions);
to.RateLimitOptions = new(from.RateLimitOptions);
Expand All @@ -127,5 +127,5 @@ public static void DeepCopy(FileRoute from, FileRoute to)
to.UpstreamHttpMethod = new(from.UpstreamHttpMethod);
to.UpstreamPathTemplate = from.UpstreamPathTemplate;
}
}
}
}
}
6 changes: 3 additions & 3 deletions test/Ocelot.AcceptanceTests/AuthorizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void should_return_response_200_authorizing_route()
},
RouteClaimsRequirement =
{
{"UserType", "registered"},
{"UserType",new []{ "registered" }},
},
},
},
Expand Down Expand Up @@ -134,7 +134,7 @@ public void should_return_response_403_authorizing_route()
},
RouteClaimsRequirement =
{
{"UserType", "registered"},
{"UserType",new []{ "registered" }},
},
},
},
Expand Down Expand Up @@ -266,7 +266,7 @@ public void should_fix_issue_240()
},
RouteClaimsRequirement =
{
{"Role", "User"},
{"Role",new []{ "User" }},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ private void GivenTheAuthServiceReturns(Response<bool> expected)
_authService
.Setup(x => x.Authorize(
It.IsAny<ClaimsPrincipal>(),
It.IsAny<Dictionary<string, string>>(),
It.IsAny<Dictionary<string, string[]>>(),
It.IsAny<List<PlaceholderNameAndValue>>()))
.Returns(expected);
}

private void ThenTheAuthServiceIsCalledCorrectly()
{
_authService.Verify(
x => x.Authorize(It.IsAny<ClaimsPrincipal>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<List<PlaceholderNameAndValue>>()),
x => x.Authorize(It.IsAny<ClaimsPrincipal>(), It.IsAny<Dictionary<string, string[]>>(), It.IsAny<List<PlaceholderNameAndValue>>()),
Times.Once);
}
}
Expand Down
46 changes: 31 additions & 15 deletions test/Ocelot.UnitTests/Authorization/ClaimsAuthorizerTests.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using Ocelot.Authorization;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses;
using System.Security.Claims;
using Ocelot.Responses;
using System.Security.Claims;

namespace Ocelot.UnitTests.Authorization
{
public class ClaimsAuthorizerTests : UnitTest
{
private readonly ClaimsAuthorizer _claimsAuthorizer;
private ClaimsPrincipal _claimsPrincipal;
private Dictionary<string, string> _requirement;
private Dictionary<string, string[]> _requirement;
private List<PlaceholderNameAndValue> _urlPathPlaceholderNameAndValues;
private Response<bool> _result;

Expand All @@ -26,9 +26,9 @@ public void should_authorize_user()
{
new("UserType", "registered"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{"UserType", "registered"},
{"UserType",new []{ "registered" }},
}))
.When(x => x.WhenICallTheAuthorizer())
.Then(x => x.ThenTheUserIsAuthorized())
Expand All @@ -42,9 +42,9 @@ public void should_authorize_dynamic_user()
{
new("userid", "14"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{"userid", "{userId}"},
{"userid",new []{ "{userId}" }},
}))
.And(x => x.GivenAPlaceHolderNameAndValueList(new List<PlaceholderNameAndValue>
{
Expand All @@ -62,9 +62,9 @@ public void should_not_authorize_dynamic_user()
{
new("userid", "15"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{"userid", "{userId}"},
{"userid", new []{ "{userId}" }},
}))
.And(x => x.GivenAPlaceHolderNameAndValueList(new List<PlaceholderNameAndValue>
{
Expand All @@ -74,7 +74,7 @@ public void should_not_authorize_dynamic_user()
.Then(x => x.ThenTheUserIsntAuthorized())
.BDDfy();
}

[Fact]
public void should_authorize_user_multiple_claims_of_same_type()
{
Expand All @@ -83,9 +83,25 @@ public void should_authorize_user_multiple_claims_of_same_type()
new("UserType", "guest"),
new("UserType", "registered"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{"UserType", new []{ "registered" }},
}))
.When(x => x.WhenICallTheAuthorizer())
.Then(x => x.ThenTheUserIsAuthorized())
.BDDfy();
}

[Fact]
public void should_authorize_user_with_route_having_multiple_claims_requirement()
{
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new("UserType", "registered"),
}))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{"UserType", "registered"},
{"UserType", new []{ "non-admin","registered" }},
}))
.When(x => x.WhenICallTheAuthorizer())
.Then(x => x.ThenTheUserIsAuthorized())
Expand All @@ -96,9 +112,9 @@ public void should_authorize_user_multiple_claims_of_same_type()
public void should_not_authorize_user()
{
this.Given(x => x.GivenAClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>()))))
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string>
.And(x => x.GivenARouteClaimsRequirement(new Dictionary<string, string[]>
{
{ "UserType", "registered" },
{ "UserType",new []{ "registered" }},
}))
.When(x => x.WhenICallTheAuthorizer())
.Then(x => x.ThenTheUserIsntAuthorized())
Expand All @@ -110,7 +126,7 @@ private void GivenAClaimsPrincipal(ClaimsPrincipal claimsPrincipal)
_claimsPrincipal = claimsPrincipal;
}

private void GivenARouteClaimsRequirement(Dictionary<string, string> requirement)
private void GivenARouteClaimsRequirement(Dictionary<string, string[]> requirement)
{
_requirement = requirement;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public DownstreamRouteExtensionsTests()
new CacheOptions(0, null, null, null),
new LoadBalancerOptions(null, null, 0),
new RateLimitOptions(false, null, null, false, null, null, null, 0),
new Dictionary<string, string>(),
new Dictionary<string, string[]>(),
new List<ClaimToThing>(),
new List<ClaimToThing>(),
new List<ClaimToThing>(),
Expand Down
Loading