Determining Active Directory Account Status with Ruby
In Active Directory, the status on accounts is controlled by the userAccountControl
attribute. This attribute has hexadecimal values, allowing it to be in many states and offer many different control options with very little storage impact.
Looking at this Microsoft doc on User-Account-Control attribute, we should be generally concerned with the last (right-most) digit. When it is 2
, the account is disabled.
Hexadecimal value | Identifier (defined in iads.h) | Description |
---|---|---|
0x00000002 | ADS_UF_ACCOUNTDISABLE | The user account is disabled. |
0x00000200 | ADS_UF_NORMAL_ACCOUNT | This is a default account type that represents a typical user. |
Accounts in a “normal” state have a value of 0x00000200
Normal accounts typically have 0x00000200
while disabled accounts have 0x00000202
Let’s convert these to integers using Ruby:
'0x00000200'.to_i(16)
# => 512
> '0x00000202'.to_i(16)
514
Most of the time we see either 512 or 514 as the integer value for this attribute. We can conver the other way as well from integer (base 10) back to hexadecimal (base 16).
512.to_s(16)
# => "200"
514.to_s(16)
# => "202"
As you can see, the last digit is 2
when userAccountControl: 514
indicating this account is disabled.
So, how can we build a method to abstract this out for us and return true for active accounts and false inactive accounts?
We could compare strings.
def enabled?(user)
user['userAccountControl'].to_s(16)[-1] != '2'
end
active_user = { 'userAccountControl' => 512 }
inactive_user = { 'userAccountControl' => 514 }
enabled? active_user
# => true
enabled? inactive_user
# => false
Or we could make use of a bitwise operation. These typically much faster than comparing strings and will use less memory. Ruby provides a nice (bitwise AND)[https://ruby-doc.org/core-2.6.4/Integer.html#method-i-26] for integers.
512 & 0x002
# => 0
514 & 0x00000002
# => 2
# Active, but locked out account
528.to_i & 0x002
# => 0
Now, we can just check that the return value of this operation is zero.
def enabled?(user)
(user['userAccountControl'] & 0x002).zero?
end
active_user = { 'userAccountControl' => 512 }
inactive_user = { 'userAccountControl' => 514 }
enabled? active_user
# => true
enabled? inactive_user
# => false
This also works with other combinations such as when no password is required.
Hexadecimal value | Identifier (defined in iads.h) | Description |
---|---|---|
0x00000020 | ADS_UF_PASSWD_NOTREQD | No password is required. |
active_nopwd_user = { 'userAccountControl' => 544 }
inactive_nopwd_user = { 'userAccountControl' => 546 }
enabled? active_nopwd_user
# => true
enabled? inactive_nopwd_user
# => false