/// <summary>
        /// Change user password
        /// </summary>
        public bool ChangePassword(string userName, string currentPassword, string newPassword, out string errorReason)
        {
            var identity = LdapIdentity.ParseUser(userName);

            errorReason = null;

            try
            {
                LdapProfile userProfile;

                using (var connection = new LdapConnection(_configuration.Domain))
                {
                    connection.Credential = new NetworkCredential(identity.Name, currentPassword);
                    connection.Bind();

                    var domain = LdapIdentity.FqdnToDn(_configuration.Domain);

                    var isProfileLoaded = LoadProfile(connection, domain, identity, out var profile);
                    if (!isProfileLoaded)
                    {
                        errorReason = Resources.AD.UnableToChangePassword;
                        return(false);
                    }
                    userProfile = profile;
                }

                _logger.Debug($"Changing password for user '{identity.Name}' in {userProfile.BaseDn.DnToFqdn()}");

                using (var ctx = new PrincipalContext(ContextType.Domain, userProfile.BaseDn.DnToFqdn(), null, ContextOptions.Negotiate, identity.Name, currentPassword))
                {
                    using (var user = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, userProfile.DistinguishedName))
                    {
                        user.ChangePassword(currentPassword, newPassword);
                        user.Save();
                    }
                }

                _logger.Debug($"Password changed for user '{identity.Name}'");
                return(true);
            }
            catch (PasswordException pex)
            {
                _logger.Warning($"Changing password for user '{identity.Name}' failed: {pex.Message}, {pex.HResult}");
                errorReason = Resources.AD.PasswordDoesNotMeetRequirements;
            }
            catch (Exception ex)
            {
                _logger.Warning($"Changing password for user {identity.Name} failed: {ex.Message}");
                errorReason = Resources.AD.UnableToChangePassword;
            }

            return(false);
        }
        /// <summary>
        /// Verify User Name, Password, User Status and Policy against Active Directory
        /// </summary>
        public ActiveDirectoryCredentialValidationResult VerifyCredential(string userName, string password)
        {
            if (string.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException(nameof(userName));
            }
            if (string.IsNullOrEmpty(password))
            {
                _logger.Error($"Empty password provided for user '{userName}'");
                return(ActiveDirectoryCredentialValidationResult.UnknowError("Invalid credentials"));
            }

            var user = LdapIdentity.ParseUser(userName);

            try
            {
                _logger.Debug($"Verifying user '{user.Name}' credential and status at {_configuration.Domain}");

                using (var connection = new LdapConnection(_configuration.Domain))
                {
                    connection.Credential = new NetworkCredential(user.Name, password);
                    connection.Bind();

                    _logger.Information($"User '{user.Name}' credential and status verified successfully at {_configuration.Domain}");

                    var domain = LdapIdentity.FqdnToDn(_configuration.Domain);

                    var isProfileLoaded = LoadProfile(connection, domain, user, out var profile);
                    if (!isProfileLoaded)
                    {
                        return(ActiveDirectoryCredentialValidationResult.UnknowError("Unable to load profile"));
                    }

                    var checkGroupMembership = !string.IsNullOrEmpty(_configuration.ActiveDirectory2FaGroup);
                    if (checkGroupMembership)
                    {
                        var isMemberOf = IsMemberOf(connection, profile.BaseDn, user, _configuration.ActiveDirectory2FaGroup);

                        if (!isMemberOf)
                        {
                            _logger.Information($"User '{user.Name}' is NOT member of {_configuration.ActiveDirectory2FaGroup} group");
                            _logger.Information($"Bypass second factor for user '{user.Name}'");
                            return(ActiveDirectoryCredentialValidationResult.ByPass());
                        }
                        _logger.Information($"User '{user.Name}' is member of {_configuration.ActiveDirectory2FaGroup} group");
                    }

                    var result = ActiveDirectoryCredentialValidationResult.Ok();

                    result.DisplayName = profile.DisplayName;
                    result.Email       = profile.Email;

                    if (_configuration.UseActiveDirectoryUserPhone)
                    {
                        result.Phone = profile.Phone;
                    }
                    if (_configuration.UseActiveDirectoryMobileUserPhone)
                    {
                        result.Phone = profile.Mobile;
                    }

                    return(result);
                }
            }
            catch (LdapException lex)
            {
                var result = ActiveDirectoryCredentialValidationResult.KnownError(lex.ServerErrorMessage);
                _logger.Warning($"Verification user '{user.Name}' at {_configuration.Domain} failed: {result.Reason}");
                return(result);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, $"Verification user '{user.Name}' at {_configuration.Domain} failed.");
                return(ActiveDirectoryCredentialValidationResult.UnknowError());
            }
        }
        private IDictionary <string, LdapIdentity> LoadForestSchema(LdapConnection connection, LdapIdentity root)
        {
            _logger.Debug($"Loading forest schema from {root.Name}");

            try
            {
                var domainNameSuffixes = new Dictionary <string, LdapIdentity>();

                var trustedDomainsResult = Query(connection,
                                                 "CN=System," + root.Name,
                                                 "objectClass=trustedDomain",
                                                 SearchScope.OneLevel,
                                                 "cn");

                var schema = new List <LdapIdentity>
                {
                    root
                };

                for (var i = 0; i < trustedDomainsResult.Entries.Count; i++)
                {
                    var entry     = trustedDomainsResult.Entries[i];
                    var attribute = entry.Attributes["cn"];
                    if (attribute != null)
                    {
                        var trustPartner = LdapIdentity.FqdnToDn(attribute[0].ToString());

                        _logger.Debug($"Found trusted domain {trustPartner.Name}");

                        if (!trustPartner.IsChildOf(root))
                        {
                            schema.Add(trustPartner);
                        }
                    }
                }

                foreach (var domain in schema)
                {
                    var domainSuffix = domain.DnToFqdn();
                    if (!domainNameSuffixes.ContainsKey(domainSuffix))
                    {
                        domainNameSuffixes.Add(domainSuffix, domain);
                    }

                    try
                    {
                        var uPNSuffixesResult = Query(connection,
                                                      "CN=Partitions,CN=Configuration," + domain.Name,
                                                      "objectClass=*",
                                                      SearchScope.Base,
                                                      "uPNSuffixes");

                        for (var i = 0; i < uPNSuffixesResult.Entries.Count; i++)
                        {
                            var entry     = uPNSuffixesResult.Entries[i];
                            var attribute = entry.Attributes["uPNSuffixes"];
                            if (attribute != null)
                            {
                                for (var j = 0; j < attribute.Count; j++)
                                {
                                    var suffix = attribute[j].ToString();

                                    if (!domainNameSuffixes.ContainsKey(suffix))
                                    {
                                        domainNameSuffixes.Add(suffix, domain);
                                        _logger.Debug($"Found alternative UPN suffix {suffix} for domain {domain.Name}");
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.Warning($"Unable to query {domain.Name}: {ex.Message}");
                    }
                }

                return(domainNameSuffixes);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Unable to load forest schema");
                return(null);
            }
        }