VB.NET LDAP Class

J

Jon Delano

Hello

After some effort I was able to come up with this class (converted from a C#
example on MS' web site).
Put this in a class in you vb.net web project and you should be able to
authenticate a user against active directory and also retrieve their user
groups.

You must replace the LDAP://yourdomain with the actual domain that you'd
like to authenticate against.

Please do with this as you will (and use at your own risk):

Imports System.DirectoryServices
Imports System.Runtime.InteropServices
Imports System.Globalization

Public Class ADAuthenticate
Private _path As String
Private _filterAttribute As String
Private _UserFirstName As String
Private _UserLastName As String
Private _UserPath As String
Private _AuthenticationErrorString As String

Public Function IsAuthenticated(ByVal UserName As String, ByVal Password
As String) As Boolean
' authenticate the user and get some info about them
Dim entry As New DirectoryEntry("LDAP://yourdomain", UserName,
Password, System.DirectoryServices.AuthenticationTypes.Secure)
Dim ds As New DirectorySearcher(entry)
Dim myFilter As String = "(&(objectClass=user)(samaccountname=" +
UserName + "))"

ds.Filter = myFilter
ds.PropertiesToLoad.Add("sn")
ds.PropertiesToLoad.Add("GivenName")

Try
Dim sRslt As SearchResult = ds.FindOne
If sRslt Is Nothing Then
Return False
Else
_filterAttribute = UserName
Dim propName As String
Dim value As Object

For Each propName In sRslt.Properties.PropertyNames
For Each value In sRslt.Properties(propName)
If propName = "sn" Then
_UserLastName = value
End If

If propName = "givenname" Then
_UserFirstName = value
End If

If propName = "adspath" Then
_UserPath = value
End If

Next value
Next propName

Return True
End If

sRslt = Nothing
ds = Nothing

Catch ex As Exception
_AuthenticationErrorString = ex.Message & "<br>" & ex.StackTrace
Return False
Finally
entry.Dispose()
entry = Nothing
ds.Dispose()
ds = Nothing
End Try

End Function

Public ReadOnly Property LdapAuthenticationErrorString() As String
Get
Return _AuthenticationErrorString
End Get
End Property

Public ReadOnly Property LdapUserFirstName() As String
Get
Return _UserFirstName
End Get
End Property

Public ReadOnly Property LdapUserLastName() As String
Get
Return _UserLastName
End Get
End Property

Public Function GetUserGroups(ByVal UserName As String, ByVal Password
As String) As String
' get the groups the user belongs to
Dim entry As New DirectoryEntry("LDAP://yourdomain", UserName,
Password, System.DirectoryServices.AuthenticationTypes.Secure)
Dim search = New DirectorySearcher(entry)

search.Filter = "(&(objectClass=user)(cn=" + _filterAttribute + "))"
search.PropertiesToLoad.Add("memberOf")

Dim groupNames As New System.Text.StringBuilder()

Try
Dim result As SearchResult = search.FindOne()
Dim propertyCount As Int32 = result.Properties("memberOf").Count
Dim dn As String
Dim equalsIndex As Int32, commaIndex As Int32
Dim propertyCounter As Int32

For propertyCounter = 0 To propertyCount - 1
dn = result.Properties("memberOf")(propertyCounter)
equalsIndex = dn.IndexOf("=", 1)
commaIndex = dn.IndexOf(",", 1)
If (-1 = equalsIndex) Then
groupNames.Append(dn)
Else
groupNames.Append(dn.Substring((equalsIndex + 1),
(commaIndex - equalsIndex) - 1))
groupNames.Append("|")
End If

Next propertyCounter

Catch ex As Exception
Throw New Exception("Error obtaining group names. " +
ex.Message)
Finally
entry.Dispose()
entry = Nothing
search = Nothing
End Try

Return groupNames.ToString()

End Function
End Class

Good luck
Jon
 
J

Joe Kaplan \(MVP - ADSI\)

Actually, the code in that article is pretty bad and contains a number of
flaws that in my opinion make it not worthy of emulation.

Problems in the IsAuthenticated method include:
- They don't use AuthenticationTypes.Secure, so password is sent in clear
text over the network (!)
- They don't properly call Dispose on the IDisposable objects, so memory
leaks will occur, especially given the bugs in the Finalize method for
DirectoryEntry in .NET 1.0 and 1.1
- They make a bad assumption about the source the failure by catching the
general Exception class instead of looking for the "bad credentials" HRESULT
on the COMException that should be thrown
- They make some bad assumptions that won't necessarily work in a
multi-domain forest about looking up the user's user name

Problems in the GetGroups methods include:
- MemberOf attribute includes non-security groups, does not included nested
group membership and does not include the primary group, so essentially that
isn't the correct list
- Use the CN attribute for making a security decision instead of
sAMAccountName even though CN may not be unique in the directory (only the
current container)
- Parse the DN with a naive check ",", even though DN's can contain ","
escaped with \
- Fail to clean up and catch proper exception types as above

As you can see, I'm pretty grumpy about that sample code. Perhaps someday
I'll post something better or it can be fixed. In the mean time, please
don't use it.

Joe K.


I hope you didn't spend too long converting this.. :)
http://support.microsoft.com/default.aspx?scid=kb;EN-US;326340
 
J

Jon Delano

Well slap someone for just trying to help out some.

I made no comments that it was the best option ... only that it worked for
me in what I was doing (I did use AuthenticationTypes.Secure in my code by
the way and I have added the dispose and set the objects to nothing.)

At the very least it might give someone a starting point if they are lost.
 
J

Joe Kaplan \(MVP - ADSI\)

I wasn't criticizing your code Jon, I was criticizing the code in the
article that Raterus pointed to when he suggested that you should have just
used it as an example instead. That is a KBase article and needs to be held
to a higher standard. It is a big pet peeve of mine.

Your code is basically fine by me! Sorry for the confusion :)

Joe K.
 
R

Raterus

This isn't the first time Joe has mentioned the faults in this code either, when I was trying to do what you are doing, I kept finding posts by him suggesting better ways, so I listened. Here is how I've been getting my groups after looking through all of his suggestions. It basically revolves around the use of tokenGroups.

I modified this too, for my purposes I needed a delimited string of groupnames. You also have to create the DirectoryEntry based on the user you are interested in, in the class I created I had already done that, so that is why you don't see "dn" declared, just used.

Private Function GetGroups() As String
Dim octetSid As String
Dim binarySid() As Byte
Dim binarySids As PropertyValueCollection
Dim iterator As Integer
Dim groups As StringBuilder = New StringBuilder

Dim gEntry As DirectoryEntry = New DirectoryEntry("LDAP://" & dn)
gEntry.RefreshCache(New String() {"tokenGroups"})

binarySids = gEntry.Properties("tokenGroups")
For iterator = 0 To binarySids.Count - 1
binarySid = CType(binarySids(iterator), Byte())
octetSid = ConvertToOctetString(binarySid)

Dim groupPath As String = String.Format("<SID={0}>", octetSid)
Dim groupEntry As New DirectoryEntry("LDAP://" & groupPath)

If iterator > 0 Then
groups.Append("|")
End If
groups.Append(groupEntry.Properties("sAMAccountName").Value.ToString())

Next

Return groups.ToString

End Function

Private Function ConvertToOctetString(ByVal value As Byte()) As String
Dim iterator As Integer
Dim builder As System.Text.StringBuilder

builder = New System.Text.StringBuilder((value.GetUpperBound(0) + 1) * 2)
For iterator = 0 To value.GetUpperBound(0)
builder.Append(value(iterator).ToString("x2"))
Next

Return builder.ToString()
End Function

--Michael
 
J

Joe Kaplan \(MVP - ADSI\)

Sorry for going off on your post too. Every time I see that article
mentioned, it makes me cringe though, so I tend to over react.

The code below is a much more solid approach. I'd recommend calling Dispose
on your DirectoryEntry objects in a finally block to ensure that you don't
leak memory, but this technique works.

I have a newer technique that uses the DirectorySearcher to do a search for
all of the SIDs at once which is a fair amount faster than binding to each
individual group, but that is just an optimization. It probably only
matters if the user is in many groups.

The downside of all of these approaches is that you should really use the
fully qualified group name (domain\name), but it isn't easy to figure out
the NETBIOS name of the domain given the SID (possible, just not easy). I'm
thinking of trying to use the DsCrackNames API via p/invoke to accomplish
this in my next attempt.

Joe K.

This isn't the first time Joe has mentioned the faults in this code either,
when I was trying to do what you are doing, I kept finding posts by him
suggesting better ways, so I listened. Here is how I've been getting my
groups after looking through all of his suggestions. It basically revolves
around the use of tokenGroups.

I modified this too, for my purposes I needed a delimited string of
groupnames. You also have to create the DirectoryEntry based on the user
you are interested in, in the class I created I had already done that, so
that is why you don't see "dn" declared, just used.

Private Function GetGroups() As String
Dim octetSid As String
Dim binarySid() As Byte
Dim binarySids As PropertyValueCollection
Dim iterator As Integer
Dim groups As StringBuilder = New StringBuilder

Dim gEntry As DirectoryEntry = New DirectoryEntry("LDAP://" & dn)
gEntry.RefreshCache(New String() {"tokenGroups"})

binarySids = gEntry.Properties("tokenGroups")
For iterator = 0 To binarySids.Count - 1
binarySid = CType(binarySids(iterator), Byte())
octetSid = ConvertToOctetString(binarySid)

Dim groupPath As String = String.Format("<SID={0}>", octetSid)
Dim groupEntry As New DirectoryEntry("LDAP://" & groupPath)

If iterator > 0 Then
groups.Append("|")
End If

groups.Append(groupEntry.Properties("sAMAccountName").Value.ToString())

Next

Return groups.ToString

End Function

Private Function ConvertToOctetString(ByVal value As Byte()) As String
Dim iterator As Integer
Dim builder As System.Text.StringBuilder

builder = New System.Text.StringBuilder((value.GetUpperBound(0) + 1)
* 2)
For iterator = 0 To value.GetUpperBound(0)
builder.Append(value(iterator).ToString("x2"))
Next

Return builder.ToString()
End Function

--Michael
 
J

Jon Delano

No problem ...I just noticed that we have emailed before (I had used ADO to
first query Active Directory) ... and I sent you what I came up with. (Its
the IsAuthenticated part of the class I created later) ... after you pointed
me in the direction of the DirectoryEntry stuff.

LOL .. too funny.

Anyway, thanks and I'm sorry I took it the wrong way ... long weeks and days
....

Take care
Jon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,983
Messages
2,570,187
Members
46,747
Latest member
jojoBizaroo

Latest Threads

Top