Reputation: 8315
I am trying to check for Claims with an IAuthorizationFilter
implementation. With following code it works just fine -
public class ClaimsAttribute : TypeFilterAttribute
{
public ClaimsAttribute(string claimType, string claimValue) : base(typeof(ClaimsFilter))
{
this.Arguments = new object[] { new Claim(claimType, claimValue) };
}
}
public class ClaimsFilter : IAuthorizationFilter
{
private readonly Claim _claim;
public ClaimsFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(p => p.Type == _claim.Type && p.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
public class DocumentController : Controller
{
[Claims(AppClaimTypes.Permission, "CanReadDocument")]
[HttpGet]
public ActionResult GetDocument()
{
return View();
}
}
So I thought, I might check for multiple Claim values as well -
public class Claims2Attribute : TypeFilterAttribute
{
public Claims2Attribute(string claimType, params string[] claimValues) : base(typeof(Claims2Filter))
{
this.Arguments = new object[claimValues.Length];
for (int i = 0; i < claimValues.Length; i++)
{
this.Arguments[i] = new Claim(claimType, claimValues[i]);
}
}
}
public class Claims2Filter : IAuthorizationFilter
{
private readonly Claim[] _claims;
public Claims2Filter(Claim[] claims)
{
_claims = claims;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool hasClaim = false;
foreach (var item in _claims)
{
hasClaim = context.HttpContext.User.Claims.Any(p => p.Type == item.Type && p.Value == item.Value);
if (hasClaim)
break;
}
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
public class DocumentController : Controller
{
[Claims2(AppClaimTypes.Permission, "CanReadSecretDocument", "SecretAccess")]
[HttpGet]
public ActionResult GetSecretDocument()
{
return View();
}
}
But now it throws error -
InvalidOperationException: A suitable constructor for type 'AuthorizationFilterTest.Claims2Filter' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
I don't think its anything related to registering services, otherwise the first version wouldn't work. It seems something more trivial/silly, but I cannot find it.
What is it that I'm doing wrong?
Upvotes: 3
Views: 1339
Reputation: 8315
Its about how the arguments are passed to instantiate the target IAuthorization
type (in this case Claims2Filter
).
If the Arguments
property (which is an array of object) contains multiple elements, then each element is considered a distinct argument to pass to a distinct parameter of the target IAuthorization
type's constructor. In other words, a one-to-one mapping is required between the number of elements in Argument
and the number of constructor parameter.
So, when the Arguments
contains two Claim object, the runtime is actually expecting Claims2Filter
to have the following constructor -
public ClaimsFilter(Claim claim1, Claim claim2)
{
//
}
To use a constructor (of the IAuthorization
type) that accepts a single parameter which is an array of Claim object, the Arguments
property (of the TypeFilterAttribute
type) should contain a single element which is an array of Claim object.
So, the following change makes everything work as expected -
public class Claims2Attribute : TypeFilterAttribute
{
public Claims2Attribute(string claimType, params string[] claimValues) : base(typeof(Claims2Filter))
{
var claims = new Claim[claimValues.Length];
for (int i = 0; i < claimValues.Length; i++)
{
claims[i] = new Claim(claimType, claimValues[i]);
}
this.Arguments = new object[] { claims };
}
}
I hope the explanation is clear enough.
Upvotes: 4