Syntax for Instance Level Authorization Rule in CSLA 4

Syntax for Instance Level Authorization Rule in CSLA 4

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


Biceman posted on Friday, August 13, 2010

Can someone show me the syntax for adding an instance level authorization rule in CSLA 4?  Rocky's overview of the Authorization Rules for CSLA 4 mentions this functionality but there is no example of how to go about adding this type of rule.

RockfordLhotka replied on Friday, August 13, 2010

Where possible (specifically for insert/update/delete operations) the Target property of the context parameter is populated with a reference to the business object instance. So your authorization rule can use the Target property to interact with the object instance.

Obviously in the case of create/fetch there is no instance when the rule runs because the rule is indicating whether it is possible to do the create/fetch before the operation occurs - so Target isn't available in those cases.

Biceman replied on Saturday, August 21, 2010

So in order to use instance level authorization for read, edit, delete, and add, I need to create a custom "IsInRole" class ... correct?  Your blog write-up didn't mention that explicitly so I was thinking I could use the Rules.CommonRules.IsInRole version. 

Do I need to override the logic in the BusinessBase so that it performs an instance level authorization before a Save or Delete (e.g., Rules.BusinessRules.HasPermission(AuthorizationActions.Delete, _myCustomer) ) or does CSLA already do this for me (check at per-type level and per-instance level)?

 

In the spirit of helping others, I'll explain the solution I came up with:

My application contains various master data tables (e.g., colors).  The Colors table comes pre-populated with some standard entries.  These are flagged in the database as "system entries".  The user is free to add to this list of color(s).  The authorization requirements are as such:

I created a read-only property named "IsSystemEntry" in my ColorBO.

I created a custom IsInRole class named "SysEntryIsInRole".  I added another cached list of roles (_SysPptyRoles) to the class to store the role(s) which can perform operations on system entries.  The Sub New contains a signature with:

(Action, SysEntryRoles as List(of String), Roles as List(of String))

Since Property level authorizations are already instance based, I did not have to worry about them in my custom class (hence there is no place holder for "element" in the Sub New).

In the Execute method, I check context.target.  If it is Nothing then I do the same look-up processing against the _Roles list that the CommonRules.IsInRole does.  However, if the target contains a reference, then an instance level authorization is being requested.  I cast the target as my ColorBO then reference the IsSystemEntry property.  Only deletion and edit authorization rules are special for system entries so if the authorization action is one of these, I loop over the _SysEntryRoles list just like you normally do for the _Roles list; otherwise I loop over the _Roles list.

Here's the code which might make things clearer:

In my ColorBO:

    Protected Shared Sub AddObjectAuthorizationRules()
        Rules.BusinessRules.AddRule(GetType(ColorBO), New Rules.CommonRules.IsInRole(Rules.AuthorizationActions.GetObject, "User"))
        Rules.BusinessRules.AddRule(GetType(ColorBO), New Rules.CommonRules.IsInRole(Rules.AuthorizationActions.CreateObject, "User"))
        Rules.BusinessRules.AddRule(GetType(ColorBO), New SysEntryIsInRole(Rules.AuthorizationActions.EditObject, "System Admin", "User"))
        Rules.BusinessRules.AddRule(GetType(ColorBO), New SysEntryIsInRole(Rules.AuthorizationActions.DeleteObject, "**NoOne**", "User"))
    End Sub

For the delete rule, I specify the fictitious role "**NoOne**" to indicate no one can perform this task.  I could just as well have passed an empty string.  Again only Edit and Delete require special checking so only those two actions reference my SysEntryIsInRole class.

 

In my SysEntryIsInRole class:

Public Class SysEntryIsInRole
    Inherits Csla.Rules.AuthorizationRule

    Private _Roles As List(Of String)
    Private _SysPptyRoles As List(Of String)

'I actually pass in a single string for the SysPptyRole (System Property Role) because I know I will have at most one entry and it saves me

'from having to pass a list

Public Sub New(ByVal Action As Csla.Rules.AuthorizationActions, ByVal SysPptyRole As String, ByVal ParamArray roles() As String)
        MyBase.New(Action)

        _Roles = New List(Of String)
        For Each item As String In roles
            _Roles.Add(item)
        Next

        _SysPptyRoles = New List(Of String)
        _SysPptyRoles.Add(SysPptyRole)

    End Sub

    Protected Overrides Sub Execute(ByVal context As Csla.Rules.AuthorizationContext)
        If context.Target Is Nothing Then  'Entity level authorization check
            CheckAuthorization(context)
        Else  'Instance level authorization check
            Dim o As ColorBO = CType(context.Target, ColorBO)
            If o.IsSystemEntry AndAlso (Me.Action = Csla.Rules.AuthorizationActions.DeleteObject OrElse Me.Action = Csla.Rules.AuthorizationActions.EditObject) Then
                context.HasPermission = False  'Default is NO
                If _SysPptyRoles IsNot Nothing Then
                    For Each role As String In _SysPptyRoles
                        If Csla.ApplicationContext.User.IsInRole(role) Then
                            context.HasPermission = True
                            Exit For
                        End If
                    Next
                End If
            Else
                CheckAuthorization(context)
            End If
        End If

    End Sub

    Private Sub CheckAuthorization(ByVal context As Csla.Rules.AuthorizationContext)
        If _Roles.Count > 0 Then
            For Each role As String In _Roles
                If Csla.ApplicationContext.User.IsInRole(role) Then
                    context.HasPermission = True
                    Exit For
                End If
            Next
        Else
            context.HasPermission = True 'Default is to authorize unless explicitly stated
        End If
    End Sub

Since I have many master tables that require this same authorization checking, I am going to create in Interface for the IsSystemEntry property so that I can cast any BO calling this rule and obtain the value of "IsSystemEntry".

 

Biceman replied on Saturday, August 21, 2010

I need to correct something I stated above.  This following statement is false:

Since Property level authorizations are already instance based, I did not have to worry about them in my custom class (hence there is no place holder for "element" in the Sub New).

The business rules contained in "AddBusinessRules" apply at the BO level (i.e., per-type).  The "AddBusinessRules" sub is only called once so you cannot place instance based property level authorization in it.

One of the property level authorization rules I omitted from my example above states:

I mistakenly had code in "AddBusinessRules" as such:

        If IsSystemEntry Then
            'protect Name
            BusinessRules.AddRule(New Rules.CommonRules.IsInRole(Rules.AuthorizationActions.WriteProperty, NameProperty, "**NoOne**"))
        Else
            BusinessRules.AddRule(New Rules.CommonRules.IsInRole(Rules.AuthorizationActions.WriteProperty, NameProperty, "User"))
        End If

Since the system entries are the first fetched from the database, the Name property was write protected for every ColorBO ... even those created by the user.

I now realize that I need to handle the read/write authorization for the Name property in my SysEntryIsInRole class since the authorization is based on the IsSystemEntry flag like the instance level edit and delete authorizations.

Copyright (c) Marimer LLC