Checking shared authorizations before instantiating business objects

Checking shared authorizations before instantiating business objects

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


ben posted on Monday, October 20, 2008

Is it possible to check Shared Authorization rules before any instance of an object type has been instantiated?.

 

We need to check whether the current user can read some properties from an object before instantiating it in order to customize the UI. When AutorizationRules.HasReadAllowedRoles is executed the following sequence of method calls ends up with a new AuthorizationRulesManager created and added to the Dictionary “mMangers” in SharedAuthorizationRules but no authorization rules are loaded into it.

 

1. AutorizationRules.HasReadAllowedRoles

2. AuthorizationRules.get_TypeRules (property “TypeRules”)

3. SharedAuthorizationRoles.GetManager

 

 

After this, when the first business object is instantiated, the shared authorization rules are not loaded because already there is an AuthorizationRulesManager loaded. So we end up with no authorization rule loaded and the user gets full access to all properties.

 

Is there a way to solve this issue?

Is it resolved in new release of CSLA? (We use version 3.0.2)

Does anybody have this problem?

 

Thanks in advance.

 

Benjamin

JoeFallon1 replied on Monday, October 20, 2008

Please post the business object code where you declared the set of authorization roles for your type.

Joe

ben replied on Tuesday, October 21, 2008

Hi Joe,

 

We are getting authorization rules from the database in a class that extends BusinessBase (from CSLA) and from which our business logic objects inherit. We have overridden method “AddAuthorizationRules

Protected Overrides Sub AddAuthorizationRules()

Dim cmd As Paca.Security.GetAuthorizationsCommand

Dim row As Paca.TableViews.TableViewRow

Dim member As String

Dim roles As String

cmd = Paca.Security.GetAuthorizationsCommand.Execute(Me.GetType().FullName)

' Authorizations

For Each row In cmd.AllowRead

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.AllowRead(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

For Each row In cmd.AllowWrite

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.AllowWrite(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

For Each row In cmd.AllowExecute

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.AllowExecute(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

' Prohibitions

For Each row In cmd.DenyRead

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.DenyRead(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

For Each row In cmd.DenyWrite

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.DenyWrite(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

For Each row In cmd.DenyExecute

   member = CStr(row("IdMiembro"))

   roles = CStr(row("Roles"))

   AuthorizationRules.DenyExecute(member, roles.Split(New Char() {";"c}, StringSplitOptions.RemoveEmptyEntries))

Next

End Sub

 

We have many grids in our application and we need to hide all their columns the user can’t see. We have added to our BusinessBase class the following property that allow the UI to customize every grid before data has been loaded:

Private Shared mSharedAuthorizations As Csla.Security.AuthorizationRules

Public Shared ReadOnly Property SharedAuthorizations() As Csla.Security.AuthorizationRules

   Get

      If mSharedAuthorizations Is Nothing Then

         ' Create an instance to make sure Authorization rules are loaded.

         Activator.CreateInstance(GetType(T), True)

         mSharedAuthorizations = New Csla.Security.AuthorizationRules(GetType(T))

      End If

      Return mSharedAuthorizations

   End Get

End Property

 

As you can see, we have found a workaround to force the CSLA framework to load authorization rules even before any business object is loaded from the UI. We don’t know if it is the best approach but it works for us (at the moment).

 

Thanks for your attention.

 

Ben

JoeFallon1 replied on Tuesday, October 21, 2008

Ben,

You asked:

"Is it possible to check Shared Authorization rules before any instance of an object type has been instantiated?"

The obvious answer is Yes - that is what the whole Authorization subsytem is designed to do!

I knew you must be doing something which is non-standard - otherwise you wouldn't have asked the question.

I suggest you start by creating a small test class which inherits directly from CSLA (skip your base class for now) which does things the "standard" way. Hard code a single role into one of the methods and then trace the sequence  so you can fully understand what happens and when it happens using the std design.

Once you have a good feel for the normal sequence you can then re-trace your non-std design and see what you are doing which causes things to break.

This is "obviously" causing the problem:
Public Shared ReadOnly Property SharedAuthorizations() As Csla.Security.AuthorizationRules

I just can't tell you how to fix it.

Joe

ben replied on Tuesday, October 21, 2008

If you are right then I'm missing something.

 

All shared authorization data is loaded in the method “AddAuthorizationRules” which is an instance method called just once, the first time its owner business object is instantiated. The constructor takes care of calling it.

 

If I’m not wrong, there is no authorization associated to one target object type until one business object of that type is instantiated. So all checks we do against any method from Csla.Security.AuthorizationRules(GetType(T))  will not have any information about authorizations. This means that all properties are allowed.

 

Our shared authorization infrastructure is not based on shared methods only. As shared methods can’t be overridden and we need to override AddAuthorizationRules to gain all its benefits then we need to depend on an instance method to be called from the object constructor.

 

Based on this thinking we decided to use our workaround. We instantiate a business object when the AuthorizationRules is asked by the first time to make sure the authorization rules are loaded following the standard procedure. Other way we would have to load authorization rules in some where different to AddAuthorizationRules. May be a shared method, but this would be so far from the standard method.

 

I can’t agree with you that CSLA authentication system is designed to allow checking authorization without instantiating any object. It seems to but it doesn’t really. The class AuthorizationRules provide access to shared authorizations only if one object has been instantiated.

 

This is what I think. Please, tell me where I’m wrong.

 

Ben.

JoeFallon1 replied on Tuesday, October 21, 2008

Try using AddObjectAuthorizationRules instead.

That is for per Type rules which do *not* require an instance to ever be created.

Joe

 

ben replied on Wednesday, October 22, 2008

I don’t know method “AddObjectAuthorizationRules”, do you mean “AddInstanceAuthorizationRules”?. We can’t use “AddInstanceAuthorizationRules” for two reasons:

 

Getting the authorization data for each instance would have too impact in performance because our application loads many instances at the same time.

 

We need access to shared authorization data before any instance is created.

 

Our workaround works for now but later, when we have time we’ll try to redesign the security system of our version of CSLA. I think the problem is because of the security system should not let other classes set its data from outside. Any UI programmer may set authorization data before any business object is instantiated so we have a big hole in our security system. The system should get by itself the information it needs. No one outside the security system should be able to set security information unless the security system asked it for it. To customize the security data source there may be an inheritable class or an interface (like IPrincipal and IIdentity), or may be both.

 

Thanks a lot for your time, any way. You’re so kind,

 

Benjamin.

 

JoeFallon1 replied on Wednesday, October 22, 2008

 I don’t know method “AddObjectAuthorizationRules”, do you mean “AddInstanceAuthorizationRules”?

Nope.

I meant what I wrote. AddObjectAuthorizationRules is for per type rules declarations.

Check out version 3.6 code base.

Joe

 

 

ben replied on Thursday, October 23, 2008

Thanks Joe. I’m going to start studying CSLA version 3.6.

 

Benjamin

JoeFallon1 replied on Thursday, October 23, 2008

Hmm.

To clarify, AddObjectAuthorizationRules is used like this:

AuthorizationRules.AllowCreate(typeof(Project), "Project Manager");
AuthorizationRules.AllowEdit(typeof(Project), "Project Manager");
AuthorizationRules.AllowDelete(typeof(Project), "Project Manager");

So I guess that does not help you.

I am going to have to defer to Rocky on this one.

Especially the comment about a potential bug.

Joe

 

JoeFallon1 replied on Thursday, October 23, 2008

Rocky - can you please review this thread?

Thanks.

Joe

 

ben replied on Thursday, October 23, 2008

I’ve spent some time looking at version 3.6 this morning. I haven’t time enough to study it as I want but I think “AddObjectAuthorizationRules” does not help me very much. To gain CRUD permission security we are already using a subsystem developed by us some time ago and its works fine. Instead of setting permission logic inside every business object we set them in the database (more manageable). Before any CRUD operation starts we report the action to the security system. If the current user has permission to do the action then everything works fine but when the user hasn’t the right permission (the right role) the security system throws an exception. UI has also a specialized method to ask for user permission getting a Boolean value.

 

To create a new permission the only thing we need is to create a new record in a table called "Permissions" and we also have a table called "RolesPermissions" to store the roles authorized for each permission. With these two tables and the other explined by Rocky in his book "Expert One-on-One Visual Basic .NET Business Objects" we are managing CRUD logic.

 

These are our key classes to manage permissions:

 

Imports System.Security

Imports System.Security.Permissions

Imports System.Collections.Specialized

Imports System.Threading

 

Namespace Security

 

   Public Class AccessControl

      Private Shared mPermission As HybridDictionary

 

      Public Shared Function UserAuthenticated() As Boolean

         If Thread.CurrentPrincipal.Identity Is Nothing _

            OrElse Not Thread.CurrentPrincipal.Identity.IsAuthenticated Then

            'The user isn't authenticated

            Return False

         Else

            Return True

         End If

      End Function

 

      Public Shared Function CanI(ByVal PermissionName As String) As Boolean

         If Not UserAuthenticated() Then

            Return False

         End If

         Try

            GetPermission(PermissionName).Demand()

            Return True

         Catch se As SecurityException

            Return False

         Catch e As Exception

            Throw e

         End Try

      End Function

 

      Public Shared Sub GoingTo(ByVal PermissionName As String)

         If Not UserAuthenticated() Then

            Throw New System.Security.SecurityException( _

                        My.Resources.Security.NotAuthenticatedUser)

         End If

         Try

            GetPermission(PermissionName).Demand()

         Catch e As Exception

            Throw e

         End Try

      End Sub

 

      Private Shared Function GetPermission(ByVal PermissionName As String) As Paca.Security.Permission

         If Permission.Contains(PermissionName) Then

            Return DirectCast(Permission.Item(PermissionName), Paca.Security.Permission)

         Else

            Dim newPerm As Paca.Security.Permission

 

            newPerm = Paca.Security.Permission.LoadPermission(PermissionName)

 

            Permission.Add(PermissionName, newPerm)

            Return newPerm

         End If

      End Function

 

      Private Shared ReadOnly Property Permission() As HybridDictionary

         Get

            If mPermission Is Nothing Then

               mPermission = New HybridDictionary

            End If

            Return mPermission

         End Get

      End Property

 

   End Class

 

End Namespace

 

 

Imports Paca.Core

Imports System.Data.SqlClient

Imports System.Security.Permissions

 

Namespace Security

 

   <Serializable()> _

   Friend Class Permission

      Inherits Csla.ReadOnlyBase(Of Permission)

 

      Private mPermissionName As String

      Private mExceptionMessage As String

      Private mPrincialPermission As PrincipalPermission

 

#Region " Application logic "

 

      Public ReadOnly Property IsRegisteredInDB() As Boolean

         Get

            Return Len(mPermissionName) > 0

         End Get

      End Property

 

      Public ReadOnly Property Name() As String

         Get

            If Not IsRegisteredInDB Then

               Throw New Exception("Permission name not registered in the data base.")

            End If

            Return mPermissionName

         End Get

      End Property

 

      Public Sub Demand()

         Try

            mPrincialPermission.Demand()

         Catch ex As Exception

            Throw New System.Security.SecurityException(mExceptionMessage)

         End Try

      End Sub

#End Region

 

#Region " Create and Load "

 

      Protected Overrides Function GetIdValue() As Object

         Return mPermissionName

      End Function

 

      Public Overrides Function ToString() As String

         Return mPermissionName

      End Function

 

      Friend Shared Function LoadPermission(ByVal UserName As String) As Paca.Security.Permission

         Try

            Return CType(Csla.DataPortal.Fetch(New Criteria(UserName)), Paca.Security.Permission)

         Catch ex As Exception ' Control exceptions from DataPortal

            Throw New GettingPermissionException(ex)

         End Try

      End Function

 

      <Serializable()> _

      Private Class Criteria

         Public PermissionName As String

 

         Public Sub New(ByVal PermissionName As String)

            Me.PermissionName = PermissionName

         End Sub

      End Class

 

      Private Sub New()

         ' prevent direct creation

      End Sub

 

#End Region

 

#Region " Data access "

 

      Protected Overrides Sub DataPortal_Fetch(ByVal Criteria As Object)

         Dim crit As Criteria = CType(Criteria, Criteria)

 

         mPrincialPermission = New PrincipalPermission("Administrador", "Administradores")

 

         Dim cn As New SqlConnection(Database.SecurityConnection)

         Dim cm As New SqlCommand

         Try

            cn.Open()

            cm.Connection = cn

            cm.CommandText = "usp_Security_ObtenerPermiso"

            cm.CommandType = CommandType.StoredProcedure

            cm.Parameters.AddWithValue("@NombrePermiso", crit.PermissionName)

            Dim dr As SqlDataReader = cm.ExecuteReader()

            Try

               If dr.Read() Then

                  mPermissionName = crit.PermissionName

                  mExceptionMessage = dr.GetString(1)

 

                  If dr.NextResult Then

                     Dim strRoleName As String

                     While dr.Read

                        strRoleName = dr.GetString(0)

                        Dim newPermis As New PrincipalPermission(Nothing, strRoleName)

                        mPrincialPermission = CType(mPrincialPermission.Union(newPermis), System.Security.Permissions.PrincipalPermission)

                     End While

                  End If

 

               Else

                  mPermissionName = ""

               End If

 

            Finally

               dr.Close()

            End Try

 

         Catch ex As SqlException ' Throw a friendlier exception

            Throw New Exceptions.SecurityDBException(ex)

            ' Leave any other exception propagate

         Finally

            cn.Close()

         End Try

      End Sub

 

#End Region

 

   End Class

 

End Namespace

 

Usage example:

 

In CRUD methods add the following line before doing anything else:

 

Paca.Security.AccessControl.GoingTo("PermissionName")

 

For the UI you can use:

 

btnDelete.Enabled = Paca.Security.AccessControl.CanI("PermissionName")

 

This implementation is very simple (was developed for CSLA 1.x) and it could be improved a lot, but it works very well. May be some one may like it and use it.

 

Benjamin

RockfordLhotka replied on Friday, October 24, 2008

CSLA .NET version 3.5 introduced per-type authorization. I don't know if it will meet your needs, but here's how it works.

In your business class you implement a Shared method named AddObjectAuthorizationRules(). In this method you specify which roles are allowed to create, get, edit or delete this type of object. You could load those roles from the database, hard code them, or whatever. Inside this method, you make calls like this:

    Csla.Security.AuthorizationRules.AllowCreate(GetType(Customer), "Manager")

The AddObjectAuthorizationRules() method is automatically called once per AppDomain, the first time anyone asks for per-type authz rules for the specfied type.

Please note that as an advanced option you could call this line of code from somewhere else, as long as it only gets called once per AppDomain. So if you wanted, you could come up with your own scheme where you set all permissions for all types - as long as you make sure that occurs once per AppDomain, and before any "real" code runs.

Anywhere else in your code - in the UI, other business objects, and so forth, you can make calls like this:

    Dim canCreate As Boolean = _
        Csla.Security.AuthorizationRules.CanCreate(GetType(Customer))

This will use the current principal and the list of roles associated with the type. If AddObjectAuthorizationRules() hasn't been called for this type yet, it is called first (but only once per AppDomain).

ben replied on Friday, October 24, 2008

Many thanks, Rocky, for your explanations. You saved me so much time to understand how “AddObjectAuthorizationRules” works.

 

Now we have work to do :-) . We have to do some test to check whether we can use “AddObjectAuthorizationRules” to load properties authorizations and use them before any business instance is created.

 

In our security requirements we have to set permissions for actions that are not business actions, such as “CustomizeToolbars”, “LoadLibraryXxxx”, “SeeSpecialCommandBars”. “UseUIFeatures”, … We don’t have business objects that implement methods or properties for those kind of actions. And I can’t see how to get this with “AddObjectAuthorizationRules” or “AddAuthorizationRules”.

 

May you tell me how we could get this with the CSLA features?.

 

Thanks again for everything.

 

Ben

RockfordLhotka replied on Friday, October 24, 2008

The CSLA per-type features are designed to help you write UI code that responds to the four operations at an object level.

 

I don’t expect that you would have objects directly corresponding to those other actions. That’s the kind of thing you may choose to attach as properties in a custom Principal or Identity object, or load as an ApplicationFeatures object when the user first logs in (and maybe put into LocalContext on the client so it is easily accessible).

 

Rocky

JoeFallon1 replied on Friday, October 24, 2008

I have both Roles and Permissions in my custom Identity class. When a user logs in they get authenticated and then assigned their roles and perms. Then as the user navigates the app you can always ask the Principal if user.HasPermission("somePerm"). This is a totally different concept than where this thread started though!! The original question was about Property level authorizations and then it took a few twists to end up here. Hopefully this helps you move on with your coding.

Joe

 

ben replied on Monday, October 27, 2008

Thanks to both. We’ll consider your suggestion.

 

Benjamin

cute replied on Monday, August 17, 2009

Hi Rocky,

As what you have posted method AddObjectAuthorizationRules in csla3.5 is loaded per AppDomain. Is there a way that I can be able to reload the ObjectAuthorizationRules for every login of the user and used the Csla.Security.AuthorizationRules.* static method to determine if object can allow object CRUD.

I made a Web Application that the super admin can modifying the roles for each Group. As of this moment I used my customize object to handle the AuthorizationRules of my web application to check the roles for every user’s login. But still I do consider that using the Csla ObjectAuthorizationRules will be an excellent approach though.

I really appreciate of your response regarding this concerned.

Cute

ben replied on Tuesday, October 21, 2008

Hi Joe,

 

I would like you to do the following test with the ProjectTracker sample application:

 

1.- Add the following code at the beginning of the procedure MainForm_Load:

 

Dim hacker As New Csla.Security.AuthorizationRules(GetType(Project))

hacker.IsReadAllowed("")

 

2.- Set a break point  to the "AddAuthorizationRules" method in the "Project" class.

3.- Run the application and try to edit any project with any user.

 

As you can see the "AddAuthorizationRules" method is never executed. These two lines of code have disabled all authorizations for the type "Project". We could do an iteration for all business classes to disable all authorizations for all properties and methods. The source of the problem is in the BusinessBase constructor. When AuthoriazationRules.IsReadAllowed is called a new AutorizationRulesManager object is added to the dictionary mManagers in the module “SharedAuthorizationRules”. After this any call to SharedAuthorizationRules.RulesExistFor will return true and so the constructor of the class “Project” don’t call its method AddAuthorizationRules.

 

I think this is a bug in the security system. Isn’t it?

 

We are using version 3.0.2, but I think this problem is similar in newer versions.

 

Thanks for your attention.

 

Benjamin

Copyright (c) Marimer LLC