Friday, August 15, 2008

Implementing Active Directory Services in ASP.NET 2.0

Active Directory and VB.NET




Microsoft Active Directory is a directory service that provides the foundation for distributed networks built on Windows 2000 and later domain controllers. The Active Directory APIs provide access to the data stored in a directory.



Active Directory Architecture.



The directory system agent (DSA) is the process that provides access to the store. The store is the physical store of directory information located on a hard disk. Clients access the directory using one of the following mechanisms supported by the DSA:






  • LDAP clients connect to the DSA using the LDAP protocol. LDAP is an acronym for Lightweight Directory Access Protocol. Active Directory supports LDAP 3.0, defined by RFC 2251, and LDAP 2.0, defined by RFC 1777.





  • MAPI clients such as Microsoft Exchange connect to the DSA using the MAPI remote procedure call interface.





  • Windows clients that use a previous version of Windows NT connect to the DSA using the Security Account Manager (SAM) interface.





  • Active Directory DSA's connect to each other to perform replication using a proprietary remote procedure call interface.




Active Directory data model is derived from the X.500 data model. The directory holds objects that represent things of various sorts, described by attributes. The universe of objects that can be stored in the directory is defined in the schema. For each object class, the schema defines what attributes an instance of the class must have, what additional attributes it may have, and what object class can be a parent of the current object class.



Active Directory schema is implemented as a set of object class instances stored in the directory. This is very different than many directories that have a schema but store it as a text file read at startup. Storing the schema in the directory has many advantages. For example, user applications can read it to discover what objects and properties are available.



Active Directory can consist of many partitions or naming contexts. The distinguished name (DN) of an object includes enough information to locate a replica of the partition that holds the object. Many times however, the user or application does not know the DN of the target object or which partition might contain the object. The global catalog (GC) allows users and applications to find objects in an Active Directory domain tree, given one or more attributes of the target object. The global catalog contains a partial replica of every naming context in the directory. It contains the schema and configuration naming contexts as well. This means the GC holds a replica of every object in Active Directory but with only a small number of their attributes.



The global catalog is built automatically by Active Directory replication system. The replication topology for the global catalog is generated automatically. The properties replicated into the global catalog include a base set defined by Microsoft. Administrators can specify additional properties to meet the needs of their installation.



Interfaces for accessing the Active Directory.






  1. LDAP. The Lightweight Directory Access Protocol (LDAP) is a directory service protocol that runs on a layer above the TCP/IP stack, and provides a mechanism for connecting to, searching, and modifying Internet directories. The LDAP directory service is based on a client-server model. The function of LDAP is to allow access to an existing directory. The data model (data and namespace) of LDAP is similar to that of the X.500 OSI directory service, but with lower resource requirements due to its streamlined features. The associated LDAP API simplifies writing Internet directory service applications.





  2. ADSI. Active Directory Service Interfaces (ADSI) is a set of COM interfaces used to access the capabilities of directory services from different network providers in a distributed computing environment, to present a single set of directory service interfaces for managing network resources. Administrators and developers can use ADSI services to enumerate and manage the resources in a directory service, regardless of the network environment that contains the resource.





  3. System.DirectoryServices. System.DirectoryServices is a namespace in the .NET Framework that provides simple programming access to LDAP directories such as Active Directory. System.DirectoryServices is built on the Active Directory Service Interfaces (ADSI) API.

    Using System.DirectoryServices namespace.



    This article will emphasize in the benefits of using the namespace System.DirectoryServices, such as:






    • Designed completely within common language runtime parameters. System.DirectoryServices leverages common language runtime features, such as garbage collection, custom indexer, and dictionaries (hashtables). It also offers other common language runtime features such as automatic memory management, efficient deployment, an object-oriented framework, evidence-based security and exception handling.





    • Simple to use. Although ADSI scripting was effective for many tasks, C++ applications for ADSI are sometimes difficult to develop. System.DirectoryServices implements some basic ADSI tasks to enable more efficient and effective application development.




    System administrators can use System.DirectoryServices to automate tasks to manage network resources in the directory, such as users and computers and also to build applications that search, create, or modify objects in a directory.



    Requirements. System.DirectoryServices is supported on Windows Server 2003. System.DirectoryServices can be redistributed on Windows 98, Windows 98 SE and Windows NT 4.0, as long as the DS Client is installed on client machines. It can also be redistributed on Windows 2000 Windows XP.



    I developed a lot of business objects which access the Active Directory, leveraging any application which needs the platform as its main database and for publishing objects in enterprise network.



    It's defined the interface for the business objects which serve as changing or setting up the password for a specific user in the directory. Later this interface is implemented with a class, which instances make the real interaction with the directory.



    In listing 1, it's shown the contract IADPasswdManager and the class ADPasswdManager.



    Imports System

    Imports System.DirectoryServices

    Namespace OLAActiveDirectory.Management

    Public Interface IADPasswdManager

    Sub ChangePassword(ByVal objUser As IADUser, ByVal strOldPasswd As String, ByVal strNewPasswd As String)

    Sub SetPassword(ByVal objUser As IADUser, ByVal strPasswd As String)

    End Interface

    Public Class ADPasswdManager : Implements IADPasswdManager

    Public Sub New()

    End Sub

    Public Sub SetPassword(ByVal objUser As IADUser, ByVal strPasswd As String) Implements IADPasswdManager.SetPassword

    Dim objLoginEntry As DirectoryEntry=objUser.DirectoryEntry

    If Not objLoginEntry Is Nothing Then

    objLoginEntry.Invoke("SetPassword", New Object(){strPasswd})

    objLoginEntry.CommitChanges()

    End If

    End Sub

    Public Sub ChangePassword(ByVal objUser As IADUser, ByVal strOldPasswd As String, ByVal strNewPasswd As String) Implements IADPasswdManager.ChangePassword

    Dim objLoginEntry As DirectoryEntry=objUser.DirectoryEntry

    If Not objLoginEntry Is Nothing Then

    objLoginEntry.Invoke("ChangePassword",New Object(){strOldPasswd,strNewPasswd})

    objLoginEntry.CommitChanges()

    End If

    End Sub

    End Class

    End Namespace



    Listing 1.



    A business entity must be defined for the users of the directory. It has all the information of a particular user in the directory knowing its Distinguished Name (DN).



    It's defined an interface IADUser and the implementation is realized in the class ADUser as shown in the Listing 2.



    Imports System

    Imports System.DirectoryServices

    Imports System.Collections

    Namespace OLAActiveDirectory.Management

    Public Interface IADUser

    ReadOnly Property DirectoryEntry() As DirectoryEntry

    ReadOnly Property IsUser() As Boolean

    ReadOnly Default Property Item(ByVal strKey As String) As PropertyValueCollection

    End Interface

    Public Class ADUser : Implements IADUser

    Private ReadOnly m_objUserEntry As DirectoryEntry

    Public Sub New(ByVal strLogin As String, ByVal strRootPath As String)

    Dim objRootEntry As DirectoryEntry = New DirectoryEntry(strRootPath)

    Dim objADSearcher As DirectorySearcher = New DirectorySearcher(objRootEntry)

    objADSearcher.Filter="(&(objectClass=user)(anr=" & strLogin & "))"

    Dim objResult As SearchResult=objADSearcher.FindOne()

    If (Not objResult Is Nothing) Then

    Me.m_objUserEntry=objResult.GetDirectoryEntry()

    Else

    Me.m_objUserEntry=Nothing

    End If

    End Sub

    Public ReadOnly Property DirectoryEntry() As DirectoryEntry Implements IADUser.DirectoryEntry

    Get

    Return Me.m_objUserEntry

    End Get

    End Property

    Public ReadOnly Default Property Item(ByVal strKey As String) As PropertyValueCollection

    Get

    Return Me.m_objUserEntry.Properties(strKey)

    End Get

    End Property

    Public ReadOnly Property IsUser() As Boolean Implements IADUser.IsUser

    Get

    Return Not Me.m_objUserEntry Is Nothing

    End Get

    End Property

    End Class

    End Namespace



    Listing 2.



    In the Presentation Layer resides an instance of the class ADUserInfoShower whose role is to create an information string for a specific user. This object is independent of the technology used for showing the user information. That is, this string can be rendered in a Web Browser, a Windows Client and a Mobile Device. In the listing 3, it's shown the code for this business object.



    Imports System

    Namespace OLAActiveDirectory.Management

    Public Interface IADUserInfoShower

    Function GetInformation(ByVal objUser As IADUser, ByVal strSep As String) As String

    End Interface

    Public Class ADUserInfoShower : Implements IADUserInfoShower

    Private Function prvInfoBuilder(ByVal objUser As IADUser, ByVal strSep As String) As String

    Dim strResult As String

    strResult="Fullname:" & objUser("givenName").Value & " " & objUser("sn").Value

    strResult &= strSep & "Mail:" & objUser("mail").Value

    strResult &= strSep & "Telephone(s):" & objUser("telephoneNumber").Value

    For Each strPhone As String In objUser("otherTelephone")

    strResult &= strSep & strPhone

    Next strPhone

    Return strResult

    End Function

    Public Sub New()

    End Sub

    Public Function GetInformation(ByVal objUser As IADUser, ByVal strSep As String) As String Implements IADUserInfoShower.GetInformation

    Return Me.prvInfoBuilder(objUser,strSep)

    End Function

    End Class

    End Namespace





====================================================================
จากตัวอย่างในเรื่องการ สร้าง Authen กับ Active Directory นั้น ทำการ authen นั้นผ่าน protocal LDAP ได้ ซึ่ง ผมได้ทำตัวอย่างไว้ ทั้งแบบ VB6 และแบบของ .Net เอง สร้างเป็น Class DLL แล้วนำไปใช้ใน Project นะครับ ทีนี้ เราก็จะสามารถ ตัดปัญหายุ่งยากเรื่อง username และ password อันหลากหลายในระบบงานที่พัฒนาขึ้นมาได้แล้วนะครับ ก็จะเป็นการทำ ในเรื่อง single singon นั่นเอง


============ เริ่มจาก ของ VB6 ก่อนเลยนะครับ ============
สำหรับในตัว VB6 นั้นข้อดีของการสร้าง Active X control จาก vb6 ทำให้เราสามารถ นำ Dll นั้นไปใช้งานกับ Project ที่พัฒนาด้วย เทคโนโลยีตัวเก่า ได้นะครับ เช่นใช้งานกับ vba ของ access หรือ excel หรือใช้งานร่วมกับ ภาษาโปรแกรมตัวอื่นๆ ก็ได้ครับ หรือ จะเอาไปใช้กับเทคโนโลยีของ .net ด้วยก็ยังได้ เพราะ dll ของเทคโนโลยีตัวเก่า สามารถ ใช้งานกับตัวใหม่ได้ แต่ถ้าพัฒนาจาก .net จะไม่สามารถนำไปใช้กับ ระบบงานเก่าๆ ได้ครับ


ของเริ่มเรื่อง เลยนะครับ


เริ่มจาก เปิด visual studio 6 ขึ้นมา สร้าง Project Active X Dll นะครับ
จากนั้นให้ เปลี่ยนชื่อ class เป็นชื่ออะไรก็ได้ ในที่นี้ผม ใช้ชื่อ cssLoginAD





Option Explicit
Const ADS_SECURE_AUTHENTICATION = 1
' method สำหรับการตรวจสอบ Login AD
Public Function ValidateAuthenAD(ByVal UserName As String, _
ByVal Domain As String, _
ByVal Password As String) As Integer
Dim MyNamespace As IADsOpenDSObject
Dim X As IADsContainer
Dim oUserName As String
Dim oPassword As String
oUserName = UserName
oPassword = Password
' ตรวจสอบ login AD
On Error GoTo CleanUp
If InStr(1, UserName, ".", vbTextCompare) <> 0 And InStr(1, UserName, "@", vbTextCompare) = 0 Then
oUserName = UserName
End If
' กำหนด Potocal LDAP สำหรับการเข้าไปค้นหา
Set MyNamespace = GetObject("LDAP:")
Set X = MyNamespace.OpenDSObject("LDAP://" + Domain, oUserName, oPassword, ADS_SECURE_AUTHENTICATION)
ValidateAuthenAD = 0
GoTo FinFonction
CleanUp:
Select Case Err.Number
Case -2147023570 ' Return Error หาก password ไม่ถูกต้อง
ValidateAuthenAD = 1
Case -2147016646 ' return Error หาก Domain ไม่ถูกต้อง
ValidateAuthenAD = 2
Case Else ' return Error อื่นๆ
ValidateAuthenAD = 99
End Select
FinFonction:
Set MyNamespace = Nothing
Set X = Nothing
End Function



หลักการของโปรแกรม



Class ActiveX cssLoginAD ในเวอร์ชั่นนี้จะประกอบไปด้วย Method ให้เรียกใช้งานได้ดังต่อไปนี้


ValidateAuthenAD Method เป็นชนิด Integer การทำงานของ method จะเปิด Directory Service ขึ้นมา เพื่อเข้าไปตรวจสอบ user และ pass และ Domain controller โดยผ่าน โปรโตคอล LDap เพื่อ Search หา user และ pass ใน DC ว่ามีอยู่หรือไม่ โดยจะ คืนค่ากลับมา เป็น 0 คือ ตรวจหาพบ หรือ error อื่นๆ ดังนี้
0 = login ถูกต้อง
1 = password ไม่ถูก
2 = ไม่พบ Domain
99 = error อื่นๆ
parameter ที่จะส่งเข้าไปใน method นี้
1. UserName
2. Domain
3. Password





//จากนั้นให้ทำการ Complier dll ออกมาเท่านี้ก็จะสามารถใช้งาน Authen AD ได้แล้วครับ

//เมื่อนำ Dll นั้นไปใช้กับ Project 

// สร้าง ฟังชั่นขึ้นมาเพื่อตรวจสอบ กับ Class cssLoginAD
Private Function chkLogin(ByVal user As String, ByVal pass As String, ByVal dc As String) As Boolean
Dim result As Boolean
Dim AuthenAD As New cssLoginAD
Dim valReturn As Integer
result = False ' กำหนด ให้ ค่าเริ่มต้นเป็น False
If user <> "" And pass <> "" And dc <> "" Then
valReturn = AuthenAD.ValidateAuthenAD(user, dc, pass)
Select Case valReturn
Case 0 ' กรณี Login ถูกต้อง
result = True
Case 1 ' กรณี password ผิด
MsgBox "กรุณาระบุ password ให้ถูกต้องด้วย !!", vbOKOnly + vbCritical, "Login ผิด !!"
Case 2 ' กรณี ชื่อ Domain ผิด
MsgBox "กรุณาระบุ Domain ให้ถูกต้องด้วย !!", vbOKOnly + vbCritical, "Login ผิด !!"
Case Else ' error อื่นๆ
MsgBox "Error !!"
End Select
Else
' หากกรอกข้อมูลไม่ครับ
MsgBox "กรุณากรอกข้อมูลให้ครบถ้วนด้วย !!", vbOKOnly + vbCritical, "Validate Form!!"
End If
Set AuthenAD = Nothing
chkLogin = result
End Function
‘ ส่วนของ ปุ่ม Submit
Private Sub Command1_Click()
If chkLogin(txtuser.Text, txtpassword.Text, Combo1.Text) Then
MsgBox "Login ถูกต้อง ยินดีต้อนรับคุณ " & txtuser.Text
Call clearText
End If
End Sub

จากตัวอย่างด้านบนเป็นการใช้ Active X Dll ที่พัฒนาจาก vb6 สำหรับระบบงาน เก่าๆ หรือใช้งาน กับ vba กับ Access นะครับ
ทีนี้ เรามาดูพระเอกของบทความนี้กันดีกว่า สร้าง Authen Active Directory ด้วย .Net

สำหรับใน .Net นั้นนะครับ ได้เตรียม namespace ไว้ให้เราเรียบร้อย จะใช้  System.DirectoryServices
ทีนี้เรามาสร้าง Class สำหรับ authen กับ Acitve directory กันนะครับ

อันดับแรก ก็เปิด vs.net แล้ว สร้าง class ขึ้นมานะครับ ดังโคดด้านล่างนี้



 Imports System.DirectoryServices  ' impoart namespace สำหรับจัดการ directoryservice

Namespace LoginAD ' สร้าง namespace ชื่อ LoginAD เอาไว้จัดกลุ่ม class นะครับ
Public Class css_AuthenAD
' สร้าง private property
Private strUser As String
Private strPass As String
Private strDomain As String
Private authenType As AuthenticationTypes

' ===============================
' สร้าง Public method
' method สำหรับ Set Domain
Public Function SetDomain(ByVal strValue As String) As Boolean
If strValue.Length <= 0 Then Return False
Me.strDomain = "LDAP://" & strValue
Return True
End Function

' method สำหรับ Set User
Public Function SetUser(ByVal strValue As String) As Boolean
If strValue.Length <= 0 Then Return False
Me.strUser = strValue
Return True
End Function







' method สำหรับ Set password
Public Function SetPass(ByVal strValue As String) As Boolean
If strValue.Length <= 0 Then Return False
Me.strPass = strValue
Return True
End Function

Public Sub SetAuthenType(Optional ByVal bValue As Boolean = False)
If bValue Then
' type สำหรับ edirectory
authenType = AuthenticationTypes.SecureSocketsLayer
Else
' type สำหรับ Active Directory
authenType = AuthenticationTypes.Secure
End If
End Sub

' และ method login สำหรับเอาไว้ authen นะครับ
Public Function Login() As Boolean
Dim result As Boolean = False
Dim DirEntry As New DirectoryEntry(Me.strDomain, _
Me.strUser, _
Me.strPass, _
Me.authenType)
Try
Dim native As Object = DirEntry.NativeObject()
result = True
Catch ex As Exception
result = False
End Try
Return result
End Function
' ===============================
End Class
End Namespace


No comments: