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.
P.S. I have a business rule to not allow them to enter duplicate names, so I don’t have to worry about them creating another “Administrators” role.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); }
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? :)
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.
Yes, but you could subclass existing Authz Rules like IsInRole rule and add custom checks in the execute method of the rule.
You can however only have 1 rule per static action or per type action.
This the actual code from BusinessRules.HasPermission (static method)
bool result = true;
var rule =
AuthorizationRuleManager.GetRulesForType(objType, ruleSet).Rules.FirstOrDefault(c => c.Element == null && c.Action == action);
if (rule != null)
{
var context = new AuthorizationContext { Rule = rule, Target = obj, TargetType = objType };
rule.Execute(context);
result = context.HasPermission;
}
Based on the code - you can also see that AutzRules for the static methods must have Element (IMemberInfo) = null. Which means that you may get the target object as parameter to the Execute method but cannot add PropertyInfo to the rule (by calling base(...)).
This applies to the AuthorizationActions CreateObject, DeleteObject, GetObject, EditObject.
Ok, then I guess it''s not practical and a dead end for me.
I don't see any support for this scenario in CSLA at all, it's kind of wierd, I thought this was a super common requirement: prevent delete based on business object state, but it seems like it was never planned for in the framework itself. I'm sure that can't be right and I'm just missing something but so far no luck.
Perhaps I'll search again on the forum for all the people that asked how to do this and never really seemed to get it worked out and ask each of them what they ended up doing.
Thanks again for your help.
There is support for this - you just have to follow some basic guidelines.
BusinessRules at Object level must have ProperyProperty = null.
AuthorizationRules for Static methods must have Member = null (CanGetObject, CanCreateObject, CanEditObject, CanDeleteObject)
BusinessRules for a Property must have PrimaryProperty != null
AuthorizationRules for property/method must have Member != null (CanWriteProperty, CanReadProperty, CanExecuteMethod)
And for AuthorizationRules there can only be one - 1 rule per check!
Since AuthzRules does not support the concept of InputProperties you must store the PropertyInfos in member field on the rule and use ReadProperty to get the values.
So there is nothing to stop you from having IPropertyInfo members in your AuthorizationRules - just follow the guidelines above.
Take f.ex this rule to be used for static methods but will still check for property values when applicable (ie context.Target is not null):
public class MyVeryOwnObjectAuthzRule : AuthorizationRule
{
private readonly IPropertyInfo _prop1;
private readonly IPropertyInfo _prop2;
public MyVeryOwnObjectAuthzRule(AuthorizationActions action, PropertyInfo<string> prop1, PropertyInfo<string> prop2)
: base(action)
{
_prop1 = prop1;
_prop2 = prop2;
}
protected override void Execute(AuthorizationContext context)
{
if (context.Target != null)
{
var val1 = (string)ReadProperty(context.Target, _prop1);
var val2 = (string)ReadProperty(context.Target, _prop2);
// add my own if tests here
}
}
}
or you can sublass an existing rule to add more tests:
public class MyObjectAuthzRule : IsInRole
{
private readonly IPropertyInfo _prop1;
private readonly IPropertyInfo _prop2;
public MyObjectAuthzRule(AuthorizationActions action, PropertyInfo<string> prop1, PropertyInfo<string> prop2, params string[] roles)
: base(action, roles)
{
_prop1 = prop1;
_prop2 = prop2;
}
public MyObjectAuthzRule(AuthorizationActions action, PropertyInfo<string> prop1, PropertyInfo<string> prop2, List<string> roles)
: base(action, roles)
{
_prop1 = prop1;
_prop2 = prop2;
}
protected override void Execute(AuthorizationContext context)
{
// chect the base rule
base.Execute(context);
if (!context.HasPermission) return;
if (context.Target != null)
{
var val1 = (string) ReadProperty(context.Target, _prop1);
var val2 = (string) ReadProperty(context.Target, _prop2);
// add my own if tests here
}
}
}
Ah! I see what you're saying now. This should really be documented specifically in the Ebook. Thank you for taking the time to answer this so thoroughly.
Cheers!
Works perfectly, thanks again Jonny!
Hi Jonny. Is there any working options for the Csla 3.8.4 Business & Auth Rules engine?
Thanks,
Troncho
Csla 3.x authz rules only get the Type as parameter.
So you can only configure AllowedRoles/DenyRoles to be checked and not inspect the actual values of the object.
Ok, so for now, overriding the BO.Delete() will have to do.
Thanks!
Troncho
Copyright (c) Marimer LLC