Extend Identity Object / Persist ApplicationContext.LocalContext - Help needed

Extend Identity Object / Persist ApplicationContext.LocalContext - Help needed

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


AndrewR posted on Sunday, October 04, 2009

Hello All,

I would like to extend the identity object to have some custom properties; namely: email, department, group name among others. 

Originally thought I could use ApplicationContext.LocalContext to store the custom properties, but I am finding that the localcontext is not persisted during postback. Perhaps, GlobalContext? But that has implications as it goes back and forth between the client/server.

I'd prefer not to cache or go back to the db for each page request. Here's my implementation, which works only for the first page (request). All subsequent requests show that the context has not been persisted.

Public Class IATIdentity

Inherits ReadOnlyBase(Of IATIdentity)

Implements IIdentity

......(additional code)

Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)

Using cn As New SqlConnection(Database.MISTConnection)

   cn.Open()

   Using cm As SqlCommand = cn.CreateCommand

      cm.CommandText = "uspIATGetWorkflowGroup"

      cm.CommandType = CommandType.StoredProcedure

      cm.Parameters.AddWithValue("@role", criteria.Role)

      Using dr As SqlDataReader = cm.ExecuteReader()

         With dr

         If .Read() Then

            Csla.ApplicationContext.LocalContext.Add("IsAuthenticated", True)

            ApplicationContext.LocalContext.Add("Role", .Item("Role"))

            ApplicationContext.LocalContext.Add("Email", .Item("Email"))

            Csla.ApplicationContext.LocalContext.Add("GroupName", .Item("GroupName"))

            ApplicationContext.LocalContext.Add("GroupDescription", .Item("GroupDescription"))

            ApplicationContext.LocalContext.Add("CanValidateBackOffice", .Item("CanValidateBackOffice"))

            ApplicationContext.LocalContext.Add("CanSubmitBackOffice", .Item("CanSubmitBackOffice"))

            ApplicationContext.LocalContext.Add("CanApproveBackOffice", .Item("CanApproveBackOffice"))

            ApplicationContext.LocalContext.Add("CanReturnBackOffice", .Item("CanReturnBackOffice"))

            ApplicationContext.LocalContext.Add("CanCancelBackOffice", .Item("CanCancelBackOffice"))

            ApplicationContext.LocalContext.Add("CanCompleteBackOffice", .Item("CanCompleteBackOffice"))

            _roles.Add(.Item("Role"))

         Else

            _roles.Clear()

            ApplicationContext.LocalContext.Clear()

         End If

      End With

   End Using

End Using

End Using

End Sub

 

And in the global.asax

Protected Sub Application_AcquireRequestState(ByVal sender As Object, ByVal e As System.EventArgs)

If Not System.Web.HttpContext.Current.Session Is Nothing Then

   Dim principal As System.Security.Principal.IPrincipal

   Try

      principal = CType(Session("IATPrincipal"), System.Security.Principal.IPrincipal)

   Catch ex As Exception

      principal = Nothing

   End Try

   If principal Is Nothing Then

      Dim roleList As List(Of String) = IATPrincipal.GetRoles()

      For Each role As String In roleList

         If User.IsInRole(role) Then

            IATPrincipal.GetPrincipal(role)

            Exit For

         End If

      Next

      System.Web.HttpContext.Current.Session("IATPrincipal") = Csla.ApplicationContext.User

   End If

End If

End Sub

......

I can get the custom properties using a static class I created:

Public Module IATUser

#Region " Public Properties "

Public ReadOnly Property Name() As String

Get

Return Csla.ApplicationContext.User.Identity.Name

End Get

End Property

Public ReadOnly Property IsAuthenticated() As Boolean

Get

Return CType(ApplicationContext.LocalContext("IsAuthenticated"), Boolean)

End Get

End Property

Public ReadOnly Property Role() As String

Get

Return CType(ApplicationContext.LocalContext("Role"), String)

End Get

End Property

Public ReadOnly Property GroupName() As String

Get

Return CType(ApplicationContext.LocalContext("GroupName"), String)

End Get

End Property

 

ABOVE WORKS, BUT ONLY FOR THE FIRST REQUEST. Any help would be greatly appreciated.

Thanks,

Andrew

(Rocky, if you are reading this, my old account ANDREWRAJCOOMAR is locked, and I need a password reset, if you can point me - I have requested a pw reset, but have not seen any emails)

 

JonnyBee replied on Sunday, October 04, 2009

Hi Andrew,

Which version of CSLA do you use and what is your UI technology?

I'd rather make a custom identity and principal object that inherits and extends from CslaIdentity/CslaPrincipal and make sure to initialize these objects on the client. Custom principal/identity may load values from server as shown in the ProjectTracker sample and set this as Csla.ApplicationContext.User.

This is transferred from client to server using the Csla DataPortal with AuthenticationMode set to anything but "Windows".

AndrewR replied on Sunday, October 04, 2009

JonnyBee,

I'm using VS 2008 with Vb.Net, ASP.Net 3.5 and CSLA 3.5. The app is web-based and uses Windows authentication. Authorization is based on NT Security groups. The app has a table mapped to the NT groups and is extended to grant specific permission different roles (NT groups) for management of a workflow.

When a user visits the web site, I grab the list of roles from my table and check to see if the user is in one of those roles. Then based on the role, I get the extended permissions from the table for the workflow.

So basically, what I want to do, is once I authenticate a user, I want to persist the users role and permissions for the duration of the user's session.

I 'd rather not use session variables, cookies or the database to persist this information. But I am not sure what else I have left.

Ragarding your suggestion about custom identity and principal object, I am not sure how to implement such a solution. Please provide some pointers.

Thanks,

Andrew

AndrewR replied on Sunday, October 04, 2009

Ok, so I believe I have all the pieces here, just a matter of being able to persist the custom identity object the same way I can persist ApplicationContext.User.

Here's my code for custom identity object:

<Serializable()> _

Public Class IATIdentity

Inherits ReadOnlyBase(Of IATIdentity) Implements IIdentity

#Region " Members and Properties "

Private _role As String = String.Empty

Private _name As String = String.Empty

Private _IsAuthenticated As Boolean = False

Public ReadOnly Property Name() As String _

Implements System.Security.Principal.IIdentity.Name

Get

Return _name

End Get

End Property

Public ReadOnly Property GroupName() As String

Get

Return Csla.ApplicationContext.LocalContext("GroupName")

End Get

End Property

Public ReadOnly Property Email() As String

Get

Return Csla.ApplicationContext.LocalContext("Email")

End Get

End Property

And from the DataPortal_Fetch

With dr

If .Read() Then

ApplicationContext.LocalContext.Add("IsAuthenticated", True)

ApplicationContext.LocalContext.Add("Role", .Item("Role"))

ApplicationContext.LocalContext.Add("Email", .Item("Email"))

ApplicationContext.LocalContext.Add("GroupName", .Item("GroupName"))

And for my Principal:

<Serializable()> _

Public Class IATPrincipal

Inherits Security.BusinessPrincipalBase

Private Sub New(ByVal identity As IIdentity)

MyBase.New(identity)

End Sub

Public Shared Function GetRoles() As List(Of String)

Return IATIdentity.GetRoles()

End Function

Public Shared Function GetPrincipal(ByVal role As String)

Return SetPrincipal(IATIdentity.GetWorkflowRolePermission(role))

End Function

Public Overrides Function IsInRole(ByVal role As String) As Boolean

Dim identity As IATIdentity = CType(Me.Identity, IATIdentity)

Return identity.IsInRole(role)

End Function

Private Shared Function SetPrincipal(ByVal identity As IATIdentity) As Boolean

If identity.IsAuthenticated Then

Dim principal As IATPrincipal = New IATPrincipal(identity)

Csla.ApplicationContext.User = principal

End If

End Function

End Class

And the static class:

<Serializable()> _

Public Module IATUser

#Region " Public Properties "

Public ReadOnly Property Name() As String

Get

Return Csla.ApplicationContext.User.Identity.Name

End Get

End Property

Public ReadOnly Property IsAuthenticated() As Boolean

Get

Return CType(ApplicationContext.LocalContext("IsAuthenticated"), Boolean)

End Get

End Property

Public ReadOnly Property Role() As String

Get

Return CType(ApplicationContext.LocalContext("Role"), String)

End Get

End Property

Public ReadOnly Property GroupName() As String

Get

Return CType(ApplicationContext.LocalContext("GroupName"), String)

End Get

End Property

Public ReadOnly Property CanValidateBackOffice() As Boolean

Get

Return CType(ApplicationContext.LocalContext("CanValidateBackOffice"), Boolean)

End Get

End Property

#End Region

End Module

And Global.asax, which is where I believe my problem lies. How do I use Session to manage the ApplicationContext.LocalContext?

Protected Sub Application_AcquireRequestState(ByVal sender As Object, ByVal e As System.EventArgs)

If Not System.Web.HttpContext.Current.Session Is Nothing Then

Dim principal As System.Security.Principal.IPrincipal

Try

principal = CType(Session("IATPrincipal"), System.Security.Principal.IPrincipal)

Catch ex As Exception

principal = Nothing

End Try

If principal Is Nothing Then

Dim roleList As List(Of String) = IATPrincipal.GetRoles()

For Each role As String In roleList

If User.IsInRole(role) Then

IATPrincipal.GetPrincipal(role)

Exit For

End If

Next

System.Web.HttpContext.Current.Session("IATPrincipal") = Csla.ApplicationContext.User

End If

End If

End Sub

Let's say I added something like:

System.Web.HttpContext.Current.Session("IATLocalContext") = Csla.ApplicationContext.LocalContext

How do I check for it; cast it and use it, so that firstly, I can get it from the session without having to go to the db again, and then so that in my UI pages, something like:

IATUser.GroupName returns a value (see my static IATUser class).

Thanks,

Andrew

AndrewR replied on Sunday, October 04, 2009

I have a working version. The change was made to the global.asax (changes are in Bold. I assumed that if principal did not exist, then localcontext would also not exist.

I'd really appreciate some feedback on this.

Protected Sub Application_AcquireRequestState(ByVal sender As Object, ByVal e As System.EventArgs)

If Not System.Web.HttpContext.Current.Session Is Nothing Then

Dim principal As System.Security.Principal.IPrincipal

Dim localContext As New System.Collections.Specialized.HybridDictionary

Try

principal = CType(Session("IATPrincipal"), System.Security.Principal.IPrincipal)

localContext = CType(Session("IATLocalContext"), System.Collections.Specialized.HybridDictionary)

Catch ex As Exception

principal = Nothing

localContext = Nothing

End Try

If principal Is Nothing Then

Dim roleList As List(Of String) = IATPrincipal.GetRoles()

For Each role As String In roleList

If User.IsInRole(role) Then

IATPrincipal.GetPrincipal(role)

Exit For

End If

Next

System.Web.HttpContext.Current.Session("IATPrincipal") = Csla.ApplicationContext.User

System.Web.HttpContext.Current.Session("IATLocalContext") = Csla.ApplicationContext.LocalContext

Else

For Each de As DictionaryEntry In localContext

Csla.ApplicationContext.LocalContext.Add(de.Key, de.Value)

Next

End If

End If

End Sub

Thanks,

Andrew

JoeFallon1 replied on Monday, October 05, 2009

A few comments:

1. You are over complicating this.

2. In a web environment you really can't apply all of the restrictions you are setting up. You have to concede someplace.

3. The simplest thing to do is add the custom properties directly to your Identity and Principal classes. Your two classes should inherit from the CSLA base classes that are designed for that purpose. Then store the Principal in Session so that the user's properties are stored between postbacks.

Joe

AndrewR replied on Monday, October 05, 2009

I'll be the first to concede that my implementation may not be the best way. However,s ince I can't come up with another way, I'm stuck with this. Unless someone else would like to share their implementation.

Some background: My UI is a workflow. Users are in NT groups and NT groups are mapped to roles in database. Workflow stages are role-based driven. My UI must be able to disable/enable workflow steps based on the rights granted to the role of the user.

RockfordLhotka replied on Monday, October 05, 2009

AndrewR:

I 'd rather not use session variables, cookies or the database to persist this information. But I am not sure what else I have left.

I think the only options you didn't list are hidden fields in the web page and a flat file on the server's hard drive. Rule those out and you have no options left without switching from the web to a smart client :)

Seriously, web servers are designed to be stateless. It is left to you to decide how (or if) state should be maintained between page requests. You've listed, and ruled out, the options at your disposal, which means you have no way to remember state between pages.

One other option is to re-retrieve the data from the real database every time. Some web sites work this way - thus avoiding storing state in any location (except the real database of course).

The most common option is to use Session, because that's easiest. Adn you can configure Session to run in-proc, out of proc or in a database, so it is very flexible even in web server farm scenarios.

Cookies are a generally bad option because that sends the data over the slowest link in your chain (the client network connection). Also there are size limits on cookies that you can easily exceed with a list of roles from your custom principal/identity object.

Manually using a "session table" in the database can be done, but then you assume responsibility for cleaning up dead sessions and so forth. And since ASP.NET can be used to do this for you it seems like you'd be better off just configuring Session to be stored in a database - less work overall.

The ProjectTracker PTWeb app shows how to use Session. That's pretty easy, and it looks like this is the direction you are going, which is probably wise.

JoeFallon1 replied on Tuesday, October 06, 2009

My second point has been clearly elaborated by Rocky - you have basically ruled out all the State options so why is this a web project? That is why I suggested that you concede one of these points and simply use session to manage state.

Joe

 

AndrewR replied on Tuesday, October 06, 2009

Thanks everyone. I'm sticking with the session with InProc in the web.config.

JoeFallon1 replied on Wednesday, October 07, 2009

One point about InProc - it is an excellent choice during development. Not so good in a production environment. When the AppDomain recycles (which happens *a lot* more frequently than you might imagine) the session is lost and all users will be kicked out of the app regardless of what they were in the middle of. I recommend the use of a State Server (it can be the same web server - it does not have to be remote.) This serializes the session state to a different process which can survive the loss of the AppDomain. It also ensures that you only ever add Serializable objects to session - you get a nice big error when you forget to add that attribute to any of your classes that need to survive a postback.

Joe

 

Copyright (c) Marimer LLC