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 IATIdentityInherits 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,
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
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 IATIdentityInherits 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
dr If .Read() ThenWith
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 FunctionEnd
ClassAnd 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
RegionEnd
ModuleAnd 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 Exceptionprincipal =
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) ThenIATPrincipal.GetPrincipal(role)
Exit For End If NextSystem.Web.HttpContext.Current.Session(
"IATPrincipal") = Csla.ApplicationContext.User End If End If End SubLet'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
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 Tryprincipal =
CType(Session("IATPrincipal"), System.Security.Principal.IPrincipal)localContext =
CType(Session("IATLocalContext"), System.Collections.Specialized.HybridDictionary) Catch ex As Exceptionprincipal =
NothinglocalContext =
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) ThenIATPrincipal.GetPrincipal(role)
Exit For End If NextSystem.Web.HttpContext.Current.Session(
"IATPrincipal") = Csla.ApplicationContext.UserSystem.Web.HttpContext.Current.Session(
"IATLocalContext") = Csla.ApplicationContext.LocalContext Else For Each de As DictionaryEntry In localContextCsla.ApplicationContext.LocalContext.Add(de.Key, de.Value)
Next End If End If End Sub Thanks,Andrew
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
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.
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.
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
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