Vibrant discussion about CSLA .NET and using the framework to build great business applications.
I have a business object and a corresponding database table called Role. In this table I am preloading two default records. The first is named "Administrators" and second is named "Users". I want to allow people to add, edit, and delete roles, except for these default ones. I was wondering how I should go about this. What I was thinking was to create a business rule, but I’m not sure how to add one to handle this particular situation. Any help / advice you can provide would be greatly appreciated.
Hi,
I'd recommend to add a flag (bool) to the Roles table that defines IsSystem.
You can then add an Authorization rule that can prevent delete/edit when a Role has IsSystem == true.
To prevent edit of field you should probably just override CanWriteProperty like this:
public static readonly PropertyInfo<bool> IsSystemProperty = RegisterProperty<bool>(c => c.IsSystem); public bool IsSystem { get { return GetProperty(IsSystemProperty); } set { SetProperty(IsSystemProperty, value); } } public override bool CanWriteProperty(Csla.Core.IPropertyInfo property) { if (IsSystem) return false; return base.CanWriteProperty(property); }
or you could create an authz rule that checks the IsSystem flag and override CanWriteProperty:
public override bool CanWriteProperty(IPropertyInfo propertyName) { if (!Csla.Rules.BusinessRules.HasPermission(AuthorizationActions.EditObject, this)) return false; return base.CanWriteProperty(property); }
Jonny Bekkum, Norway CslaContrib Coordinator
Thank you very much for your help and for your quick response. I will try this out today.
Could you show me what the authorization rule would like like for preventing them from deleting and editing a role that has IsSystem == true?
Pretty muuch like this:
public class IsSystem : Csla.Rules.AuthorizationRule { private IPropertyInfo SystemField { get; set; } public IsSystem(AuthorizationActions action, IMemberInfo element, IPropertyInfo systemField) : base(action, element) { SystemField = systemField; } public override bool CacheResult { get { return false; } } protected override void Execute(AuthorizationContext context) { if (context.Target == null) { context.HasPermission = true; return; } //var isSystem = (bool)MethodCaller.CallPropertyGetter(context.Target, SystemField.Name); var isSystem = (bool) ReadProperty(context.Target, SystemField); context.HasPermission = !isSystem; } }
My implementation usually intercepts the delete function.
public override void Delete() { if (this.IsSystem) { throw new InvalidOperationException(Resources.SystemDefinedObjectCannotBeDeletedMessage); } base.Delete(); } public static void DeleteRoleEdit(int roleId) { if (RoleIsSystemCommand.RoleIsSystem(roleId)) { throw new InvalidOperationException(Resources.SystemDefinedObjectCannotBeDeletedMessage); } RoleEdit.DeleteRoleEdit(new RoleDataCriteria { RoleId = roleId }); }
Not sure if this is the better way to do it with CSLA.
When wiring this rule up ... would the value passed for element be null?
Hi JonnyBee, I'm trying to implement this authorization rule and it's giving me trouble. Apparently it's in the name of the rule or something.
After the return from the constructor I consistently get an exception:
System.ArgumentException with the only message being "rule"
at Csla.Rules.BusinessRules.EnsureUniqueRule(AuthorizationRuleManager mgr, IAuthorizationRule rule)
at Csla.Rules.BusinessRules.AddRule(IAuthorizationRule rule)
at MyApp.Library.PartWarehouse.AddBusinessRules() in C:\data\an\MyApp.Library\PartWarehouse.cs:line 262
at Csla.Core.BusinessBase.InitializeBusinessRules()
It's called from here:
public static void AddObjectAuthorizationRules()
{
Csla.Rules.BusinessRules.AddRule(typeof(PartWarehouse), new Admin.IsAllowed(Csla.Rules.AuthorizationActions.GetObject, RootObjectTypes.PartWarehouse));
Csla.Rules.BusinessRules.AddRule(typeof(PartWarehouse), new Admin.IsAllowed(Csla.Rules.AuthorizationActions.CreateObject, RootObjectTypes.PartWarehouse));
Csla.Rules.BusinessRules.AddRule(typeof(PartWarehouse), new Admin.IsAllowed(Csla.Rules.AuthorizationActions.EditObject, RootObjectTypes.PartWarehouse));
Csla.Rules.BusinessRules.AddRule(typeof(PartWarehouse), new Admin.IsAllowed(Csla.Rules.AuthorizationActions.DeleteObject, RootObjectTypes.PartWarehouse));
Csla.Rules.BusinessRules.AddRule(typeof(PartWarehouse), new Admin.IsSystemObjectRule(Csla.Rules.AuthorizationActions.DeleteObject, IsDefaultWarehouseProperty));
}
Is it necessary to set a Rule URI or something manually?
Also here is the complete rule which is, I believe, identical to yours with minor exception:
using System;
using Csla.Rules;
using Csla.Core;
namespace MyApp.Library.Admin
public class IsSystemObjectRule : Csla.Rules.AuthorizationRule
private IPropertyInfo IsSystemObjectField { get; set; }
public IsSystemObjectRule(AuthorizationActions action, IPropertyInfo isSystemObjectField) : base(action)
IsSystemObjectField = isSystemObjectField;
public override bool CacheResult
get
return false;
protected override void Execute(AuthorizationContext context)
if(context.Target == null)
context.HasPermission = true;
return;
var isSystem = (bool)ReadProperty(context.Target, IsSystemObjectField);
context.HasPermission = !isSystem;
}//eoc
Any help would be greatly appreciated
This is a limitation in the rule engine. There can only be one AuthzRule per static action or per action/property.
IE: The RuleEngine do NOT support having a "set" of AuthzRules as is the case for BusinessRules.
Your only option here is to add more properties to the same rule instance.
Ahh...thanks Jonny, that clears it up.
Perhaps the error message: "rule" could be improved upon? :)
JonnyBee:Your only option here is to add more properties to the same rule instance.
Hi Jonny, I've been playing with it and I can't figure out how to do that. In fact I thought I *did* do that in my sample:
BusinessRules.AddRule(new Admin.IsSystemObjectRule(Csla.Rules.AuthorizationActions.DeleteObject, IsDefaultWarehouseProperty));
Could you please tell me what I'm missing here?
Aha! Figured it out, sorry Jonny, I didn't dig enough. Thanks for your help.
For any future people wondering you need to pass a property to the base constructor for the authorization rule, like this based on my code above (change in bold):
public IsSystemObjectRule( AuthorizationActions action, IPropertyInfo isSystemObjectField) : base(action,isSystemObjectField)
Well, I got the rule working in that it's constructor is called and it doesn't blow up with an exception.
However the rules' execute method is *never* called. Is this because I've had to specify a property to make it unique and now it's not tied to the delete operation any more but only if that property get's modified?
As I stated in my previous post the rule engine only supports one - 1 rule per action for any check.
While you may add more rules - there will only be executed one-1 rule for each Authz action.
Oh, well that's a problem then.
I thought if the bogus property was added it became unique and in addition to no longer throwing the cryptic exception would execute.
So what you're saying is that you can not write an authorization rule to do what the topic of this thread is and still have regular authorization rules for security? Or we'd need some kind of really complex hybrid single authorization rule that handled not only the security role case but also the single instance state prevents deletion case.