Reputation: 4325
I'm writing some code to query Active Directory using an LDAP connection. I'm only interested in users and I'm testing against a dummy instance of AD.
It's working well - I'm specifying specific properties to return and getting back results with those properties.
Last challenge is to filter out disabled users. Around the web I've discovered that this requires the following clause in the DirectorySearcher
's Filter
property:
(!(userAccountControl:1.2.840.113556.1.4.803:=2))
However, this wasn't working. Certain disabled users were always being returned. To investigate I wrote a little console app to reveal all the user properties:
Notice that 2 users are returned, but only one has the 'Account control' property? (Note that the label 'Account control' is reporting the userAccountControl
property.) The second user, no matter if enabled or disabled, never returns a userAccountControl
property so I cannot filter it based on this.
Can anyone explain please?
* UPDATE *
Adding some of my code which performs the query:
using (DirectoryEntry de = new DirectoryEntry(ConnectionString))
{
//de.Path = Path;
de.Username = Username;
de.Password = Password;
DirectorySearcher directorySearcher = new DirectorySearcher(de);
directorySearcher.PageSize = 1001;// To Pull up more than 100 records.
// Note that the userAccountControl clause excludes disabled users
directorySearcher.Filter = string.Format("(&(objectClass=user){0}{1})", DisabledUserFilter(), query);
Console.WriteLine("------------");
Console.WriteLine(directorySearcher.Filter);
Attributes.ForEach(a => directorySearcher.PropertiesToLoad.Add(a.Key));
directorySearcher.SearchScope = SearchScope.Subtree;
try
{
var result = directorySearcher.FindAll();
...
Upvotes: 5
Views: 10391
Reputation: 2187
Sounds like you find other object types that do not have the "userAccountControl"-attribute (e.g. Contacts). Try this saerch filter:
(&(userAccountControl=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
This one tests if the attribute is set at all and also that the disabled bit is not set.
Hint: You should also included a filter for "objectCategory=user" when searching for user accounts, because computer accounts also have the object class "user" and a "userAccountControl"-attribute.
Upvotes: 4
Reputation: 300
What classes do you use to get details of AD entry?
Here's some code that we use to detect AD entry status (sorry for VB.NET):
Private Sub verifyUser(ByVal username As String, ByVal password As String)
'1. Check if username exists in AD
Dim userPath As String = String.Empty
If Not findUser(username, userPath) Then
'not found
Exit Sub
End If
'2. Get AD Entry
Using adsUser As New DirectoryEntry(userPath, ADsUserName, ADsPassword)
'3. Is account Locked
If isLockedHack(adsUser) Then
'account is locked
Exit Sub
End If
'4. Is account Disabled
If isDisabled(adsUser) Then
'account is disabled
Exit Sub
End If
'5. Has account expired
If hasAccountExpired(adsUser) Then
'account expired
Exit Sub
End If
'6. Check whether user has to change password at next login
If hasToChangePassAtNextLoginHack(adsUser) Then
'change pass next login
Exit Sub
End If
'7. Has password expired
If hasPasswordExpired(adsUser) Then
'password expired
Exit Sub
End If
'8. Check whether password entered is okay
If isPassCorrect(userPath, username, password) Then
'account is fine
Else
'account is fine but password is incorrect
End If
End Using
End Sub
Private strMaxPwdAge As String = "maxPwdAge"
Private strPwdLastSet As String = "pwdLastSet"
Private strLockoutTime As String = "lockoutTime"
Private strAccountDisabled As String = "AccountDisabled"
Private Function hasToChangePassAtNextLoginHack(ByVal adsUser As DirectoryEntry) As Boolean
If adsUser.Properties.Contains(strPwdLastSet) Then
Dim objPwdLastSet As Object = adsUser.Properties(strPwdLastSet)(0)
If objPwdLastSet.LowPart = 0 Then
Return True
End If
End If
Return False
End Function
Private Function hasPasswordExpired(ByVal adsUser As DirectoryEntry) As Boolean
Dim pwdLastSet As Object
pwdLastSet = adsUser.Properties(strPwdLastSet).Value
Dim pwdDate As DateTime = GetDateFromLargeInteger(pwdLastSet)
Dim today As DateTime = Date.Now
If pwdDate.AddDays(CInt(getMaxPwdAge())).CompareTo(today) < 0 Then
Return True
Else
Return False
End If
Return False
End Function
Private Function isLockedHack(ByVal adsUser As DirectoryEntry) As Boolean
Dim pcoll As PropertyCollection = adsUser.Properties
If adsUser.Properties.Contains(strLockoutTime) Then
Dim oli2 As Object = adsUser.Properties(strLockoutTime)(0)
Dim timeVal As Long = (oli2.HighPart * &H100000000) + oli2.LowPart
If timeVal > 0 Then
Return True
End If
End If
Return False
End Function
Private Function isPassCorrect(ByVal userPath As String, ByVal user As String, _
ByVal pass As String) As Boolean
Try
Using adsUser As New DirectoryEntry(userPath, user, pass)
Dim natObj As Object = adsUser.NativeObject
adsUser.Dispose()
End Using
Catch ex As Exception
Return False
End Try
Return True
End Function
Private Function isLocked(ByVal adsUser As DirectoryEntry) As Boolean
Dim binIL As Boolean
binIL = adsUser.InvokeGet("IsAccountLocked")
If binIL Then
Return True
End If
Return False
End Function
Private Function isDisabled(ByVal adsUser As DirectoryEntry) As Boolean
If adsUser.InvokeGet(strAccountDisabled) Then
Return True
End If
Return False
End Function
Private Function hasAccountExpired(ByVal adsUser As DirectoryEntry) As Boolean
Dim myDate As DateTime = adsUser.InvokeGet("AccountExpirationDate")
Dim defExpiredDate As New DateTime(1601, 1, 1)
Dim passNeverExpiresDate As New DateTime(1970, 1, 1)
Dim today As DateTime = Date.Now
If (myDate.CompareTo(defExpiredDate) <> 0) And (myDate.CompareTo(passNeverExpiresDate) <> 0) Then
If myDate.CompareTo(today) < 0 Then
Return True
Else
Return False
End If
End If
Return False
End Function
Private Function findUser(ByVal username As String, ByRef path As String) As Boolean
path = ContainerFound(ContainerType.user, username)
If path = "" Then
Return False
End If
Return True
End Function
Private Function getMaxPwdAge() As String
Using domainRoot As New DirectoryEntry(strLDAP & strADsPath, ADsUserName, ADsPassword)
Dim maxPwdAge As LargeInteger
Dim ticksDividerValue = -864000000000
Dim numDays As Long
If domainRoot.Properties.Contains(strMaxPwdAge) Then
maxPwdAge = domainRoot.Properties(strMaxPwdAge)(0)
numDays = ((maxPwdAge.HighPart * 2 ^ 32) + maxPwdAge.LowPart) / ticksDividerValue
Else
numDays = 0
End If
Return numDays.ToString()
End Using
End Function
Private Function ContainerFound(ByVal Type As Byte, ByVal Item As String) As String
'Finds the path of an object within the active directory
Using objADObject As New DirectoryEntry
objADObject.Path = strLDAP & strADsPath
objADObject.Username = ADsUserName
objADObject.Password = ADsPassword
objADObject.AuthenticationType = AuthenticationTypes.Secure
Using objADSearcher As New DirectorySearcher(objADObject)
Dim Results As SearchResult
Select Case Type
Case ContainerType.organisationalUnit
objADSearcher.Filter = ("ou=" & Item)
Case ContainerType.group, ContainerType.user
objADSearcher.Filter = ("cn=" & Item)
End Select
Try
Results = objADSearcher.FindOne
Catch ex As Exception
'CannotConnectDomain
Return ""
End Try
If IsNothing(Results) = True Then
Return ""
Else
: Return Results.Path
End If
End Using
End Using
End Function
Public Function GetDateFromLargeInteger(ByVal largeInteger As Object) As Date
Try
Return Date.FromFileTime(GetInt64FromLargeInteger(largeInteger))
Catch e As ArgumentOutOfRangeException
Throw New ArgumentException(strNotValidVal, e)
End Try
End Function
Public Function GetInt64FromLargeInteger(ByVal largeInteger As Object) As Long
Const highShift As Long = &H100000000
Dim lowPart As Integer
Dim highPart As Integer
Dim longVal As Long
Dim largeIntType As Type
largeIntType = largeInteger.GetType()
Try
highPart = CType(largeIntType.InvokeMember("HighPart", BindingFlags.GetProperty Or BindingFlags.Public, Nothing, largeInteger, Nothing), Integer)
lowPart = CType(largeIntType.InvokeMember("LowPart", BindingFlags.GetProperty Or BindingFlags.Public, Nothing, largeInteger, Nothing), Integer)
longVal = (highPart * highShift) + lowPart
Return longVal
Catch e As MissingMethodException
Throw New ArgumentException(strInvalidCom, e)
End Try
End Function
Hopefully it will give you a brief idea how this is done for our AD check. Let me know if that is helpful.
Upvotes: 0