Home > Blockchain >  C# How to get the AD user cannot change the password property from LDAP attribute userAccountControl
C# How to get the AD user cannot change the password property from LDAP attribute userAccountControl

Time:12-24

I am trying to get the user account control properties using library Novell.Directory.Ldap in ASP .NET Core 5. When I search the users attributes I found the attribute name userAccountControl which is set to some number. After searching solution I am able to find:

bool isUserActive = false;
bool userMustChangePassword = false;
bool passwordNeverExpires = false;
bool passwordCannotBeChanged = false;

var flags = Convert.ToInt32(attributeSet.GetAttribute("userAccountControl").StringValue);
isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled
if ((flags == 66048)) //65536 512
{
  passwordNeverExpires = true; //2. Password never expires property
}
long value = Convert.ToInt64(attributeSet.GetAttribute("pwdLastSet").StringValue);
if (value == 0)
{
    userMustChangePassword = true; //3. User must change password at next login
}

But I am not able to figure out how to get the User cannot change password and if the account is locked properties? Or how can I compare the binary value like 0x0040? Please help

Edit:

I tried the steps given by @Gabriel Luci in enter image description here

I am not getting where to look user cannot change the password settings?

Edit 2: According to @Gabriel Luci updated answer, it worked for me like this:

var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true, new byte[] { 48, 3, 2, 1, 7 }));
var getNtSecurityByteValue=attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, getNtSecurityByteValue, 0);
var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");
foreach (var ace in security.DiscretionaryAcl)
{
   if(ace.GetType().Name == "ObjectAce")
   {
      ObjectAce objAce = (ObjectAce)ace;
      if (objAce.AceType == AceType.AccessDeniedObject && objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword)
      {
          cannotChangePassword = true;
          break;
      }
   }
}

CodePudding user response:

The userAccountControl value is a bit flag, meaning that every bit in the binary representation of the number is an "on" or "off" depending on if it's a 1 or 0. So the decimal value is meaningless.

You are already checking the value properly when you're checking if it's enabled:

isUserActive = !Convert.ToBoolean(flags & 0x0002); //1. checks if user is enabled

Likewise, you should do the same when checking any of the other flags. The value of each is listed in the documentation.

When you're checking if the password is set to never expire, you're comparing the decimal value, which won't always give you a correct answer. Instead, check the bit value:

passwordNeverExpires = Convert.ToBoolean(flags & 0x10000);

Similar for account is locked:

var accountLocked = Convert.ToBoolean(flags & 0x0010);

For the user cannot change password setting, unfortunately that's more difficult and requires reading the permissions on the user account, which I have never done using the Novell.Directory.Ldap library. But I can try to point you in the right direction.

The account permissions are in the nTSecurityDescriptor attribute. Read this issue about how to get the byte array from that attribute: How to read/set NT-Security-Descriptor attributes?

I wrote an article about how to get the byte array into a usable format: Active Directory: Handling NT Security Descriptor attributes.

Then you'll be looking for two permissions that get added when the 'User cannot change password' checkbox is checked:

  1. Deny Change Password to 'Everyone'
  2. Deny Change Password to 'SELF'

You can probably get away with only looking for #2.

Update: I finally tried this out for myself. I had never used the Novell.Directory.Ldap library before, so this was new to me.

With the help of this answer, I figured out that you need to set an LDAP control for it to return the nTSecurityDescriptor attribute at all:

var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl("1.2.840.113556.1.4.801", true
                                           , new byte[] {48, 3, 2, 1, 7}));

Once you retrieve the object, you can check the permissions like this:

var byteValue = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
var security = new CommonSecurityDescriptor(true, true, byteValue, 0);

var self = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
var userChangePassword = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");

var cannotChangePassword = false;

foreach (ObjectAce ace in (security.DiscretionaryAcl)) {
    if (ace.AceType == AceType.AccessDeniedObject && ace.SecurityIdentifier == self
            && ace.ObjectAceType == userChangePassword) {
        cannotChangePassword = true;
        break;
    }
}

The GUID of the User-Change-Password permission is taken from the Control Access Rights documentation.

Notice that you don't need to use IADsSecurityDescriptor, and thus you don't need a reference to Interop.ActiveDs. This is because we're given the value as a byte array already. The reference to Interop.ActiveDs would have locked you into running your app on Windows only, which maybe you don't want.

  • Related