.NET Declarative Security: Why is SecurityAction.Deny impossible to work with?
- by rally25rs
I've been messing with this for about a day and a half now sifting through .NET reflector and MSDN docs, and can't figure anything out...
As it stands in the .NET framework, you can demand that the current Principal belong to a role to be able to execute a method by marking a method like this:
[PrincipalPermission(SecurityAction.Demand, Role = "CanEdit")]
public void Save() { ... }
I am working with an existing security model that already has a "ReadOnly" role defined, so I need to do exactly the opposite of above... block the Save() method if a user is in the "ReadOnly" role. No problem, right? just flip the SecurityAction to .Deny:
[PrincipalPermission(SecurityAction.Deny, Role = "ReadOnly")]
public void Save() { ... }
Well, it turns out that this does nothing at all. The method still runs fine. It seems that the PrincipalPermissionAttribute defines:
public override IPermission CreatePermission()
But when the attribute is set to SecurityAction.Deny, this method is never called, so no IPermission object is ever created. Does anyone know of a way to get .Deny to work? I've been trying to make a custom secutiry attribute, but even that doesn't work. I tried to get tricky and do:
public class MyPermissionAttribute : CodeAccessSecurityAttribute
{
private SecurityAction securityAction;
public MyPermissionAttribute(SecurityAction action)
: base(SecurityAction.Demand)
{
if (action != SecurityAction.Demand && action != SecurityAction.Deny)
throw new ArgumentException("Unsupported SecurityAction. Only Demand and Deny are supported.");
this.securityAction = action;
}
public override IPermission CreatePermission()
{
// do something based on the SecurityAction...
}
}
Notice my attribute constructor always passes SecurityAction.Demand, which is the one action that would work previously.
However, even in this case, the CreatePermission() method is still only called when the attribute is set to .Demand, and not .Deny! Maybe the runtime is actually checking the attribute instead of the SecurityAction passed to the CodeAccessSecurityAttribute constructor?
I'm not sure what else to try here... anyone have any ideas? You wouldn't think it would be that hard to deny method access based on a role, instead of only demanding it. It really disturbed me that the default PrincipalPermission appears from within an IDE like it would be just fine doing a .Deny, and there is like a 1-liner in the MSDN docs that hint that it won't work. You would think the PrincipalPermissionAttribute constructor would throw an exception immediately if anything other that .Demand is specified, since that could create a big security hole! I never would have realized that .Deny does nothing at all if I hadn't been unit testing!
Again, all this stems from having to deal with an existing security model that has a "ReadOnly" role that needs to be denied access, instead of doing it the other way around, where I cna just grant access to a role.
Thanks for any help!
Quick followup:
I can actually make my custom attribute work by doing this:
public class MyPermissionAttribute : CodeAccessSecurityAttribute
{
public SecurityAction SecurityAction { get; set; }
public MyPermissionAttribute(SecurityAction action)
: base(action)
{
}
public override IPermission CreatePermission()
{
switch(this.SecurityAction) { ... } // check Demand or Deny
}
}
And decorating the method:
[MyPermission(SecurityAction.Demand, SecurityAction = SecurityAction.Deny, Role = "ReadOnly")]
public void Save() { ... }
But that is terribly ugly, since I'm specifying both Demand and Deny in the same attribute. But it does work...
Another interesting note: My custom class extends CodeAccessSecurityAttribute, which in turn only extends SecurityAttribute. If I cnage my custom class to directly extend SecurityAttribute, then nothing at all works. So it seems the runtime is definately looking for only CodeAccessSecurityAttribute instances in the metadata, and does something funny with the SecurityAction specified, even if a custom constructor overrides it.