查找无法更改密码的用户

时间:2015-04-23 03:02:00

标签: c# active-directory

我正在尝试准备无法在AD中更改密码的用户的报告。 AD安装在Window Server 2012上。

以下是我认为有效但不起作用的方法 -

    /// <summary>
    /// Check whether password of user cannot be changed.
    /// </summary>
    /// <param name="user">The DirectoryEntry object of user.</param>
    /// <returns>Return true if password cannot be changed else false.</returns>
    public static bool IsPasswordCannotBeChanged(DirectoryEntry user)
    {
        if (user.Properties.Contains("userAccountControl") &&
            user.Properties["userAccountControl"].Value != null)
        {
            var userFlags = (UserFlags)user.Properties["userAccountControl"].Value;
            return userFlags.Contains(UserFlags.PasswordCannotChange);
        }
        return false;
    }

这是enum UserFlags -

[Flags]
public enum UserFlags
{
    // Reference - Chapter 10 (from The .NET Developer's Guide to Directory Services Programming)

    Script = 1,                                     // 0x1
    AccountDisabled = 2,                            // 0x2
    HomeDirectoryRequired = 8,                      // 0x8
    AccountLockedOut = 16,                          // 0x10
    PasswordNotRequired = 32,                       // 0x20
    PasswordCannotChange = 64,                      // 0x40
    EncryptedTextPasswordAllowed = 128,             // 0x80
    TempDuplicateAccount = 256,                     // 0x100
    NormalAccount = 512,                            // 0x200
    InterDomainTrustAccount = 2048,                 // 0x800
    WorkstationTrustAccount = 4096,                 // 0x1000
    ServerTrustAccount = 8192,                      // 0x2000
    PasswordDoesNotExpire = 65536,                  // 0x10000 (Also 66048 )
    MnsLogonAccount = 131072,                       // 0x20000
    SmartCardRequired = 262144,                     // 0x40000
    TrustedForDelegation = 524288,                  // 0x80000
    AccountNotDelegated = 1048576,                  // 0x100000
    UseDesKeyOnly = 2097152,                        // 0x200000
    DontRequirePreauth = 4194304,                   // 0x400000
    PasswordExpired = 8388608,                      // 0x800000 (Applicable only in Window 2000 and Window Server 2003)
    TrustedToAuthenticateForDelegation = 16777216,  // 0x1000000
    NoAuthDataRequired = 33554432                   // 0x2000000
}

您能否分享 64 (密码无法更改)的原因,不能为无法更改密码的用户退回?

或者你有更好的方法来解决这个问题?

编辑 -

UserFlagExtension 使事情变得更快的代码 -

public static class UserFlagExtensions
{
    /// <summary>
    /// Check if flags contains the specific user flag.
    /// </summary>
    /// <param name="haystack">The bunch of flags</param>
    /// <param name="needle">The flag to look for.</param>
    /// <returns>Return true if flag found in flags.</returns>
    public static bool Contains(this UserFlags haystack, UserFlags needle)
    {
        return (haystack & needle) == needle;
    }
}

3 个答案:

答案 0 :(得分:3)

经过几个小时的搜索和努力,我能够制定出有效的解决方案。

.Net 2.0方式

请继续关联AD .NET - User's Can't Change Password Attribute (Get/Set)

您需要添加对 ActiveDS 的引用才能使其正常工作。虽然我没有时间去测试它。但它应该有很多地方可以工作。所以......

上述文章中的代码段 - (如果文章被删除)

public bool GetCantChangePassword(string userid)
 {
        bool cantChange = false;
        try
        {
            DirectoryEntry entry = new DirectoryEntry(string.Format("LDAP://{0},{1}", "OU=Standard Users,OU=Domain", "DC=domain,DC=org"));
            entry.AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.ServerBind;
            DirectorySearcher search = new DirectorySearcher(entry);
            search.Filter = string.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))", userid);
            search.SearchScope = SearchScope.Subtree;
            SearchResult results = search.FindOne();
            if (results != null)
            {
                try
                {
                    DirectoryEntry user = results.GetDirectoryEntry();
                    ActiveDirectorySecurity userSecurity = user.ObjectSecurity;
                    SecurityDescriptor sd = (SecurityDescriptor)user.Properties["ntSecurityDescriptor"].Value;
                    AccessControlList oACL = (AccessControlList)sd.DiscretionaryAcl;

                    bool everyoneCantChange = false;
                    bool selfCantChange = false;

                    foreach (ActiveDs.AccessControlEntry ace in oACL)
                    {
                        try
                        {
                            if (ace.ObjectType.ToUpper().Equals("{AB721A53-1E2F-11D0-9819-00AA0040529B}".ToUpper()))
                            {
                                if (ace.Trustee.Equals("Everyone") && (ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT))
                                {
                                    everyoneCantChange = true;
                                }
                                if (ace.Trustee.Equals(@"NT AUTHORITY\SELF") && (ace.AceType == (int)ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT))
                                {
                                    selfCantChange = true;
                                }
                            }
                        }
                        catch (NullReferenceException ex)
                        {
                            //Logger.append(ex.Message);
                        }
                        catch (Exception ex)
                        {
                            Logger.append(ex);
                        }
                    }


                    if (everyoneCantChange || selfCantChange)
                    {
                        cantChange = true;
                    }
                    else
                    {
                        cantChange = false;
                    }

                    user.Close();
                }
                catch (Exception ex)
                {
                    // Log your errors!
                }
            }
            entry.Close();
        }
        catch (Exception ex)
        {
            // Log your errors!
        }
        return cantChange;
    }

.Net 4.0方式

这就是我能够把它钉死的方式。它很容易修复。但是,我需要使用AuthenticablePrincipal.UserCannotChangePassword Property

我使用的代码段 -

    /// <summary>
    /// Check whether password of user cannot be changed.
    /// </summary>
    /// <param name="user">The DirectoryEntry object of user.</param>
    /// <returns>Return true if password cannot be changed else false.</returns>
    public static bool IsPasswordCannotBeChanged(DirectoryEntry user)
    {
        var isUserCantChangePass = false;

        try
        {
            // 1. Get SamAccountName
            var samAccountName = Convert.ToString(user.Properties["sAMAccountName"].Value);
            if (!string.IsNullOrEmpty(samAccountName))
            {
                // 2. Prepare domain context
                using (var domainContext = new PrincipalContext(ContextType.Domain, _domain, _domainUser, _domainPass))
                {
                    // 3. Find user
                    var userPrincipal = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, samAccountName);

                    // 4. Check if user cannot change password
                    using (userPrincipal)
                        if (userPrincipal != null) isUserCantChangePass = userPrincipal.UserCannotChangePassword;
                }
            }

        }
        catch (Exception exc)
        {
            Logger.Write(exc);
        }

        return isUserCantChangePass;
    }

答案 1 :(得分:1)

活动目录不使用所有这些标志。具体来说,

  • AccountLockedOut
  • PasswordCannotChange
  • PasswordExpired
  

Active Directory实际上使用不同的机制来控制它们   帐户属性,所以不要尝试从中读取它们   userAccountControl的!我们讨论如何处理特殊情况   即将到来的部分。

- From The .NET Developer's Guide to Directory Services User Account Management by Ryan Dunn and Joe Kaplan

PasswordCannotChange背后的想法表明该帐户的密码不能由帐户本身更改,但要做到这一点,您实际上必须拒绝这一点(在帐户安全标签下)

尝试使用msDS-User-Account-Control-Computed属性检查ADS_UF_PASSWD_CANT_CHANGE flag。像这样:

DirectoryEntry user = ...

const string ATTRIBUTE_NAME= "msDS-User-Account-Control-Computed";
const ADS_UF_PASSWD_CANT_CHANGE = 64; // use enum for more robust code

using (user)
{
    user.RefreshCache(new string[]{ATTRIBUTE_NAME}); 

    int userFlags = (int)user.Properties[ATTRIBUTE_NAME].Value;

    bool userCantChangePassword = (userFlags & ADS_UF_PASSWD_CANT_CHANGE) == ADS_UF_PASSWD_CANT_CHANGE;

...
}

答案 2 :(得分:1)

如果有人像我一样来这里寻找如何使用.NET Core 3.1进行操作,这是我想出的解决方案,用于获取并设置PasswordCannotChange属性上的UserAccountControl位在广告中。

我使用System.DirectoryServices.Protocols库提供对LdapConnection类以及相关类和方法的访问。我还使用System.Security.AccessControl库与安全描述符一起工作。

假设您可以成功连接到AD服务器以创建LdapConnection类,其余的应该可以工作。

这是我对get的解决方案:

public bool GetUserCannotChangePassword(string userDistinguishedName){
    using (var ldapConnection = CreateLdapConnection()) //Assuming you've connected using Admin rights
    {
        bool cantChange = false;
        
        //Get RootDomainNamingContext as searchContainer
        var r1 = (SearchResponse)ldapConnection.SendRequest(new SearchRequest("", "(objectClass=*)", SearchScope.Base));
        var searchContainer = response.Entries[0].Attributes["rootdomainnamingcontext"].GetValues(typeof(string))[0]
                                .ToString();
        
        //Set Filter to get specified user
        var filter = $"(&(objectClass=user)(!(objectClass=computer))(distinguishedName={userDistinguishedName}))";
        
        //Get the ntSecurityDescriptor attribute of the user
        var searchRequest = new SearchRequest(searchContainer, filter, SearchScope.Subtree, new[] { "ntSecurityDescriptor" });
        var searchOptions = new SearchOptionsControl(SearchOption.DomainScope);
        searchRequest.Controls.Add(searchOptions);
        var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest);
        var result = searchResponse.Entries.OfType<SearchResultEntry>()
            .SingleOrDefault();
            
        if (result != null)
        {
            //Parse as RawSecurityDescriptor
            RawSecurityDescriptor sd =
                new RawSecurityDescriptor((byte[]) result.Attributes["ntSecurityDescriptor"][0], 0);
            var oACL = sd.DiscretionaryAcl;

            bool everyoneCantChange = false;
            bool selfCantChange = false;

            //Loop through the Access Control Entries that are of ObjectAce type
            foreach (var ace in oACL.OfType<ObjectAce>())
            {
                if (ace?.ObjectAceType.ToString().Equals("AB721A53-1E2F-11D0-9819-00AA0040529B",
                    StringComparison.OrdinalIgnoreCase) == true) //Match on change password ACE (https://docs.microsoft.com/en-us/windows/win32/adsi/modifying-user-cannot-change-password-ldap-provider)
                {
                    if (ace.SecurityIdentifier.Value.Equals("S-1-1-0", StringComparison.OrdinalIgnoreCase) &&
                        ace.AceType == AceType.AccessDeniedObject) //Match on Everyone SecurityIdentifier
                    {
                        everyoneCantChange = true;
                    }

                    if (ace.SecurityIdentifier.Value.Equals("S-1-5-10", StringComparison.OrdinalIgnoreCase) &&
                        ace.AceType == AceType.AccessDeniedObject) //Match on Self SecurityIdentifier
                    {
                        selfCantChange = true;
                    }
                }
            }


            if (everyoneCantChange || selfCantChange)
            {
                cantChange = true;
            }
        }

        return cantChange;
    }
}

这是我对set的解决方案:

public bool SetUserCannotChangePassword(string userDistinguishedName, bool userCannotChangePassword)
{
    using (var ldapConnection = CreateLdapConnection()) //Assuming you've connected using Admin rights
    {
        bool success = true;
        try
        {
            //Get RootDomainNamingContext as searchContainer
            var r1 = (SearchResponse)ldapConnection.SendRequest(new SearchRequest("", "(objectClass=*)", SearchScope.Base));
            var searchContainer = response.Entries[0].Attributes["rootdomainnamingcontext"].GetValues(typeof(string))[0]
                                    .ToString();
            
            //Set Filter to get specified user
            var filter = $"(&(objectClass=user)(!(objectClass=computer))(distinguishedName={userDistinguishedName}))";
            
            //Get the ntSecurityDescriptor attribute of the user
            var searchRequest = new SearchRequest(searchContainer, filter, SearchScope.Subtree, new[] { "ntSecurityDescriptor", "distinguishedName" });
            var searchOptions = new SearchOptionsControl(SearchOption.DomainScope);
            searchRequest.Controls.Add(searchOptions);
            var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest);
            var result = searchResponse.Entries.OfType<SearchResultEntry>()
                .SingleOrDefault();

            if (result != null)
            {
                try
                {
                    RawSecurityDescriptor sd =
                        new RawSecurityDescriptor((byte[]) result.Attributes["ntSecurityDescriptor"][0], 0);
                    var dn = result.Attributes["distinguishedName"][0];
                    var oACL = sd.DiscretionaryAcl;

                    int? everyoneCantChangeIndex = null;
                    ObjectAce everyoneAce = null;
                    int? selfCantChangeIndex = null;
                    ObjectAce selfAce = null;

                    for (var i = 0; i < oACL.Count; i++)
                    {
                        var oAce = oACL[i] as ObjectAce;
                        if (oAce?.ObjectAceType.ToString().Equals("AB721A53-1E2F-11D0-9819-00AA0040529B",
                            StringComparison.OrdinalIgnoreCase) == true)
                        {
                            if (oAce.SecurityIdentifier.Value.Equals("S-1-1-0",
                                StringComparison.OrdinalIgnoreCase))
                            {
                                everyoneCantChangeIndex = i;
                                everyoneAce = oAce;
                            }

                            if (oAce.SecurityIdentifier.Value.Equals("S-1-5-10",
                                    StringComparison.OrdinalIgnoreCase) &&
                                oAce.AceType == AceType.AccessDeniedObject)
                            {
                                selfCantChangeIndex = i;
                                selfAce = oAce;
                            }
                        }
                    }

                    if (everyoneCantChangeIndex.HasValue)
                    {
                        oACL.RemoveAce(everyoneCantChangeIndex.Value);
                    }

                    if (selfCantChangeIndex.HasValue)
                    {
                        if (everyoneCantChangeIndex.HasValue &&
                            everyoneCantChangeIndex.Value < selfCantChangeIndex.Value)
                        {
                            selfCantChangeIndex--; //Adjust index to ensure removing correct ACE
                        }

                        oACL.RemoveAce(selfCantChangeIndex.Value);
                    }

                    if (userCannotChangePassword)
                    {
                        oACL.InsertAce(everyoneCantChangeIndex ?? oACL.Count,
                            new ObjectAce(AceFlags.None, AceQualifier.AccessDenied,
                                everyoneAce?.AccessMask ?? 256,
                                everyoneAce?.SecurityIdentifier ??
                                new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                                ObjectAceFlags.ObjectAceTypePresent,
                                everyoneAce?.ObjectAceType ??
                                new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
                                everyoneAce?.InheritedObjectAceType ?? Guid.Empty,
                                everyoneAce?.IsCallback ?? false, everyoneAce?.GetOpaque()));
                                
                        oACL.InsertAce(selfCantChangeIndex ?? oACL.Count,
                            new ObjectAce(AceFlags.None, AceQualifier.AccessDenied, selfAce?.AccessMask ?? 256,
                                selfAce?.SecurityIdentifier ??
                                new SecurityIdentifier(WellKnownSidType.SelfSid, null),
                                ObjectAceFlags.ObjectAceTypePresent,
                                selfAce?.ObjectAceType ?? new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
                                selfAce?.InheritedObjectAceType ?? Guid.Empty, selfAce?.IsCallback ?? false,
                                selfAce?.GetOpaque()));
                    }
                    else
                    {
                        oACL.InsertAce(everyoneCantChangeIndex ?? oACL.Count,
                            new ObjectAce(AceFlags.None, AceQualifier.AccessAllowed,
                                everyoneAce?.AccessMask ?? 256,
                                everyoneAce?.SecurityIdentifier ??
                                new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                                ObjectAceFlags.ObjectAceTypePresent,
                                everyoneAce?.ObjectAceType ??
                                new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}"),
                                everyoneAce?.InheritedObjectAceType ?? Guid.Empty,
                                everyoneAce?.IsCallback ?? false, everyoneAce?.GetOpaque()));
                    }

                    var modification = new DirectoryAttributeModification
                    {
                        Operation = DirectoryAttributeOperation.Replace,
                        Name = "ntSecurityDescriptor"
                    };

                    sd.DiscretionaryAcl = OrderRawAcl(oACL);

                    var ba = new byte[sd.BinaryLength];
                    sd.GetBinaryForm(ba, 0);
                    modification.Add(ba);

                    var modifyRequest = new ModifyRequest(dn.ToString(), modification);

                    var modifyResponse = ldapConnection.SendRequest(modifyRequest);
                    if (modifyResponse.ResultCode != ResultCode.Success)
                    {
                        success = false;
                    }
                }
                catch (Exception ex)
                {
                    success = false;
                }
            }
        }
        catch (Exception ex)
        {
            success = false;
        }

        return success;
    }
}

private RawAcl OrderRawAcl(RawAcl oAcl)
{
    // Thanks to this post for this awesome method (https://stackoverflow.com/questions/8126827/how-do-you-programmatically-fix-a-non-canonical-acl)
    
    // A canonical ACL must have ACES sorted according to the following order:
    //   1. Access-denied on the object
    //   2. Access-denied on a child or property
    //   3. Access-allowed on the object
    //   4. Access-allowed on a child or property
    //   5. All inherited ACEs 
    List<GenericAce> implicitDenyDacl = new List<GenericAce>();
    List<GenericAce> implicitDenyObjectDacl = new List<GenericAce>();
    List<GenericAce> inheritedDacl = new List<GenericAce>();
    List<GenericAce> implicitAllowDacl = new List<GenericAce>();
    List<GenericAce> implicitAllowObjectDacl = new List<GenericAce>();
    foreach (var ace in oAcl)
    {
        if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited)
        {
            inheritedDacl.Add(ace);
        }
        else
        {
            switch (ace.AceType)
            {
                case AceType.AccessAllowed:
                    implicitAllowDacl.Add(ace);
                    break;

                case AceType.AccessDenied:
                    implicitDenyDacl.Add(ace);
                    break;

                case AceType.AccessAllowedObject:
                    implicitAllowObjectDacl.Add(ace);
                    break;

                case AceType.AccessDeniedObject:
                    implicitDenyObjectDacl.Add(ace);
                    break;
            }
        }
    }

    Int32 aceIndex = 0;
    RawAcl newDacl = new RawAcl(oAcl.Revision, oAcl.Count);
    implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
    implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
    implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
    implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
    inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));

    if (aceIndex != oAcl.Count)
    {
        throw new Exception("Reordering Access Control List unsuccessful. The number of items in the reordered list does not match the number of items submitted list.");
    }
    return newDacl;
}

这基本上遵循本文档中详细介绍的步骤:https://docs.microsoft.com/en-us/windows/win32/adsi/modifying-user-cannot-change-password-ldap-provider

希望有人发现这有帮助。