Question

I'm working on some Role-based security for our app and I essentially want to do customized verison MVC's AuthorizeAttribute - but only at the business logic layer, where we don't link to MVC.

I've looked at PrincipalPermissionAttribute but it seems it doesn't have a way to customize it as it's sealed. I just want to create a custom version where I can check for membership in any of a list of roles without using multiple attributes, and also define where to look for the role membership.

Is there anything like this in .Net that I'm missing? Or does anybody have some insight on how to do this without reimplementing ASP.Net's AuthorizeAttribute/RoleProvider/etc?

EDIT

I currently have a imperative version running, but I'd rather have a declarative-attribute version, as it's easier to see it above the method/class.

Right now I have the following in an abstract base class for my business layer:

protected void EnsureEditorLevelAccess()
{
    var allowedRoles = new[]
                            {
                                Roles.Administrator,
                                Roles.Editor,
                            };

    var roles = GetAccountRoles(GetCurrentUsername());

    if (roles.Any(role => allowedRoles.Contains(role)))
    {
        return;
    }

    throw new SecurityException("You do not have sufficient privileges for this operation.");
}

I like being able to use Roles.Administrator etc because the role names are hideous (Active Directory group based...), so I was thinking of wrapping those details up in the constructor of a custom attribute that I can just plop on top of classes/methods.

GetAccountRoles is just a facade over an injectable role-provider property, which I can set to use either AD or a testing version that uses the database.

I could subclass Attribute, but not sure how it would kick off the security check.

Was it helpful?

Solution

You can create a new attribute that uses the existing PrincipalPermission if that would be sufficient for your needs. If your existing imperative implementation uses PrincipalPermission, then this should be the case. However, if your imperative version does something else, you may need to consider implementing both a custom permission and a corresponding attribute. If you're not sure whether this is necessary, perhaps you could share some details regarding your current imperative approach...


After question update...

It's actually possible to use "any" logic with PrincipalPermission, although it requires unioning of multiple instances, which is not particularly practical to work with in an attribute. This makes it much more reasonable to create a custom attribute, which might look something like the following:

[Serializable]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class AnyRolePermissionAttribute : CodeAccessSecurityAttribute
{
    public AnyRolePermissionAttribute(SecurityAction action)
        : base(action)
    {
    }

    public string Roles { get; set; }

    public override IPermission CreatePermission()
    {
        IList<string> roles = (this.Roles ?? string.Empty).Split(',', ';')
                                .Select(s => s.Trim())
                                .Where(s => s.Length > 0)
                                .Distinct()
                                .ToList();

        IPermission result;
        if (roles.Count == 0)
        {
            result = new PrincipalPermission(null, null, true);
        }
        else
        {
            result = new PrincipalPermission(null, roles[0]);
            for (int i = 1; i < roles.Count; i++)
            {
                result = result.Union(new PrincipalPermission(null, roles[i]));
            }
        }

        return result;
    }
}

Unfortunately, you can't use arrays in security attributes, so the role list has to be represented as a string. e.g.:

[AnyRolePermission(SecurityAction.Demand, Roles = "Foo, Bar")]

You could use it with your constants via design-time concatenation. e.g.:

[AnyRolePermission(SecurityAction.Demand, Roles = Roles.Administrator + ", " + Roles.Editor)]

As for your custom role provider, the appropriate place to use it is in the thread principal, not the permission or attribute. For example, if you're currently using a GenericPrincipal, you could replace it with a custom principal that uses your custom role provider to retrieve the target identity's roles.

OTHER TIPS

You could derive your own CodeAccessSecurityAttribute and implement your logic around the Thread.CurrentPrincipal (http://msdn.microsoft.com/en-us/library/system.security.permissions.codeaccesssecurityattribute.aspx).

essentially, you'd want to verify allowedRoles.Any(r => Thread.CurrentPrincipal.IsInRole(r))

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top