3/1/15

Active Directory SetPassword or ChangePassword Possible Errors


When we try to update the user password on an Active Directory (AD or AD LDS) environment, we usually read on the documentation about the basic steps that are needed to set or change the password to a user. If we follow those basic steps, we would be very lucky of all goes well. There are often several other requirements that need to be met to be able to set the password. Some of these include:
  • The password is clear text
  • The password does not meet the minimum length requirement
  • The password does not meet the format like numeric or capitalization (password policy)
  • The Authentication type is not high enough


Any of these items can cause COM error similar to this:

“Exception has been thrown by the target of an invocation COMException (0x80005009)

To illustrate this, let’s looks at what the basic code to set a user’s password looks like:

//add these setting on the configuration file
        const string adConnection = "ldap://mydomain:389/CN=ORG,DC=OZKARY,DC=COM";
        const string adUsername = "someuser"//user with admin access to AD
        const string adPw = "*******";

        public static void SetPassword(string username, string password){

            using (var de = new DirectoryEntry(adConnection, adUsername, adPw))
            {
                //Win Server 2008 schema or less uses sAMAccount 
                //not userPrincipalName
         string query = String.Format("(&(objectCategory=person)(objectClass=user)(&(userPrincipalName={0}))", username);           

                using (var ds = new DirectorySearcher(de))
                {
                    ds.SearchScope = System.DirectoryServices.SearchScope.Subtree;
                    ds.Filter = query;
                    SearchResult res = ds.FindOne();

                    if (res != null)
                    {
                        using (var userEntry = res.GetDirectoryEntry())
                        {
                            userEntry.Invoke("SetPassword", new object[] 
                            { password });                           
                        }

                    }
                }
            }
        }

If we are lucky, this should be enough for the operation to be successful, but we are probably getting errors, and that is why we are writing this article.  The first thing to do is to figure out more about this error by extracting the COM exception error code and mapping them to a root cause. This is where this function may be helpful:

  private static void HandleComException(TargetInvocationException tie)
        {          
            if (tie != null && tie.InnerException is COMException)
            {
                COMException ce = (COMException)tie.InnerException;
                int errorCode = ce.ErrorCode;
                string message = ce.Message;
                message += ce.InnerException != null ? ce.InnerException.Message :                                      string.Empty;
                Trace.TraceError(message);
                message = "error_not_found";

                // if the exception is due to password not meeting complexity requirements, 
                // then return ProviderException                
                if ((errorCode == unchecked((int)0x800708c5)) 
                    || (errorCode == unchecked((int)0x8007202f)) 
                    || (errorCode == unchecked((int)0x8007052d)) 
                    || (errorCode == unchecked((int)0x8007052f)))
                {
                    message = "password_not_complex";//password policy not met
                }
                else if ((errorCode == unchecked((int)0x8000500d)))
                {
                    message = "no_secure_conn_for_password";//need higher than security=None
                }
                else if ((errorCode == unchecked((int)0x80072035)))
                {
                    //min password length or the password was used before
                    //The server is unwilling to process the request
                    message="min_password_length_used_before";
                }
                else if ((errorCode == unchecked((int)0x80005009)))
                {
                    message="password_clear_text";//need to set option to use clear text
                }

                throw new ProviderException(message, ce);//need to match this error
            }
           
        }
The exception usually is of type TargetInvocation, but it does not provide enough information to determine the problem. The function reads the inner exception (COM exception) and compare the error code to the possible Active Directory Service Interface (ADSI) errors. As you can see there are other possible errors, but we are only handling the ones that are specific to a password change.

The password not complex and the password length errors basically mean that the new password is not meeting the password policy requirements. We need to find out about those requirements from the AD administrator and make sure that the new password meets them before we attempt to set the password.

Some requirements may be:
  • Length of 8 characters
  • At least one capital letter or number
  • One special character

The unsecured connection and clear text errors can be addressed by making modifications to the code. When we try to update the password for a user, we can’t use a Secure=None level on the authentication type. We need to provide a higher security by adding these lines of code:

AuthenticationTypes auth = AuthenticationTypes.Signing | AuthenticationTypes.Sealing 
AuthenticationTypes.Secure;          
using (var de = new DirectoryEntry(adConnection, adUsername, adPw,auth))

The clear text error is due to the fact that the password is not hashed, so we need to indicate that this would be OK by adding this line of code:

userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingClear;

Once we have added those changes, the password update should be able to work or we should at least have added instrumentation in our code to help identified the problem. This is what the new version looks like:

public static void SetPasswordNew(string username, string password)
        {
            AuthenticationTypes auth = AuthenticationTypes.Signing |  AuthenticationTypes.Sealing                                        | AuthenticationTypes.Secure;          
            using (var de = new DirectoryEntry(adConnection, adUsername, adPw,auth))
            {
                //Win Server 2008 schema or less uses sAMAccount not userPrincipalName. 
                //add to config file
string query = String.Format("(&(objectClass=user)(&(userPrincipalName={0}))",                      username);           

                using (var ds = new DirectorySearcher(de))
                {
                    ds.SearchScope = System.DirectoryServices.SearchScope.Subtree;
                    ds.Filter = query;
                    SearchResult res = ds.FindOne();

                    if (res != null)
                    {
                        try
                        {
                            using (var userEntry = res.GetDirectoryEntry())
                            {
                                userEntry.Options.PasswordEncoding =                                 PasswordEncodingMethod.PasswordEncodingClear;    
                                userEntry.Invoke("SetPassword", new object[] { password });
                            }
                        }
                        catch (TargetInvocationException tie)
                        {
                            HandleComException(tie);
                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError(ex.Message); //handle this other error
                          
                        }
                       
                    }
                }
            }
        }


By adding these modifications, the password update should be successful.

Hope this helps and thanks.

0 comments :

Post a Comment

What do you think?