Developing a dedicated class for Security/Authorizaton under CSLA .NET.

Developing a dedicated class for Security/Authorizaton under CSLA .NET.

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


tarekahf posted on Monday, May 26, 2008

I am developing a class to be used as the Central Point of access to Control All Functions for All Application.

In general, the class will provide the following functionality:

For a given Application, Who Can Do What and When.

I need your input and feedback to implement this class in the best way possible, and if you have other suggestions, please feel free to let me know.

Sample Use for this Class: 

For example, we have Attendance System which is live for the past 4 years. Everyone will punch the in/out times. Some times, staff will forget to punch in or out ! So, I created a Screen to Enter Attendance Adjustments.

Of course, this screen need to be secured. Not any one can enter adjustment. Right ?

So, the HR Users requested me to provide the screen for 2 main User Roles:

- Application Admin: He can enter the adjustment for any Staff.

- Department Admin: He can enter the adjustment only for the staff who are in the same department of the user who is making the Data Entry.

Now, I have another application which is used to Display Staff Profile Info On-Line (eHRMD), such as Contact Info, Personal Info, Salary, Medical Lab Requests ...etc.

So, the HR Users requested me to provide extreme flexibility to allow Authorized Staff to view the Profile Data of other Staff based on predefined rules.

For example:

- Application Admin: Can view staff info of any other staff.

- Director: Can view all Staff Info EXCEPT Medical Data.

- Section Head: Can view all Staff Info EXCEPT Medical Data and Salary Data

...etc...

Database Design:

I decided to make the Database as follows:

Following is a Sample of each table above:

tApplications



AppID AppDescEng AppDescArab
AtSys Attendance System
eHRMD Staff Profile Application
 

tUIElements



AppID UIElmID UIElmDescEng UIElemDescArab UsesPrivLvl
Attendance System UI001 Adjustmentment Entry.
No
Attendance System UIAll All UI Elements of Attendance System.
No
Staff Profile Application UI001 View Info of Other Staff.
Yes
Staff Profile Application UIAll All UI Elements of eHRMD.
No
 

tUIElmActions



AppID UIElmID ActionID ActionDescEng ActionDescArb
Attendance System UI001 AC001 Enter Adjustment for All Staff.
Attendance System UI001 AC003 Enter Adjustment for Staff Only in Same Dept.
Attendance System UIAll ACAll All Actions of Attendance System.
Staff Profile Application UI001 AC001 View All Staff Info of any Other Staff.
Staff Profile Application UI001 AC003 View Staff Info of Other Staff Only in the Same Dept.
Staff Profile Application UI001 ACAll All Actions for View Other Staff Info as per Privacy Level.
Staff Profile Application UIAll ACAll All Actions of eHRMD.
 

tRoles



RoleID RoleDescEng RoleDescArb CanViewMaxPrivLvl
R001 Application Admin
5
R005 Director
3
R007 Section Head
3
R009 Department Adminsitrator
2
R015 Attendance Adjustment Entry
1
R020 Staff
1

tAuthorization



AppID UIElmID ActionID RoleID IsDenied
Attendance System UI001 AC001 Application Admin No
Attendance System UI001 AC001 Attendance Adjustment Entry No
Attendance System UI001 AC003 Department Adminsitrator No
Staff Profile Application UI001 AC001 Application Admin No
Staff Profile Application UI001 AC003 Director No
Staff Profile Application UI001 ACAll Staff No

Application Security Class Implementation:

I decided to implement the class as follows:

- Class Name: AppSecurity 

- Use Singleton Pattern: "appsec = AppSecurity.GetSingleton()" to get an instance of the class.

- Load the Tables above into memory using DataSet only once during the Application Life in the Worker Process. This is to avoid frequent Database access.

- Use CSLA .NET DataProtal Fetch method to load the Data. This is to make use of Mobile Business Object, when needed.

- Use DataRelation to relate the tables in the DataSet.

- Use CSLA .NET Bindable Objects only when need to bind the Data with the Presentation Layer.

- To check for security, following is the Application UI sample Code of the Attendance Adjustment Entry Screen (note that this is for clarification purposes, as such code must be encapsulated in the Business Logic Layer in a special class):

sub Page_Load(...)
appsec = AppSecurity.GetSingleton()
if (appsec.CanPerformAction(AppCodes.AtSys, UIElmCodes.UI001, UIElmActCodes.AC001) then
' Means can Enter Adjustment for All Staff.
' Yes, he is authorized, continue ..
' Setup the DataSource of the Screen to work with all Staff
elseif (appsec.CanPeformAction(AppCodes.AtSys, UIElmCodes.UI001, UIElmActCodes.AC003) then
' Means can Enter Adjustment for only for Staff in the same Dept.
' Get the Dept. Code of the Loged In User.
' Filter the DataSource of the Screen to allow only working with
' Staff who are in the same Dept. of the Loged in user.
else
response.write("Access Denied or something like that."
end if
end sub

Why not using .NET Native Authorization using web.config:

Enabling authorization using web.config and Allow/Deny rules is good to Allow or Deny Specific User/Roles to "Open" certain pages/links within the web site (with an option for Security Trimming", but using this technique "as-is" is not good enough. Why ? Because the logic also depends on further data to be retrieved from the Database.

Using web.config without further work also has additional problem: You have to hard-code the authorized Roles within the web.config, and hard-code the authorization rules also. In my approach, I am not hard-coding the Rules, instead, I am using predefined Keys to hard-code the meaning of the codes in the program, and allow the user to change the Mapping between the Roles and the Action Codes.

In order to defined the mapping between the Application Functions and User Roles outside the Program, you MUST do additional work. The .NET does not do this work for you. Please correct me if I am wrong. However, .NET gives something called "Authorization Providers", where you can implement them in your program. This is exactly what I am trying to do. In the end, the Implementation of such Provider will have to call the functionality of the "AppSecurity" class which I mentioned earlier.

You may ask "Why I have encapsulated the Application Security in this AppSecurity Class ? Why not implement the Authorization Provider directly ?". The answer is: "To allow developing a Web Service Wrapper for this class and enable other applications to use it for Authorization. Or, to compile it into a DLL, and allow all kind of supported UI Platforms to use it where needed."

Sample Code of the Class:

I am posting below the sample code of the class to clarify the concept:

<Serializable()> _
Public Class AppSecurity

#Region " Hard-Coded Constant Values "
'
' Define the Hard-Coded Values to be used in the program
' Such values are related to the Key Values in the Security Database Tables
' They are defined as Constants in Classes jsut to make them easier to work with
'
Public Enum AppCodes
eHRMD
AtSys
End enum
Public Enum UIElmCodes
UIAll
UI001
End Enum
Public Enum
ULElmActCodes
ACAll
AC001
AC003
End Enum

#End Region

Private Shared
mSingleton As AppSecurity
Private Shared lockobject As New Object
Private
mAppSecDataSet As DataSet
#Region " Factory Methods "
Private Sub New()

End Sub
Public Shared Function
GetSingleton() As AppSecurity
If mSingleton Is Nothing Then
SyncLock
lockobject
If mSingleton Is Nothing Then
mSingleton = DataPortal.Fetch(Of AppSecurity)(New Criteria())
End If
End SyncLock
End If
Return
mSingleton
End Function
#End Region

#Region " Data Access "

<Serializable()> _
Private Class Criteria
Public Sub New()

End Sub
End Class
Private Overloads Sub
DataPortal_Fetch(ByVal criteria As Criteria)
'Dim TestEnum As AppCodes
'TestEnum = AppCodes.AtSys
'Console.WriteLine(TestEnum.ToString)
Try
Using con As New OleDb.OleDbConnection(Database.AppSecurityDB)
Using cmd As OleDb.OleDbCommand = con.CreateCommand
cmd.CommandType = CommandType.Text
Using da As New OleDb.OleDbDataAdapter(cmd)
'
' Load all tables with small set of records into
' memory using DataSets
'
cmd.CommandText = "select * from tApplications"
mAppSecDataSet = New DataSet("ApplicationSecurity")
da.Fill(mAppSecDataSet, "tApplications")

cmd.CommandText = "select * from tAppDataTypes"
da.Fill(mAppSecDataSet, "tAppDataTypes")

cmd.CommandText = "select * from tUIElements"
da.Fill(mAppSecDataSet, "tUIElements")

cmd.CommandText = "select * from tUIElmActions"
da.Fill(mAppSecDataSet, "tUIElmActions")

cmd.CommandText = "select * from tRoles"
da.Fill(mAppSecDataSet, "tRoles")

cmd.CommandText = "select * from tAuthorization"
da.Fill(mAppSecDataSet, "tAuthorizations")

'
' Any lookup required against the Staff or Staff Roles
' will have to be done against the Database directly.
' This is to avoid loading large number of records
'

'
' Now define the Relationship among the tables in the DataSet
'
Dim ParentColumns(0 To 0) As DataColumn
Dim ChildColumns(0 To 0) As DataColumn
Dim Rel As DataRelation
ParentColumns(0) = mAppSecDataSet.Tables("tApplications").Columns("AppID")
ChildColumns(0) = mAppSecDataSet.Tables("tAppDataTypes").Columns("AppID")
Rel = New DataRelation("App_DataTypes", ParentColumns, ChildColumns)
mAppSecDataSet.Relations.Add(Rel)
Rel = Nothing

ReDim
ParentColumns(0 To 0)
ReDim ChildColumns(0 To 0)
ParentColumns(0) = mAppSecDataSet.Tables("tApplications").Columns("AppID")
ChildColumns(0) = mAppSecDataSet.Tables("tUIElements").Columns("AppID")
Rel = New DataRelation("App_UIElements", ParentColumns, ChildColumns)
mAppSecDataSet.Relations.Add(Rel)
Rel = Nothing

ReDim
ParentColumns(0 To 1)
ReDim ChildColumns(0 To 1)
ParentColumns(0) = mAppSecDataSet.Tables("tUIElements").Columns("AppID")
ParentColumns(1) = mAppSecDataSet.Tables("tUIElements").Columns("UIElemID")
ChildColumns(0) = mAppSecDataSet.Tables("tUIElmActions").Columns("AppID")
ChildColumns(1) = mAppSecDataSet.Tables("tUIElmActions").Columns("UIElemID")
Rel = New DataRelation("UIElements_Actions", ParentColumns, ChildColumns)
mAppSecDataSet.Relations.Add(Rel)
Rel = Nothing

ReDim
ParentColumns(0 To 2)
ReDim ChildColumns(0 To 2)
ParentColumns(0) = mAppSecDataSet.Tables("tUIElmActions").Columns("AppID")
ParentColumns(1) = mAppSecDataSet.Tables("tUIElmActions").Columns("UIElemID")
ParentColumns(2) = mAppSecDataSet.Tables("tUIElmActions").Columns("ActionID")
ChildColumns(0) = mAppSecDataSet.Tables("tAuthorization").Columns("AppID")
ChildColumns(1) = mAppSecDataSet.Tables("tAuthorization").Columns("UIElmID")
ChildColumns(2) = mAppSecDataSet.Tables("tAuthorization").Columns("ActionID")
Rel = New DataRelation("Actions_Authorization", ParentColumns, ChildColumns)
mAppSecDataSet.Relations.Add(Rel)
Rel = Nothing

ReDim
ParentColumns(0 To 0)
ReDim ChildColumns(0 To 0)
ParentColumns(0) = mAppSecDataSet.Tables("tRoles").Columns("RoleID")
ChildColumns(0) = mAppSecDataSet.Tables("tAuthorization").Columns("RoleID")
Rel = New DataRelation("Roles_Authorizations", ParentColumns, ChildColumns)
mAppSecDataSet.Relations.Add(Rel)
Rel = Nothing

End
Using
End Using
End Using
Catch ex As Exception
Throw New Exception(ex.Message, ex)
End Try
End Sub

#End Region

End Class

Questions:

- What is the best approach to use CSLA .NET Framework to Implment this Logic ? I am finding some difficulty to fit this in CSLA .NET Authorizations.

If you think there is something wrong or you have any suggestions, I appreciate your feedback.

Tarek.

tarekahf replied on Friday, August 29, 2008

Dear All,

I really need your help !

My question here is based on the post above.

I have read Chapter 1,2, 6, 7, 8 and 10 of the Expert VB 2005 Business Objects.

I am having some difficulty in fitting my requirement (as per above) into CSLA .NET Model.

The problem I am facing is in Authorization.

Let us take a simple Example:

1. User A logs on to the Staff Profile Application (Windows Authentication), and he can view his HR related data.

2. User A requests to view the Staff Profile of another User B, and he requested to view certain data (Education History for example).

3. The logic to allow User A to view the Education History of User B, if all the following validation check are TRUE:

   - If User A is in the Manager Role,

   - If User A is in the same Department of User B.

   - If the sensitivity level of the Education History is less than the max sensitivity level allowed form the Manager Role.

As you can see is that in order to perform the Authorization Check, the code must perform several lookups to the Security Control Data and to the Profile Data. This means the authorization depends on:

  - The Department Code of the Authenticated User and the Dept. Code of the requested user,

  - The Role of the Authenticated User,

  - The sensitivity level of the request user info and the User Role of the authenticated user.

Now going back to CSLA .NET, in the Authorization Section, the relevant Authorization method that applies here is this one:

Public Shared Function CanGetObject() As Boolean
Return Csla.ApplicationContext.User.IsInRole("Some Roles")
End Function

However, as you can see, the Role is hard-coded in the method, and there is no indication on how to perform lookup within this method using CSLA Approach via the Data Portal FETCH method. Because as per the book, the FETCH will occur after the check for CanGetObject().

The same problem I will face while checking for Authorization for each property:

AuthorizationRules.AllowRead("PropertyName", "SomeRole")

and

CanReadProperty()

Also, the same lookup mentioned above is needed to check for authorization, which really does not make sense to perform such lookup for each property and for the CanGetObject() method !!!!!##### This will make the application response very slow, because the number of properties if very large (more than 20 properties).

The other problem I am facing is that the control info changes from one case to another (depends on the selected user), this means the method checking for authorization cannot depend on Shared Properties.

Check the standard code for GetStaffProfile():

Public Shared Function GetStaffProfile(ByVal id As String) As StaffProfile
If Not CanGetObject() Then
Throw New System.Security.SecurityException( _
"User not authorized to view staff profile of this Staff.")
End If
Return DataPortal.Fetch(Of StaffProfile)(New Criteria(id))
End Function

Because I was thinking to load the control info once at the beginning, then perform the check when needed, but seems this is not possible because this is a shared method "GetStaffProfile()".

This means that I must first create the object, load the control info, then perform authorization check. So, I was thinking to change the methods above as follows:

<Serializable()> _
public class StaffProfileControl
' Add ControlCriteria to be used to call the FETCH of the Control Info
<Serializable()> _
Private Class ControlCriteria
Private mId As Guid
Public ReadOnly Property Id() As Guid
Get
Return mId
End Get
End Property
Public Sub New(ByVal id As Guid)
mId = id
End Sub
End Class

'Add the call to FETCH the Control Info the Staff Profile as follows
Public Shared Function GetStaffProfileControl(ByVal id As Guid) As StaffProfileControl
Return DataPortal.Fetch(Of StaffProfileControl)(New ControlCriteria(id))
End Function
...
... the rest almost the same
...
End Class

The idea here is to FIRST OF ALL, do fetch the Control Info, and create an new special control object of StaffProfile which has only the control info. So, in the UI Code, this is the first call to be made and any user can access the control info of this object, so no need to do any authorization here.

Then, during the subsequent calls to load the actual StaffProfile object, we can pass the StaffProfileControl Object as a parameter to the methods where needed.

I hope this is the correct way for implementing my requirements using CSLA .NET.

Your feedback will be appreciated.

Tarek.


RockfordLhotka replied on Wednesday, September 03, 2008

tarekahf:

Now going back to CSLA .NET, in the Authorization Section, the relevant Authorization method that applies here is this one:

Public Shared Function CanGetObject() As Boolean
Return Csla.ApplicationContext.User.IsInRole("Some Roles")
End Function

However, as you can see, the Role is hard-coded in the method, and there is no indication on how to perform lookup within this method using CSLA Approach via the Data Portal FETCH method. Because as per the book, the FETCH will occur after the check for CanGetObject().

This is not part of CSLA, this code is in your business class. It is just an example of what you might do, not of what you must do. You can replace this with any other code you'd like. And you can move it to another location.

You might choose to do this check inside DataPortal_Fetch() because that's the only place you can really do the check. That's fine, CSLA doesn't care.

tarekahf:

The same problem I will face while checking for Authorization for each property:

AuthorizationRules.AllowRead("PropertyName", "SomeRole")

and

CanReadProperty()

 

You can override CanReadProperty() and CanWriteProperty() to customize their behavior for a specific type of object. Obviously the implementation in BusinessBase just checks roles, but you could choose to do other checks before or after letting the base class do its work.

tarekahf replied on Wednesday, September 03, 2008

RockfordLhotka:

This is not part of CSLA, this code is in your business class. It is just an example of what you might do, not of what you must do. You can replace this with any other code you'd like. And you can move it to another location.

You might choose to do this check inside DataPortal_Fetch() because that's the only place you can really do the check. That's fine, CSLA doesn't care.

You can override CanReadProperty() and CanWriteProperty() to customize their behavior for a specific type of object. Obviously the implementation in BusinessBase just checks roles, but you could choose to do other checks before or after letting the base class do its work.

Thank you Rocky,

OK, I think I understand what you mean. However, please allow me to summarize the problem, as the previous post I made was very long and complicated.

The problem:

1. In CSLA, checking for authorization is done in Shared Methods, which means you cannot first load the control info into private properties which could be used later for checking if certain operation is authorized for some user.

2. Checking for authorization has nearly same logic in the authorization methods [ Ex. CanReadProperty() and CanGetObject() ], and will have to be repeated. So, if the check requires first to perform Database Access (look-up), this means such data must be made available in private properties so that it can be reused when needed to avoid making Database Lookups over and over.

Please correct me if I misunderstood you.

You suggested to move the check to inside DataPortal_Fetch(), so at this time the base object is created, and I can perform the needed lookup to load the control data into private properties. Is my understanding correct ?

But, I will have the same requirement for authorization checking for the other DataPortal_XXX methods, which may require same control data, or probably others.

That is why I was thinking to create a CSLA .NET Base Object to represent the Control Data. This object will have to be created in the UI code before working with the other regular business objects. And also, it can be cashed so that it can be used by other pages ...etc.

Suppose I have a BO called "StaffProfile". So, I will create another object called "StaffProfileControl" for example. It will be based on CSLA Read Only Business Base, and it is just to load the Control Data into its public properties. And, I will pass this control object as a parameter to the Factory Methods of the regular BO, so that it can be stored as a provide property inside this object for later use any time needed by any other method in this object.

So, the UI Code will do something like this:

dim ctrl as StaffProfileControl
ctrl = StaffProfileControl.getObject(AuthenticatedStaffID, AnotherStaffID)
dim theProfile as StaffProfile
theProfile = StaffProfile.getObject(ctrl)
...
...
theProfile.save()

So, the "ctrl" object will have the necessary data to perform authorization checking, and it can have also any complex code needed for implement the checking, and can be reused over and over without any performance overhead.

You feedback will be appreciated, because I think this is a bit complicated, and my colleagues may not like this approach. I am now trying to make CSLA .NET as part of our standards for Application Development.

Tarek.

RockfordLhotka replied on Wednesday, September 03, 2008

There are three things here I think.

 

1.       You can use Shared/static methods to implement per-type authorization – authorization that is not specific to any instance of an object, but is common for all instances of a given type. That is what you get with the CanGetObject() and similar methods shown in the book. And this is what you get with CSLA 3.5, which formalizes this concept and makes it part of the framework.

2.       You can write your own authorization methods to do per-instance authorization. You need to decide when and where to do these checks, but one logical place is at the top (or bottom) of each DataPortal_XYZ method, because that’s the first opportunity you have to check the security principal against the object instance.

Another logical place to do per-instance authorization is in your factory methods and an override of the Save() method. These are also locations where you have access to the object instance and the security principal.

3.       You can do per-property authorization using CanReadProperty() and CanWriteProperty(). These methods can be overridden so you can customize or replace the default role-based behavior.

 

If you choose to override the per-property authorization, you must remember that your code will run on the client and/or the server. And it will be called A LOT. So you must make the code efficient. The user’s information should be in the principal/identity objects, and you may want to cache the results (CSLA does) so you don’t need to re-check the per-property authz each time.

 

If you choose to implement per-instance authorization in the factory methods and a Save() override, you must remember that your code will run on the client. Again, you need to make this code reasonably efficient – though this isn’t as critical as with per-property, because the factory and Save() methods aren’t called nearly as often.

 

Rocky

 

tarekahf replied on Wednesday, September 03, 2008

Rocky, Thank you again ... the explanation you provided was really so useful.

Please allow me to clarify some points.

RockfordLhotka:

There are three things here I think.

1.       You can use Shared/static methods to implement per-type authorization – authorization that is not specific to any instance of an object, but is common for all instances of a given type. That is what you get with the CanGetObject() and similar methods shown in the book. And this is what you get with CSLA 3.5, which formalizes this concept and makes it part of the framework.

OK, I will use this type of authorization when the checking does not require Database Access (lookup), and in case it will only depend on Role-Based Authorization. Right ?

You can write your own authorization methods to do per-instance authorization. You need to decide when and where to do these checks, but one logical place is at the top (or bottom) of each DataPortal_XYZ method, because that’s the first opportunity you have to check the security principal against the object instance.
This means I will have to load the Control Info inside the DataPortal_XYZ method, and do the check before any further processing or data access. So there is no need to create another Object for loading the control data ?.

Another logical place to do per-instance authorization is in your factory methods and an override of the Save() method. These are also locations where you have access to the object instance and the security principal.
In the factory method ??!! you mean after executing this call:

myStaffProfile = DataPortal.Fetch(Of StaffProfile)(New Criteria(id))

Because only after this call, I will have a instance of the object "myStaffProfile". But this means that authorization checking is done after completing the load of the entire private properties, which may not be wise to do so. I think it is better to check for authorization before loading any other data inside DataPortal_XYZ call.

Another thing which confusing me now is that you are saying that the "security principal" I have access to it in the Factor Methods ...etc. I thought this security principal, I have access to it any time any where in the code during run-time, right ?

Just to confirm that for authorization checking, not only I need to access the control info of the authenticated user, but also need to have access to the control info of the Staff ID which is requested by the authenticated user. This control info is the Department Code of and User Role, and probably few other data fields.

The user’s information should be in the principal/identity objects, and you may want to cache the results (CSLA does) so you don’t need to re-check the per-property authz each time.
Are you telling me that I have to load the control info (eg. Dept. Code) of the authenticated user in the principal/identity objects ? Actually, I tried to do that, but every time I need to get the Dept. Code of the authenticated user using the identity object, then I have to CAST (Convert the Type of) the object to the custom identity object defined in my project. Is my understanding correct ?

Your feedback is always appreciated.

Tarek.

tarekahf replied on Tuesday, April 10, 2012

Hi Rocky,

Just wanted to say thank you ... I have completed the implementation of this Security Class, and I deployed it live for more than one year now.

I have used VS 2005 and CSLA .NET 2.0 and the logic is working like a charm for more than 5 different application objects, and the total number of users is more than 1500 users, and the total number of concurrent users can easily exceed more than 200.

Thanks God, until now, no problem what so ever.

Tarek.

Copyright (c) Marimer LLC