Business Rule for Not Allowing Edit or Delete for a Specific Record

Business Rule for Not Allowing Edit or Delete for a Specific Record

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


korzy1bj@gmail.com posted on Thursday, January 19, 2012

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.

JonnyBee replied on Thursday, January 19, 2012

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);
    }

korzy1bj@gmail.com replied on Monday, January 23, 2012

Thank you very much for your help and for your quick response. I will try this out today.

korzy1bj@gmail.com replied on Wednesday, January 25, 2012

Could you show me what the authorization rule would like like for preventing them from deleting and editing a role that has IsSystem == true?

JonnyBee replied on Wednesday, January 25, 2012

Pretty muuch like this:

  public class IsSystem : Csla.Rules.AuthorizationRule
  {
 
    private IPropertyInfo SystemField { getset; }
 
    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;
    }
  }

mattruma replied on Wednesday, January 25, 2012

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.

mattruma replied on Wednesday, January 25, 2012

When wiring this rule up ... would the value passed for element be null?

JCardina replied on Thursday, November 01, 2012

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

JonnyBee replied on Thursday, November 01, 2012

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. 

JCardina replied on Friday, November 02, 2012

Ahh...thanks Jonny, that clears it up.

Perhaps the error message: "rule" could be improved upon? :)

JCardina replied on Friday, November 02, 2012

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?

JCardina replied on Friday, November 02, 2012

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)

JCardina replied on Friday, November 02, 2012

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?

 

JonnyBee replied on Friday, November 02, 2012

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.

JCardina replied on Friday, November 02, 2012

Oh, well that's a problem then. Smile

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.

JonnyBee replied on Friday, November 02, 2012

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.

 

 

 

JCardina replied on Saturday, November 03, 2012

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.

JonnyBee replied on Sunday, November 04, 2012

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

      }

    }

  }

 

JCardina replied on Monday, November 05, 2012

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!

JCardina replied on Monday, November 05, 2012

Works perfectly, thanks again Jonny!

Troncho replied on Friday, November 09, 2012

Hi Jonny. Is there any working options for the Csla 3.8.4 Business & Auth Rules engine?

Thanks,

Troncho

JonnyBee replied on Saturday, November 10, 2012

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.

Troncho replied on Saturday, November 10, 2012

Ok, so for now, overriding the BO.Delete() will have to do.

Thanks!

Troncho

Copyright (c) Marimer LLC