16

I have a custom Attribute called AuthoriseAttribute whose constructor looks like this:

public AuthoriseAttribute(int userId)
{
  .. blah
}

This is used with a method called GetUserDetails() like this:

[Authorise(????????)]
public UserDetailsDto GetUserDetails(int userId)
{
  .. blah
}

At runtime, the presence of the Authorise attribute causes some authorisation code to execute which requires the ID of the user. Obviously, this can be extracted from the parameter of the GetUserDetails() method, but this means that the authorisation code depends on the method's parameter being given a particular name.

I would like to be able to pass in the actual value of the userId parameter into the attribute, so that the authorisation code works with the value passed in to the attribute (i.e. not the method parameter), whose name is known.

Something like this (which doesn't work):

[Authorise(userId)]
public UserDetailsDto GetUserDetails(int userId)
{
  .. blah
}

Is such a thing possible?

Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
David
  • 15,750
  • 22
  • 90
  • 150
  • 7
    No not possible. Attributes are meta data. Parameter values must be a constant value. – vcsjones May 03 '12 at 16:22
  • There is something I don't understand - why would you want to authorize a method parameter? IMO - you might need to authorize the caller of the method - is this correct? – Sunny May 03 '12 at 16:24
  • 2
    What you describe can't be directly done, as I'm sure the compiler error told you. It would be helpful to know how "the presence of the Authorise attribute causes some authorisation code to execute" is working. You should be able to have the code there look at the userId parameter. – Tim S. May 03 '12 at 16:25
  • 1
    Instead of passing user info into each method, you could have your attribute get it by some other means, like the WCF context, or from Active Directory... Then your methods are clean and don't have multiple responsibilities. – Bob Horn May 03 '12 at 16:27
  • Thanks guys. @Tim: I'm using Castle Windsor interception. The presence of the attribute causes the method invocation to be intercepted and allows me to run code before the invocation. I have access to the method info and the attribute info. I need to get the ID of the user (and other stuff, but I'm keeping the question simple), but there's no simple way to tell the interceptor where to find the user ID. It might be passed in to the method as an int parameter called `userId`, or it might be buried deep in some complex type parameter. – David May 03 '12 at 16:30
  • @Bob. Can I be honest? Yes, I can get the user from some injected service. I know that. I just wanted to use a simple example. Go with it for now. But the authorisation code does need to find data in the method parameters that might be expressed in different ways by different methods. For example, one method might accept an int `userId`, whilst another method might accept a complex type with the user's ID buried in it. How do I tell the authorisation code where to find the user's ID? – David May 03 '12 at 16:33

4 Answers4

23

There is a way to do this _in ASP.NET MVC_ with action-methods (not with attributes in general)

public class CustomAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        int userId = (int)filterContext.ActionParameters["userId"];
    }
}
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
17

Making vcsjones' comment an answer, this is not possible.

Attributes are metadata; they are compiled into the assembly at compile-time and do not change during runtime. As such, any parameters you pass into an attribute must be constants; literals, constant variables, compiler defines, etc.

The one way this would work is to make the attribute an AOP element, using a framework like PostSharp or rolling your own with the Unity Framework etc. This would allow you to attach an "interceptor" to the method by decorating it with an attribute, which will then run code in the attribute and will also have knowledge about exactly how the method was called including parameter values. Check out this blog: http://www.progware.org/Blog/post/Interception-and-Interceptors-in-C-(Aspect-oriented-programming).aspx

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • You've stolen vcsjones' answer and I will reward you for it! I do have the AOP stuff up and running; my problem is telling the code in the interceptor where to find the data it needs to run the authorisation logic, since the attribute might be used to decorate multiple methods with different signatures. – David May 03 '12 at 16:35
  • 11
    I wouldn't say it's stealing. +1 for taking my comment and turning it into a useful answer. – vcsjones May 03 '12 at 16:37
  • 1
    You would probably give it this information by giving it the name of the parameter you would use as a string: `[Authorize("userId")]`. When the interceptor you have defined runs, it should be able to examine metadata about the method call that was made to trigger it, and should be able to find the parameter with the specified name and give its value to whatever code should run based on this attribute. – KeithS May 03 '12 at 16:41
  • Thank you. Yes, I thought about that. I'm not sure how I would get that to work if the parameter is a complex type. I would have to define some kind of magic syntax for expressing the location of the required data as a string. Need to give this some thought. – David May 03 '12 at 16:43
  • Well you wouldn't want to define `[Authorize("userId.SomeProp")]`. If you need to know the exact type of the parameter you can specify that as well: `[Authorize("userId", typeof(MyComplexType))]` – KeithS May 03 '12 at 16:46
  • I ended up writing some Reflection code that recursively examines all properties of the parameters looking for one with the specified name and type. – David May 11 '12 at 10:04
0

I was able to get around this by using the following:

public class AuthorizeAttribute
{
    protected bool RequireIdClaim { get; private set; }

    public AuthorizeAttribute(bool requireIdClaim = false)
    {
        RequireIdClaim = requireIdClaim;
    }

    public Authorize() 
    {
        //regular auth stuff here

        if (RequireIdClaim)
        {
            var routeData = context.ActionContext.Request.GetRouteData();
            var requiredIdClaim = Convert.ToInt32(routeData.Values["id"]); 

            //Check here if their user profile has a claim to that Id
        }
    }
}

And then on the specific methods you want to check Ids on,

[HttpGet]
[Route("{id}")]
[Authorize(requireIdClaim: true)]
public UserDetailsDto GetUserDetails(int userId)
{
    .. blah
}

And if you don't care to check their Id, but just that they're authenticated

[HttpGet]
[Route("")]
[Authorize]
public bool isLoggedIn()
{
    .. blah
}

Of course you can organize your authorize procedure however you like but this idea allows you to get their ID in your auth procedure there since it is passed in as route data. More here: https://stackoverflow.com/a/16054886

Brian Davis
  • 745
  • 1
  • 11
  • 14
0

Probably because this is an old post but this is now possible

[MyAttribute(MyAttributeVar= "myMethodVar")]
public void MyMethod(int myMethodVar)