atiyar
atiyar

Reputation: 8315

Checking claims with IAuthorizationFilter - "A suitable constructor for type could not be located"

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

Answers (1)

atiyar
atiyar

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

Related Questions