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
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:
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!
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.
Sounds like an excellent idea, especially if you're having a bunch of new people using Csla. Good luck!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!
Copyright (c) Marimer LLC