Custom Authentication - (More) help please!

Custom Authentication - (More) help please!

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


gajit posted on Tuesday, February 21, 2012

OK, I'm *almost* there with implementing some custom authentication for my windows forms using csla4.

I'm struggling somewhat with the Dal implementation of it though. The sample projects use a MockDb and i'm having difficulties trying to make the correct references between interfaces and objects.

Here are the classes/interfaces I have currently:

DataAccess.IIdentityDal:

 Public Interface IIdentityDal

    Function VerifyUser(username As String, password As String) As Boolean
    Function GetUser(username As String) As IDataReader
    Function GetRoles(username As String) As IDataReader

End Interface

 

 

DataAccess.IdentityDal: (I can't put this in DataAccess.SQL as I would have to make a reference from my library to the specific class library for SQL - which means no portability - why?)

Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla.Data
Imports System.Data.SqlClient

Public Class IdentityDal
    Implements IIdentityDal

    Public Function VerifyUser(username As String, password As String) As Boolean Implements IIdentityDal.VerifyUser

        Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
            Using cm = ctx.Connection.CreateCommand()
                cm.CommandType = System.Data.CommandType.StoredProcedure
                cm.CommandText = "VerifyUser"
                cm.Parameters.AddWithValue("@ps_Username", username)
                cm.Parameters.AddWithValue("@ps_Password", password)
                cm.Parameters.AddWithValue("@pn_IsValid", 0)
                cm.Parameters("@pn_IsValid").Direction = ParameterDirection.Output

                Dim r = cm.ExecuteScalar()
                Dim newId As Boolean = cm.Parameters("@pn_IsValid").Value
                Return newId
            End Using
        End Using

    End Function

    Public Function GetUser(username As String) As System.Data.IDataReader Implements IIdentityDal.GetUser

        Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
            Dim cm = ctx.Connection.CreateCommand()
            cm.CommandType = System.Data.CommandType.Text
            cm.CommandText = _
                                    "SELECT " + _
                                    "   u.USERNAME " + _
                                    "   , u.PASSWORD " +
                                    "FROM dbo.USERS u ( NOLOCK ) " + _
                                    "WHERE ( u.USERNAME = @ps_Username )"
            cm.Parameters.AddWithValue("@ps_Username", username)
            Return cm.ExecuteReader()
        End Using

    End Function

    Public Function GetRoles(username As String) As System.Data.IDataReader Implements IIdentityDal.GetRoles

        Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
            Dim cm = ctx.Connection.CreateCommand()
            cm.CommandType = System.Data.CommandType.Text
            cm.CommandText = _
                                    "SELECT " + _
                                    "   r.USERNAME " + _
                                    "   , r.ROLE " +
                                    "FROM dbo.Roles r ( NOLOCK ) " + _
                                    "WHERE ( r.USERNAME = @ps_Username )"
            cm.Parameters.AddWithValue("@ps_Username", username)
            Return cm.ExecuteReader()
        End Using

    End Function

End Class


Library.Net.CustomPrincipal:

 Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla
Imports Csla.Security
Imports Csla.Serialization

Namespace Security

    <Serializable()> _
    Public Class CustomPrincipal
        Inherits CslaPrincipal
        Private Sub New(identity As CustomIdentity)
            MyBase.New(identity)
        End Sub

        Public Shared Sub BeginLogin(username As String, password As String, completed As Action(Of Exception))
            CustomIdentity.GetCustomIdentity(username, password, Sub(o, e)
                                                                     If e.[Error] IsNot Nothing Then
                                                                         Logout()
                                                                     Else
                                                                         Csla.ApplicationContext.User = New CustomPrincipal(e.[Object])
                                                                     End If
                                                                     completed(e.[Error])
                                                                 End Sub)
        End Sub

#If Not Silverlight Then
        Public Shared Sub Login(username As String, password As String)
            Dim identity = CustomIdentity.GetCustomIdentity(username, password)
            Csla.ApplicationContext.User = New CustomPrincipal(identity)
        End Sub

        Public Shared Sub Load(username As String)
            Dim identity = CustomIdentity.GetCustomIdentity(username)
            Csla.ApplicationContext.User = New CustomPrincipal(identity)
        End Sub
#End If

        Public Shared Sub Logout()
            Csla.ApplicationContext.User = New UnauthenticatedPrincipal()
        End Sub
    End Class
End Namespace

Library.Net.CustomIdentity:

Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports Csla
Imports Csla.Security
Imports Csla.Serialization

Namespace Security

    <Serializable()> _
    Public Class CustomIdentity
        Inherits CslaIdentityBase(Of CustomIdentity)

        Public Shared ReadOnly FullNameProperty As PropertyInfo(Of String) = RegisterProperty(Of String)(Function(c) c.FullName)
        Public Property FullName() As String
            Get
                Return GetProperty(FullNameProperty)
            End Get
            Private Set(value As String)
                LoadProperty(FullNameProperty, value)
            End Set
        End Property

        Public Shared Sub GetCustomIdentity(username As String, password As String, callback As EventHandler(Of DataPortalResult(Of CustomIdentity)))
            DataPortal.BeginFetch(Of CustomIdentity)(New UsernameCriteria(username, password), callback)
        End Sub

#If Not Silverlight Then
        Public Shared Function GetCustomIdentity(username As String, password As String) As CustomIdentity
            Return DataPortal.Fetch(Of CustomIdentity)(New UsernameCriteria(username, password))
        End Function

        Friend Shared Function GetCustomIdentity(username As String) As CustomIdentity
            Return DataPortal.Fetch(Of CustomIdentity)(username)
        End Function

        Private Sub DataPortal_Fetch(criteria As UsernameCriteria)

            AuthenticationType = "Custom"

            Dim dal = New DataAccess.IdentityDal
            If dal.VerifyUser(criteria.Username, criteria.Password) Then
                LoadUserData(criteria.Username, dal)
            End If

        End Sub

        Private Sub DataPortal_Fetch(username As String)
           
            AuthenticationType = "Custom"

            Dim dal = New DataAccess.IdentityDal
            LoadUserData(username, dal)

        End Sub

        Private Sub LoadUserData(username As String, dal As DataAccess.IdentityDal)

            Dim userData As CustomIdentity = dal.GetUser(username)
            IsAuthenticated = (userData IsNot Nothing)
            If IsAuthenticated Then
                Name = userData.Name
                FullName = userData.FullName
                Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name))
            End If
        End Sub
#End If
    End Class

End Namespace

 

The problem I have is when the call is made here: - 

 Public Shared Function GetCustomIdentity(username As String, password As String) As CustomIdentity
            Return DataPortal.Fetch(Of CustomIdentity)(New UsernameCriteria(username, password))
        End Function

I get a "DataPortal.Fetch failed (Unable to cast object of type 'System.Data.SqlClient.SqlDataReader' to type Library.Security.CustomIdentity'.)"

I can see WHY it blows up, but I have no clue how to rectify it. It seems that I'm goinf around in circles with creating the Identity, IIdentity and CustomIdentity pieces - and I'm pretty sure something is in the wrong place, as I my IdentityDal should be in my Database-specific Dal library (.SQL)

This is obviously the offending code:>

    Public Function GetUser(username As String) As System.Data.IDataReader Implements IIdentityDal.GetUser

        Using ctx = ConnectionManager(Of SqlConnection).GetManager("Security")
            Dim cm = ctx.Connection.CreateCommand()
            cm.CommandType = System.Data.CommandType.Text
            cm.CommandText = _
                                    "SELECT " + _
                                    "   u.USERNAME " + _
                                    "   , u.PASSWORD " +
                                    "FROM dbo.USERS u ( NOLOCK ) " + _
                                    "WHERE ( u.USERNAME = @ps_Username )"
            cm.Parameters.AddWithValue("@ps_Username", username)
            Return cm.ExecuteReader() '<*************** Idatareader, but I need 'customidentity'(??)
        End Using

    End Function

 Any ideas?

Thanks,

Graham

 

 

 

 

JonnyBee replied on Tuesday, February 21, 2012

            Dim userData As CustomIdentity = dal.GetUser(username)

dal.GetUser returns an IDataReader - and you cannot cast an IDataReader to CustomIdentiy (just as the exception says).

gajit replied on Tuesday, February 21, 2012

Thanks Jonny. I realized that - and was posting as you replied.

Still have an issue though... ^

Thanks,

Graham

gajit replied on Tuesday, February 21, 2012

Getting closer (plus I figured out the structure of the classes so that the interface IIdentity is in DataAccess and IdentityDal is in DataAccess.SQL)

I corrected the Fetch(es) in CustomIdentity:

        Private Sub DataPortal_Fetch(criteria As UsernameCriteria)

            AuthenticationType = "Custom"

            Using dalManager = DataAccess.DalFactory.GetManager()

                Dim dal = dalManager.GetProvider(Of DataAccess.IIdentityDal)()
                If dal.VerifyUser(criteria.Username, criteria.Password) Then
                    LoadUserData(criteria.Username, dal)
                End If

            End Using

        End Sub

        Private Sub DataPortal_Fetch(username As String)
           
            AuthenticationType = "Custom"

            Using dalManager = DataAccess.DalFactory.GetManager()

                Dim dal = dalManager.GetProvider(Of DataAccess.IIdentityDal)()
                LoadUserData(username, dal)

            End Using

        End Sub 

 

Still get an exception though..... I'll investigate a bit further see if I can;t nail down where it's comingfrom.

Thanks,

Graham

 

 

 

gajit replied on Tuesday, February 21, 2012

2 steps forward, and 3 back.

I isolated my latest issue to the get of the DalManager type.

The implementation of DalManager/IDalManager previous used the App.settings to retrieve a database connection for my application.

Because the security tables are in a different database, it wasn't marrying up correctly. Are we supposed to have a different IDalManager interface for the authentication objects?

Now though, I'm getting a

 (There is already an open DataReader associated with this Command which must be closed first.)

When the GetCustomIdentity is fired....

Grrrrrrrrrrr...

 

 

gajit replied on Tuesday, February 21, 2012

another step forward.

I get the "not closed" error when it attempts to do THIS call:

    If IsAuthenticated Then
                    Name = data.GetString("USERNAME")
                    FullName = data.GetString("USERNAME")
                    Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name)) '****< error
    End If 

calling the IdentityDal's GetRoles is trying to use another connection.

How *should* I be implementing this? Should I be passing the ctx as a parameter to get roles?

Seems strange...

G.

 

 

JonnyBee replied on Tuesday, February 21, 2012

Long discussion here on the same theme:

http://forums.lhotka.net/forums/p/11065/51435.aspx#51435

rocky

The thing with building a DAL interface based on IDataReader is that you have to fully understand how data readers and connections work, and live within those constraints.

Rocky uses this technique in the Data Access samples so have a look there.

gajit replied on Tuesday, February 21, 2012

OK, will take a look and let you know how I make out.

Thanks,

Graham

gajit replied on Tuesday, February 21, 2012

Nope Jonny.

I still don't get it.

My code around LoadUserData is exactly the same as Rocky's in the DataPortal book which discusses implmentation of CustomIdentity - but the sample project only shows a MockDb implementation. I'm guessing that has no connection pooling issues - this does.

If I were creating a parent/Child bo I'd have obviously strong-typed classes for say, User and UserRole - but this is custom authentication and we should need thsm as we are using the identity base.

I can hack away at this code but I'd like to see the correct, recommended solution for the problem - which I just don;t see in the samples OR in the book.

Thanks,

Graham

 

 

 

 

 

gajit replied on Tuesday, February 21, 2012

OK, I figured it out. I *believe*...

If you could have a look over what I've done and verify that it's a reasonable approach.

My LoadUserData now looks like:

Private Sub LoadUserData(username As String, dal As ParabolaSols.DataAccess.IIdentityDal)

            Using data As New Csla.Data.SafeDataReader(dal.GetUser(username))
                data.Read()
                IsAuthenticated = (data IsNot Nothing)

                If IsAuthenticated Then
                    Name = data.GetString("USERNAME")
                    FullName = data.GetString("USERNAME")
                End If

            End Using
            Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(username))

        End Sub 

I moved the Roles outside of the using and also realized that the List implementation of Roles needed to be handled correctly, so in my GetRoles I now do.

    Public Function GetRoles(username As String) As Csla.Core.MobileList(Of String) Implements IIdentityDal.GetRoles

        Using ctx = ConnectionManager(Of SqlConnection).GetManager("ParabolaSols_Security")
            Dim cm = ctx.Connection.CreateCommand()
            cm.CommandType = System.Data.CommandType.Text
            cm.CommandText = _
                                    "SELECT " + _
                                    "   r.USERNAME " + _
                                    "   , r.ROLE " +
                                    "FROM dbo.Roles r ( NOLOCK ) " + _
                                    "WHERE ( r.USERNAME = @ps_Username )"
            cm.Parameters.AddWithValue("@ps_Username", username)

            Dim roles = New Csla.Core.MobileList(Of String)

            Using data As New Csla.Data.SafeDataReader(cm.ExecuteReader)

                While data.Read

                    data.Read()
                    roles.Add(data("Name"))

                End While

            End Using

            Return roles

        End Using

    End Function 

 That works!

Now, what I'd really like to do is NOT use a list, but if that's too much like hard work, I'll settle.

Does that look correct? Could it be optimized?

Thanks,

Graham

gajit replied on Tuesday, February 21, 2012

I *thought* I'd figured it out.

I changed the LoadUserData in CustomIdentity to:

        Private Sub LoadUserData(username As String, dal As ParabolaSols.DataAccess.IdentityDal)

            Using data As New Csla.Data.SafeDataReader(dal.GetUser(username))
                data.Read()
                IsAuthenticated = (data IsNot Nothing)

                If IsAuthenticated Then
                    Name = data.GetString("Name")
                    FullName = data.GetString("FullName")
                    Roles = New Csla.Core.MobileList(Of String)(dal.GetRoles(Name))
                End If

            End Using

        End Sub

Clearly, I need to load the data from the datareader (duh).

But now I get the error:

DataPortal.Fetch failed (Invalid attempt to call Read when reader is closed.)

I still think something is wrong with the way my classes are structured though...

 

Thanks,

Graham

Copyright (c) Marimer LLC