简体   繁体   中英

Detect if an Active Directory user account is locked using LDAP in Python

I'm validating user logins using python's ldap module. When the login fails, I get a ldap.INVALID_CREDENTIALS login, but this can be either because of a wrong password or because the account is locked. The account get's locked after the 3rd try.

I would like to detect that the account is locked and report that to the frustrated user, instead of the same "invalid login" message.

Searching for a solution I found:

  • The userAccountControl LOCKED flag is not used by AD;
  • The lockoutTime attribute should be used instead

The LDAP query I should be using to find locked users is:

(&(objectClass=user)(lockoutTime>=1))

Or for a specific user:

(&(objectClass=user)(sAMAccountName=jabberwocky)(lockoutTime>=1))

But this is not working, the query returns no results every time.

A value of zero in lockoutTime means it's not locked out. So, you should try this.

(&(objectClass=user)(!lockoutTime=0)) 

Actually, the above query is still not 100% correct. If you read the fine print from MSDN, Microsoft is suggesting you to add the Lockout-Time attribute to the Lockout-Duration attribute and then compare it with the current time. That's because there is such a thing called lockout duration. Once the lockout duration passes, the user is unlocked automatically. Zero in Lockout-Duration means the account is locked forever until the administrator unlock it.

See this MSDN article

This attribute value is only reset when the account is logged onto successfully. This means that this value may be non zero, yet the account is not locked out. To accurately determine if the account is locked out, you must add the Lockout-Duration to this time and compare the result to the current time, accounting for local time zones and daylight savings time.

lockoutTime is a <not set> attribute so the easiest way is to use:

(&(objectClass=user)(lockoutDuration=*))) 

for the non-empty entries.

Update:

However, this value is also set when the password expires, password needs to change etc.

So it needs to be filtered by:

UserPrincipal userPrincipal = new UserPrincipal(context);
bool isLocked = userPrincipal.IsAccountLockedOut();

to get the cases where the user is locked out because they violated the password policy eg incorrectly entered the password 5 times.

I found also this list of property flags: How to use the UserAccountControl flags

SCRIPT  0x0001  1
ACCOUNTDISABLE  0x0002  2
HOMEDIR_REQUIRED    0x0008  8
LOCKOUT 0x0010  16
PASSWD_NOTREQD  0x0020  32
PASSWD_CANT_CHANGE 0x0040   64
ENCRYPTED_TEXT_PWD_ALLOWED  0x0080  128
TEMP_DUPLICATE_ACCOUNT  0x0100  256
NORMAL_ACCOUNT  0x0200  512
INTERDOMAIN_TRUST_ACCOUNT   0x0800  2048
WORKSTATION_TRUST_ACCOUNT   0x1000  4096
SERVER_TRUST_ACCOUNT    0x2000  8192
DONT_EXPIRE_PASSWORD    0x10000 65536
MNS_LOGON_ACCOUNT   0x20000 131072
SMARTCARD_REQUIRED  0x40000 262144
TRUSTED_FOR_DELEGATION  0x80000 524288
NOT_DELEGATED   0x100000    1048576
USE_DES_KEY_ONLY    0x200000    2097152
DONT_REQ_PREAUTH    0x400000    4194304
PASSWORD_EXPIRED    0x800000    8388608
TRUSTED_TO_AUTH_FOR_DELEGATION  0x1000000   16777216
PARTIAL_SECRETS_ACCOUNT 0x04000000      67108864

You must make a binary-AND of property userAccountControl with 0x002 . In order to get all locked (ie disabled) accounts you can use

(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

For operator 1.2.840.113556.1.4.803 see LDAP Matching Rules

In addition, I found that lockoutTime is not guaranteed for all users in AD (at least in our configuration), but will be created upon reaching the number of failed lockout attempts. So in checking for locked accounts, checking for None or an equivalent will be required as well.

use this query to get the best results,

Get-ADUser -LDAPFilter "(&(objectCategory=Person)(objectClass=User)(lockoutTime>=1))" -Properties LockedOut

(&(objectClass=user)(&(lockoutTime=*)(!(lockoutTime=0))))

Will return objects which are users and have a present attribute named lockoutTime which isn't equal to 0.

Do it like this:

def islocked(self, user, basedn, conn):

    search_filter = "(&(objectCategory=Person)(objectClass=User)(lockoutTime>=1))"
    search_attribute = ["sAMAccountName"]

    try:
        conn.search(basedn,
                        search_filter,
                        attributes=search_attribute)
        results = conn.entries
    except ldap3.core.exceptions.LDAPException as e:
        print(e)
    lockedaccounts = [x['sAMAccountName'] for x in results]
    lockedaccounts = [str(x) for x in lockedaccounts]
    lockedaccounts = [x for x in lockedaccounts]
    if user in lockedaccounts:
        return True
    else:
        return False

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM