Authorization rules on methods

Authorization rules on methods

Old forum URL: forums.lhotka.net/forums/t/1941.aspx


edika2000 posted on Monday, December 11, 2006

Hi all,

I would like to specify wich roles are enabled to use or not each public methods (that execute CommandBase) on my classes.
Someone can suggest me a way to do it without  the use of the authorization rules of the properties?
Thanks.

Edika

ajj3085 replied on Monday, December 11, 2006

Usually you check the prinical's role and throw a securityexception if teh user doesn't have the proper role(s).

SonOfPirate replied on Monday, December 11, 2006

We added a "CanExecuteMethod" method to our base class(es) that behaves just like the CanReadProperty and CanWriteProperty methods.  We added AllowExecute and DenyExecute methods along with IsExecuteAllowed and IsExecuteDenied properties to the AuthorizationRules collection.  This allowed us to implement these checks in a consistent manner as with the properties.  They behave in the same way using the method name instead of the property name as an argument for the various method and property calls.

HTH

 

edika2000 replied on Monday, December 11, 2006

Thanks, this is what I'd wonder to do but can you post an example of how you've implmented this?

Edika

ajj3085 replied on Monday, December 11, 2006

That's actually a great idea.

ChristianPena replied on Monday, December 11, 2006

That's a nice clean solution. It's a good example of where inheriting from your own base classes really brings benefit as well.

ajj3085 replied on Monday, December 11, 2006

I hope Pirate posts and gives more insight.  I started adding this and although it so far seems easy to do, I went and added some extensions to AuthRulesManager, AuthRules and ValidationRules via partial classes.  Basically role lists are totally seperate between proprties and methods (and obviously you have ExecuteDeny or grant only, not four different combinations).

SonOfPirate replied on Monday, December 11, 2006

It actually was as easy at it sounds - at least they way I did it.  I did not separate properties from methods internally because the name already makes then unique within each object, so I didn't think this was necessary.  This allowed me to simply patterns another set of methods around what was already there for properties.

For strictly sematical reasons, I renamed the RolesForProperty class to RolesForMember and changed all applicable code referring to it (actually, the built-in refactoring on rename did it for me!).  Inside that class, I added private variables with the same form and syntax as the existing four for _executeAllowed and _executeDenied along with corresponding properties and IsExecuteAllowed and IsExecuteDenied methods.  So the end result looked like this (I'll spare you the comments):

/// <summary>
/// Maintains a list of allowed and denied user roles for a specific class member.
/// </summary>
[Serializable()]
internal class RolesForMember
{
    private List<string> _executeAllowed = new List<string>();
    private List<string> _executeDenied = new List<string>();
    private List<string> _readAllowed = new List<string>();
    private List<string> _readDenied = new List<string>();
    private List<string> _writeAllowed = new List<string>();
    private List<string> _writeDenied = new List<string>();
   
    public List<string> ExecuteAllowed
    {
        get { return _executeAllowed; }
    }
   
    public List<string> ExecuteDenied
    {
        get { return _executeDenied; }
    }
   
    public List<string> ReadAllowed
    {
        get { return _readAllowed; }
    }
   
    public List<string> ReadDenied
    {
        get { return _readDenied; }
    }
   
    public List<string> WriteAllowed
    {
        get { return _writeAllowed; }
    }
   
    public List<string> WriteDenied
    {
        get { return _writeDenied; }
    }
   
    public bool IsExecuteAllowed(IPrincipal principal)
    {
        foreach (string role in ExecuteAllowed)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
   
    public bool IsExecuteDenied(IPrincipal principal)
    {
        foreach (string role in ExecuteDenied)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
   
    public bool IsReadAllowed(IPrincipal principal)
    {
        foreach (string role in ReadAllowed)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
   
    public bool IsReadDenied(IPrincipal principal)
    {
        foreach (string role in ReadDenied)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
   
    public bool IsWriteAllowed(IPrincipal principal)
    {
        foreach (string role in WriteAllowed)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
   
    public bool IsWriteDenied(IPrincipal principal)
    {
        foreach (string role in WriteDenied)
            if (principal.IsInRole(role))
                return true;
   
        return false;
    }
}

In AuthorizationRules, rather than change GetRolesForProperty to GetRolesForMember, I decided to leave the distinction here because I didn't think it was appropriate to add ExecuteAllowed and ExecuteDenied to the AccessType enumeration. I didn't want to make it seem possible to query GetRolesForProperty(validPropertyName, AccessType.ExecuteAllowed). So, I chose not to extend the AccessType enumeration.

Instead, I added the following method to the AuthorizationRules class:

private RolesForMember GetRolesForMethod(string methodName)
{
    RolesForMember currentRoles = null;
   
    if (!Rules.ContainsKey(methodName))
    {
        currentRoles = new RolesForMember();
        Rules.Add(methodName, currentRoles);
    }
    else
        currentRoles = Rules[methodName];
   
    return currentRoles;
}

Next, I added the following to the AuthorizationRules class to allow rule setting and checking:

public void AllowExecute(string methodName, params string[] roles)
{
    RolesForMember currentRoles = GetRolesForMethod(methodName);
   
    foreach (string item in roles)
        currentRoles.ExecuteAllowed.Add(item);
}
 
public void DenyExecute(string methodName, params string[] roles)
{
    RolesForMember currentRoles = GetRolesForMethod(methodName);
   
    foreach (string item in roles)
        currentRoles.ExecuteDenied.Add(item);
}
 
public bool HasExecuteAllowedRoles(string methodName)
{
    return (GetRolesForMethod(methodName).ExecuteAllowed.Count > 0);
}
 
public bool IsExecuteAllowed(string methodName)
{
    return GetRolesForMethod(methodName).IsExecuteAllowed(ApplicationContext.User);
}
 
public bool HasExecuteDeniedRoles(string methodName)
{
    return (GetRolesForMethod(methodName).ExecuteDenied.Count > 0);
}
 
public bool IsExecuteDenied(string methodName)
{
    return GetRolesForMethod(methodName).IsExecuteDenied(ApplicationContext.User);
}

Finally, the following were added to BusinessBase:

public bool CanExecuteMethod(bool throwOnFalse)
{
    string methodName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Substring(4);
    bool result = CanExecuteMethod(methodName);
   
    if (throwOnFalse && result == false)
        throw new System.Security.SecurityException(String.Format("{0} ({1})", Resources.MethodExecuteNotAllowed, methodName));
   
    return result;
}
   
public bool CanExecuteMethod(string methodName, bool throwOnFalse)
{
    bool result = CanExecuteMethod(methodName);
   
    if (throwOnFalse && result == false)
        throw new System.Security.SecurityException(String.Format("{0} ({1})", Resources.MethodExecuteNotAllowed, methodName));
   
    return result;
}
   
public bool CanExecuteMethod()
{
    string methodName = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().Name.Substring(4);
   
    return CanExecuteMethod(methodName);
}
   
public virtual bool CanExecuteMethod(string methodName)
{
    bool result = true;
   
    if (AuthorizationRules.HasExecuteAllowedRoles(methodName))
    {
        // some users are explicitly granted execute rights
        // in which case all other users are denied.
        if (!AuthorizationRules.IsExecuteAllowed(methodName))
            result = false;
    }
    else if (AuthorizationRules.HasExecuteDeniedRoles(methodName))
    {
        // some users are explicitly denied execute rights
        if (AuthorizationRules.IsExecuteDenied(methodName))
            result = false;
    }
   
    return result;
}

I'm pretty sure that's it.  Hope that answers your questions...

Oh, create the rules in AddAuthorizationRules just like properties and at the start of each applicable method, just call CanExecuteMethod just like CanReadProperty and CanWriteProperty.  That simple.

HTH

ajj3085 replied on Monday, December 11, 2006

Pirate,

Thanks for the code.  I went the seperate route so that you couldn't have Executeallowed / Denied on properties, and allow / deny / read / write for methods.

Also, it helped keep my changes in seperate code files, so that I don't have alot of work on each upgrade (just have to add back the partial keyword).

Andy

willfarnaby replied on Monday, December 11, 2006

ajj3085, can you please post your C# code files (either inline as text, or in an attached RAR or ZIP file if this board supports and allows attachments) for your modified authorization implementation?

Thanks.

ajj3085 replied on Tuesday, December 12, 2006

Not sure if my company would like that.  Its pretty simple though.  All I did was copy the methods / properties for Reading a property into another file (which is the other part of the partial class) and change Read to Execute.  The files are AuthorizationRules.cs, AuthorizationRulesManager.cs.  I also created a copy of RoleForProperty and named it RoleForMethod, making appropriate changes.  Finally I added to the enumeration... you'll be able to find the name.

Then in your business subclass you can add CanExecuteMethod, again pretty much copying and pasting the code for CanReadProperty and making appropriate changes.

SonOfPirate replied on Monday, December 11, 2006

ajj3085:
Pirate,

Thanks for the code.  I went the seperate route so that you couldn't have Executeallowed / Denied on properties, and allow / deny / read / write for methods.

Also, it helped keep my changes in seperate code files, so that I don't have alot of work on each upgrade (just have to add back the partial keyword).

Andy

I opted to have separate methods and not add to the AccessType enumeration to accomplish this.  Realistically, because these methods are all accepting strings, there really isn't a safeguard in place to validate the name being passed.  I've considered making a simply call using Reflection to verify the property and method names are valid.  That would ensure that no properties were passed to the Allow/DenyExecute method and visa versa.

In fact, now that I am thinking about it again, I am going to do just that.  Our framework is being rolled out for team development including an off-shore team, so I'd better bullet-proof it where ever I can!

 

ajj3085 replied on Tuesday, December 12, 2006

SonOfPirate:
I opted to have separate methods and not add to the AccessType enumeration to accomplish this.  Realistically, because these methods are all accepting strings, there really isn't a safeguard in place to validate the name being passed. 


Right, I actually did add to the enumeration though, but it sounds like we have more or less the same code.

SonOfPirate:
I've considered making a simply call using Reflection to verify the property and method names are valid.  That would ensure that no properties were passed to the Allow/DenyExecute method and visa versa.

In fact, now that I am thinking about it again, I am going to do just that.  Our framework is being rolled out for team development including an off-shore team, so I'd better bullet-proof it where ever I can!

Sounds like an excellent idea, especially if you're having a bunch of new people using Csla.  Good luck!

edika2000 replied on Tuesday, December 12, 2006

That's great SonOfPirate, very smart solution. It could be a good idea for Rocky to add it in the next release of the CSLA.
Many thanks

Edika

david.wendelken replied on Monday, December 11, 2006

Please compare notes and come up with a "best of both approaches" set of code.  Then contact Rocky to get permission to post it to the CSLA Contrib project.   It sounds like a great extension to the product, and maybe we'll get him to incorporate it into the base product.  (Particularly if someone ports it to the other language!)

Copyright (c) Marimer LLC