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
Please post the business object code where you declared the set of authorization roles for your type.
Joe
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
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
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))
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.
Try using AddObjectAuthorizationRules instead.
That is for per Type rules which do *not* require an instance to ever be created.
Joe
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.
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
Thanks Joe. I’m going to start studying CSLA version 3.6.
Benjamin
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
Rocky - can you please review this thread?
Thanks.
Joe
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.
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
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).
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
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
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
Thanks to both. We’ll consider your suggestion.
Benjamin
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
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