C# 如何获取 AD 用户无法从 LDAP 属性 userAccountControl 更改密码 属性?

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

我正在尝试使用 ASP .NET Core 5 中的库 Novell.Directory.Ldap 获取用户帐户控制属性。当我搜索用户属性时,我发现属性名称 userAccountControl 设置为某个数字。搜索解决方案后,我能够找到:

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
}

但我无法弄清楚如何获得 User cannot change password 以及 account is locked 属性?或者如何比较 0x0040 之类的二进制值?请帮忙

编辑:

我尝试了@Gabriel Luci 在 https://www.gabescode.com/active-directory/2019/07/25/nt-security-descriptors.html 中给出的步骤并尝试了以下代码:

var act = attributeSet.GetAttribute("nTSecurityDescriptor").ByteValue;
ADsSecurityUtility secUtility = new ADsSecurityUtility();
IADsSecurityDescriptor convertAttrToSD = (IADsSecurityDescriptor)secUtility.ConvertSecurityDescriptor(act, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID);
var byteArray = (byte[])secUtility.ConvertSecurityDescriptor(
                            convertAttrToSD,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_IID,
                            (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW
                         );
var security = new CommonSecurityDescriptor(true, true, byteArray, 0);

如果我检查 security 它显示

我不知道去哪里查看用户无法更改密码设置?

编辑 2: 根据@Gabriel Luci 更新的答案,它对我来说是这样的:

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;
      }
   }
}

userAccountControl 值是一个位标志,这意味着数字的二进制表示中的每一位都是“开”或“关”,具体取决于它是 1 还是 0。所以十进制值没有意义。

当您检查它是否启用时,您已经正确地检查了该值:

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

同样,您在检查任何其他标志时也应该这样做。每个的值都列在 in the documentation.

当您检查密码是否设置为永不过期时,您是在比较十进制值,这并不总能为您提供正确答案。相反,检查位值:

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

类似帐户被锁定:

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

因为用户无法更改密码设置,不幸的是,这更困难并且需要读取用户帐户的权限,我从未使用 Novell.Directory.Ldap 库完成过。但我可以尝试为您指明正确的方向。

帐户权限在 nTSecurityDescriptor 属性中。阅读有关如何从该属性获取字节数组的问题:How to read/set NT-Security-Descriptor attributes?

我写了一篇关于如何将字节数组转换成可用格式的文章:Active Directory: Handling NT Security Descriptor attributes

然后您将寻找选中 'User cannot change password' 复选框时添加的两个权限:

  1. 拒绝将密码更改为 'Everyone'
  2. 拒绝将密码更改为 'SELF'

你可能只需要寻找 #2 就可以逃脱。

更新: 我终于亲自尝试了这个。我以前从未使用过 Novell.Directory.Ldap 库,所以这对我来说是新的。

的帮助下,我发现您需要为它设置一个 LDAP 控件 return nTSecurityDescriptor 属性:

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

检索对象后,您可以像这样检查权限:

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 (var ace in (security.DiscretionaryAcl)) {
    if (ace is ObjectAce objAce && objAce.AceType == AceType.AccessDeniedObject
            && objAce.SecurityIdentifier == self && objAce.ObjectAceType == userChangePassword) {
        cannotChangePassword = true;
        break;
    }
}

用户更改密码权限的 GUID 取自 Control Access Rights documentation

请注意,您不需要使用 IADsSecurityDescriptor,因此您不需要引用 Interop.ActiveDs。这是因为我们已经将值作为字节数组给出了。