SmartEnum icon indicating copy to clipboard operation
SmartEnum copied to clipboard

Cannot use to decorate attributes

Open mkgn opened this issue 4 years ago • 4 comments

I have been using this successfully and got stuck when trying to use it in an Attribute parameter. Basically I have a class for permissions

public sealed class ToDoSecurityClaims : SecurityClaims<ToDoSecurityClaims>
{
    public static readonly ToDoSecurityClaims Complete = new ToDoSecurityClaims("Complete any todo", "todo.complete");
    private ToDoSecurityClaims(string name, string value) : base(name, value)
    {
    }
}

I have a custom AuthorizeAttribute

    public MultiAuthorizeAttribute(PolicyComparison comparisonType = PolicyComparison.And, params object[] policies) : base(typeof(MultiAuthorizeFilter))
    {
        Arguments = new object[] { policies, comparisonType };
    }

However when decorating such as;

[MultiAuthorize(PolicyComparison.Or, ToDoSecurityClaims.Complete)]

compiler gives an error Severity "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter". I can use nameof(ToDoSecurityClaims.Complete), but then I have to rename these to match my claim name. So the neat name ToDoSecurityClaims.Complete must be something like ToDoSecurityClaims.Todo_Complete. Basically the name of the claim value and the name itself must be same.

Seems like there is no way around this problem isn't it? Seems like I have to go back to official Enums in this case

mkgn avatar Jan 04 '21 15:01 mkgn

You are correct, there is no elegant way around this problem. I was facing the exact same scenario and came up with the following not-so-elegant solution.

Define the values of your claims as constants inside ToDoSecurityClaims. Then use these constants when defining your smart enums:

public sealed class ToDoSecurityClaims : SecurityClaims<ToDoSecurityClaims>
{
    public static class Names 
    {
        public const string CompleteName = "todo.complete";
    }

    public static readonly ToDoSecurityClaims Complete = new ToDoSecurityClaims("Complete any todo", Names.CompleteName);

    private ToDoSecurityClaims(string name, string value) : base(name, value)
    {
    }
}

Next, modify your attribute to accept strings instead of ToDoSecurityClaims. In your constructor, parse the string values into ToDoSecurityClaims.

public MultiAuthorizeAttribute(PolicyComparison comparisonType = PolicyComparison.And, params string[] policies) : base(typeof(MultiAuthorizeFilter))
{
    Arguments = policies.Select(ToDoSecurityClaims.FromValue).ToArray();
}

Finally, changed the usage of your attribute to pass in the string constant rather than the ToDoSecurityClaims:

[MultiAuthorize(PolicyComparison.Or, ToDoSecurityClaims.Names.CompleteName)]

Hope this helps!

mglace avatar Feb 26 '21 19:02 mglace

@mkgn - I just realized that your problem is a little more complicated than I originally thought because your custom attribute has to be able to deal with multiple types of SecurityClaims. I need to rethink my answer.

mglace avatar Feb 27 '21 03:02 mglace

Sorry for late response. Well, I did it like this;

public sealed class ToDoSecurityClaims : SecurityClaims<ToDoSecurityClaims>{
    public static readonly ToDoSecurityClaims todo_complete = new ToDoSecurityClaims("Complete any todo", $"{nameof(todo_complete)}");
    public static readonly ToDoSecurityClaims todo_completeself = new ToDoSecurityClaims("Complete owned todos", $"{nameof(todo_completeself)}");
}

Then in controllers I decorate it as;

    [HttpPost]
    [Route("create")]
    [MultiAuthorize(PolicyComparison.Or, nameof(CoreSecurityClaims.resource_create))]
    public async Task<IActionResult> Create(ToDoDto todoData){
    }

So basically I opted to use nameof().

The other one I am wondering is related to #38 Localization. You have given a link in that thread but I don't understand what you were trying to say.

What I want is to basically be able to translate the description part of enum entry like;

new ToDoSecurityClaims("Complete any todo", $"{nameof(todo_complete)}"); <-- translate "Complete any todo" Since they are defined as static do you have any thoughts about a work around and get them localized?

mkgn avatar Mar 05 '21 16:03 mkgn

You have given a link in that thread but I don't understand what you were trying to say.

I think you have me confused with someone else. I did not comment in thread #38

mglace avatar Mar 05 '21 17:03 mglace