bool set_and_validate_user_attributes()

in sql/auth/sql_user.cc [1341:2045]


bool set_and_validate_user_attributes(
    THD *thd, LEX_USER *Str, acl_table::Pod_user_what_to_update &what_to_set,
    bool is_privileged_user, bool is_role, Table_ref *history_table,
    bool *history_check_done, const char *cmd,
    Userhostpassword_list &generated_passwords, I_multi_factor_auth **i_mfa,
    bool if_not_exists) {
  bool user_exists = false;
  ACL_USER *acl_user;
  plugin_ref plugin = nullptr;
  char outbuf[MAX_FIELD_WIDTH] = {0};
  unsigned int buflen = MAX_FIELD_WIDTH, inbuflen;
  const char *inbuf;
  const char *password = nullptr;
  const enum_sql_command command = thd->lex->sql_command;
  bool current_password_empty = false;
  bool new_password_empty = false;
  char new_password[MAX_FIELD_WIDTH]{0};
  unsigned int new_password_length = 0;
  authentication_policy::Factors policy_factors;
  authentication_policy::get_policy_factors(policy_factors);

  what_to_set.m_what = NONE_ATTR;
  what_to_set.m_user_attributes = acl_table::USER_ATTRIBUTE_NONE;
  assert(assert_acl_cache_read_lock(thd) || assert_acl_cache_write_lock(thd));

  if (history_check_done) *history_check_done = false;
  /* update plugin,auth str attributes */
  if (Str->first_factor_auth_info.uses_identified_by_clause ||
      Str->first_factor_auth_info.uses_identified_with_clause ||
      Str->first_factor_auth_info.uses_authentication_string_clause)
    what_to_set.m_what |= PLUGIN_ATTR;
  else
    what_to_set.m_what |= DEFAULT_AUTH_ATTR;

  /* update ssl attributes */
  if (thd->lex->ssl_type != SSL_TYPE_NOT_SPECIFIED)
    what_to_set.m_what |= SSL_ATTR;
  /* update connection attributes */
  if (thd->lex->mqh.specified_limits) what_to_set.m_what |= RESOURCE_ATTR;

  if ((acl_user = find_acl_user(Str->host.str, Str->user.str, true)))
    user_exists = true;

  /* copy password expire attributes to individual user */
  Str->alter_status = thd->lex->alter_password;

  if ((!user_exists && thd->lex->ignore_unknown_user) ||
      (user_exists && thd->lex->grant_if_exists)) {
    /*
     REVOKE IF EXISTS ... with missing privilege AND
     REVOKE ... IGNORE UNKNOWN USER with missing user account
     should be a no-op and be ignored.
    */
    if (command == SQLCOM_REVOKE) {
      what_to_set.m_what = NONE_ATTR;
      return false;
    }
  }

  if (user_exists && if_not_exists) {
    /*
      CREATE USER/ROLE IF NOT EXISTS ... when the account exists
      should be a no-op and be ignored.
    */
    assert(command == SQLCOM_CREATE_USER || command == SQLCOM_CREATE_ROLE);

    /* use the default plugin when it's not supplied */
    if (!Str->first_factor_auth_info.uses_identified_with_clause)
      authentication_policy::get_first_factor_default_plugin(
          thd->mem_root, &Str->first_factor_auth_info.plugin);

    /*
      Make sure the hashed credentials are set so the statement is logged
      correctly. We need the plugin reference for this.
    */
    st_mysql_auth *auth = nullptr;
    assert(plugin == nullptr);
    if (Str->first_factor_auth_info.uses_identified_by_clause ||
        Str->first_factor_auth_info.uses_authentication_string_clause) {
      plugin =
          my_plugin_lock_by_name(nullptr, Str->first_factor_auth_info.plugin,
                                 MYSQL_AUTHENTICATION_PLUGIN);

      /* check if plugin is loaded */
      if (!plugin) {
        what_to_set.m_what = NONE_ATTR;
        my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0),
                 Str->first_factor_auth_info.plugin.str);
        return true;
      }
      auth = (st_mysql_auth *)plugin_decl(plugin)->info;
    }

    if (Str->first_factor_auth_info.uses_identified_by_clause) {
      inbuf = Str->first_factor_auth_info.auth.str;
      inbuflen = (unsigned)Str->first_factor_auth_info.auth.length;
      if (auth->generate_authentication_string(outbuf, &buflen, inbuf,
                                               inbuflen)) {
        plugin_unlock(nullptr, plugin);
        what_to_set.m_what = NONE_ATTR;
        /*
          generate_authentication_string may return error status
          without setting actual error.
        */
        if (!thd->is_error()) {
          String error_user;
          log_user(thd, &error_user, Str, false);
          my_error(ER_CANNOT_USER, MYF(0), cmd, error_user.c_ptr_safe());
        }
        return true;
      }
      password = strmake_root(thd->mem_root, outbuf, buflen);
      Str->first_factor_auth_info.auth = {password, buflen};
    } else if (Str->first_factor_auth_info.uses_authentication_string_clause) {
      assert(!is_role);
      if (auth->validate_authentication_string(
              const_cast<char *>(Str->first_factor_auth_info.auth.str),
              (unsigned)Str->first_factor_auth_info.auth.length)) {
        my_error(ER_PASSWORD_FORMAT, MYF(0));
        plugin_unlock(nullptr, plugin);
        what_to_set.m_what = NONE_ATTR;
        return true;
      }
    }
    if (plugin) plugin_unlock(nullptr, plugin);
    what_to_set.m_what = NONE_ATTR;
    return false;
  }

  mysql_mutex_lock(&LOCK_password_history);
  Str->alter_status.password_history_length =
      Str->alter_status.use_default_password_history
          ? global_password_history
          : Str->alter_status.password_history_length;
  mysql_mutex_unlock(&LOCK_password_history);
  mysql_mutex_lock(&LOCK_password_reuse_interval);
  Str->alter_status.password_reuse_interval =
      Str->alter_status.use_default_password_reuse_interval
          ? global_password_reuse_interval
          : Str->alter_status.password_reuse_interval;
  mysql_mutex_unlock(&LOCK_password_reuse_interval);

  /* update password expire attributes */
  if (Str->alter_status.update_password_expired_column ||
      !Str->alter_status.use_default_password_lifetime ||
      Str->alter_status.expire_after_days)
    what_to_set.m_what |= PASSWORD_EXPIRE_ATTR;

  /* update account lock attribute */
  if (Str->alter_status.update_account_locked_column)
    what_to_set.m_what |= ACCOUNT_LOCK_ATTR;

  if (Str->first_factor_auth_info.plugin.length)
    optimize_plugin_compare_by_pointer(&Str->first_factor_auth_info.plugin);

  if (user_exists) {
    switch (command) {
      case SQLCOM_CREATE_USER: {
        /*
          Since user exists, we are likely going to fail
          unless IF NOT EXISTS is specified. In that case
          we need to use default plugin to generate password
          so that binlog entry is correct.
        */
        if (!Str->first_factor_auth_info.uses_identified_with_clause)
          authentication_policy::get_first_factor_default_plugin(
              thd->mem_root, &Str->first_factor_auth_info.plugin);
        break;
      }
      case SQLCOM_ALTER_USER: {
        if (!Str->first_factor_auth_info.uses_identified_with_clause) {
          /* If no plugin is given, get existing plugin */
          Str->first_factor_auth_info.plugin = acl_user->plugin;
        } else if (!(Str->first_factor_auth_info.uses_identified_by_clause ||
                     Str->first_factor_auth_info
                         .uses_authentication_string_clause) &&
                   auth_plugin_supports_expiration(
                       Str->first_factor_auth_info.plugin.str)) {
          /*
            This is an attempt to change existing users authentication plugin
            without specifying any password. In such cases, expire user's
            password so we can force password change on next login
          */
          Str->alter_status.update_password_expired_column = true;
          what_to_set.m_what |= PASSWORD_EXPIRE_ATTR;
        }
        /*
          always check for password expire/interval attributes as there is no
          way to differentiate NEVER EXPIRE and EXPIRE DEFAULT scenario
        */
        if (Str->alter_status.update_password_expired_fields)
          what_to_set.m_what |= PASSWORD_EXPIRE_ATTR;

        /* detect changes in the plugin name */
        if (Str->first_factor_auth_info.plugin.str != acl_user->plugin.str) {
          what_to_set.m_what |= DIFFERENT_PLUGIN_ATTR;
          if (Str->retain_current_password) {
            my_error(ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE, MYF(0),
                     Str->user.str, Str->host.str);
            what_to_set.m_what = NONE_ATTR;
            return true;
          }
          if (acl_user->credentials[SECOND_CRED].m_auth_string.length) {
            what_to_set.m_what |= USER_ATTRIBUTES;
            what_to_set.m_user_attributes |=
                acl_table::USER_ATTRIBUTE_DISCARD_PASSWORD;
          }
        }

        if (Str->retain_current_password || Str->discard_old_password) {
          assert(!(Str->retain_current_password && Str->discard_old_password));
          what_to_set.m_what |= USER_ATTRIBUTES;
          if (Str->retain_current_password)
            what_to_set.m_user_attributes |=
                acl_table::USER_ATTRIBUTE_RETAIN_PASSWORD;
          if (Str->discard_old_password)
            what_to_set.m_user_attributes |=
                acl_table::USER_ATTRIBUTE_DISCARD_PASSWORD;
          current_password_empty =
              acl_user->credentials[PRIMARY_CRED].m_auth_string.length ? false
                                                                       : true;
        }

        break;
      }
      case SQLCOM_SET_PASSWORD: {
        if (Str->retain_current_password) {
          what_to_set.m_what |= USER_ATTRIBUTES;
          what_to_set.m_user_attributes |=
              acl_table::USER_ATTRIBUTE_RETAIN_PASSWORD;
          current_password_empty =
              acl_user->credentials[PRIMARY_CRED].m_auth_string.length ? false
                                                                       : true;
        }
        break;
      }

      /*
        We need to fill in the elements of the LEX_USER structure even for
        GRANT and REVOKE.
       */
      case SQLCOM_GRANT:
        [[fallthrough]];
      case SQLCOM_REVOKE:
        what_to_set.m_what = NONE_ATTR;
        Str->first_factor_auth_info.plugin = acl_user->plugin;
        Str->first_factor_auth_info.auth.str =
            acl_user->credentials[PRIMARY_CRED].m_auth_string.str;
        Str->first_factor_auth_info.auth.length =
            acl_user->credentials[PRIMARY_CRED].m_auth_string.length;
        break;
      default: {
        if (!Str->first_factor_auth_info.uses_identified_with_clause) {
          /* if IDENTIFIED WITH is not specified set plugin from cache */
          Str->first_factor_auth_info.plugin = acl_user->plugin;
          /* set auth str from cache when not specified for existing user */
          if (!(Str->first_factor_auth_info.uses_identified_by_clause ||
                Str->first_factor_auth_info
                    .uses_authentication_string_clause)) {
            Str->first_factor_auth_info.auth.str =
                acl_user->credentials[PRIMARY_CRED].m_auth_string.str;
            Str->first_factor_auth_info.auth.length =
                acl_user->credentials[PRIMARY_CRED].m_auth_string.length;
          }
        } else if (!(Str->first_factor_auth_info.uses_identified_by_clause ||
                     Str->first_factor_auth_info
                         .uses_authentication_string_clause) &&
                   auth_plugin_supports_expiration(
                       Str->first_factor_auth_info.plugin.str)) {
          /*
            This is an attempt to change existing users authentication plugin
            without specifying any password. In such cases, expire user's
            password so we can force password change on next login
          */
          Str->alter_status.update_password_expired_column = true;
          what_to_set.m_what |= PASSWORD_EXPIRE_ATTR;
        }
      }
    };

    /* if we don't update password history take the user's password history */
    if (!Str->alter_status.update_password_history) {
      if (acl_user->use_default_password_history) {
        mysql_mutex_lock(&LOCK_password_history);
        Str->alter_status.password_history_length = global_password_history;
        mysql_mutex_unlock(&LOCK_password_history);
      } else
        Str->alter_status.password_history_length =
            acl_user->password_history_length;
    }
    /* if we don't update password reuse interval take the user's interval */
    if (!Str->alter_status.update_password_reuse_interval) {
      if (acl_user->use_default_password_reuse_interval) {
        mysql_mutex_lock(&LOCK_password_reuse_interval);
        Str->alter_status.password_reuse_interval =
            global_password_reuse_interval;
        mysql_mutex_unlock(&LOCK_password_reuse_interval);
      } else
        Str->alter_status.password_reuse_interval =
            acl_user->password_reuse_interval;
    }
  } else { /* User does not exist */
    if (add_default_auth_plugins(thd, Str, policy_factors)) return true;

    if (command == SQLCOM_GRANT) {
      my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0));
      return true;
    }
  }

  optimize_plugin_compare_by_pointer(&Str->first_factor_auth_info.plugin);

  /*
    Check if non-default password expiraition option
    is passed to a plugin that does not support it and raise
    and error if it is.
  */
  if (Str->alter_status.update_password_expired_fields &&
      !Str->alter_status.use_default_password_lifetime &&
      Str->alter_status.expire_after_days != 0 &&
      !auth_plugin_supports_expiration(
          Str->first_factor_auth_info.plugin.str)) {
    my_error(ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD, MYF(0),
             Str->first_factor_auth_info.plugin.length,
             Str->first_factor_auth_info.plugin.str);
    return true;
  }

  plugin = my_plugin_lock_by_name(nullptr, Str->first_factor_auth_info.plugin,
                                  MYSQL_AUTHENTICATION_PLUGIN);

  /* check if plugin is loaded */
  if (!plugin) {
    what_to_set.m_what = NONE_ATTR;
    my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0),
             Str->first_factor_auth_info.plugin.str);
    return (true);
  }

  st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;

  if (user_exists && (what_to_set.m_what & PLUGIN_ATTR)) {
    if (auth->authentication_flags &
        AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE) {
      if (!is_privileged_user &&
          (command == SQLCOM_ALTER_USER || command == SQLCOM_GRANT)) {
        /*
          An external plugin that prevents user
          to change authentication_string information
          unless user is privileged.
        */
        what_to_set.m_what = NONE_ATTR;
        my_error(ER_ACCESS_DENIED_ERROR, MYF(0),
                 thd->security_context()->priv_user().str,
                 thd->security_context()->priv_host().str,
                 thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO));
        plugin_unlock(nullptr, plugin);
        return (true);
      }
    }

    if (!(auth->authentication_flags & AUTH_FLAG_USES_INTERNAL_STORAGE) &&
        command == SQLCOM_SET_PASSWORD) {
      /*
        A plugin that does not use internal storage and
        hence does not support SET PASSWORD
      */
      my_error(ER_SET_PASSWORD_AUTH_PLUGIN_ERROR, MYF(0), Str->user.str,
               Str->host.str);
      plugin_unlock(nullptr, plugin);
      what_to_set.m_what = NONE_ATTR;
      return (true);
    }
  }

  if (!(auth->authentication_flags & AUTH_FLAG_USES_INTERNAL_STORAGE)) {
    if (Str->alter_status.password_history_length ||
        Str->alter_status.password_reuse_interval) {
      /*
        A plugin that does not use internal storage and
        hence does not support password history is passed a password history
      */
      if (Str->alter_status.update_password_history ||
          Str->alter_status.update_password_reuse_interval)
        push_warning_printf(
            thd, Sql_condition::SL_WARNING,
            ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID,
            ER_THD(thd, ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID),
            Str->user.str, Str->host.str, plugin_decl(plugin)->name);
      /* reset back the password history clauses for that user */
      Str->alter_status.password_history_length = 0;
      Str->alter_status.password_reuse_interval = 0;
      Str->alter_status.update_password_history = true;
      Str->alter_status.update_password_reuse_interval = true;
      Str->alter_status.use_default_password_history = true;
      Str->alter_status.use_default_password_reuse_interval = true;
    }
  }

  if ((auth->authentication_flags & AUTH_FLAG_REQUIRES_REGISTRATION) &&
      command == SQLCOM_CREATE_USER) {
    /*
      Plugin which requires registration step is not allowed as part of first
      factor auth method if user does not have PASSWORDLESS_USER_ADMIN privilege
    */
    if (!(thd->security_context()
              ->has_global_grant(STRING_WITH_LEN("PASSWORDLESS_USER_ADMIN"))
              .first)) {
      my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
               "PASSWORDLESS_USER_ADMIN");
      plugin_unlock(nullptr, plugin);
      what_to_set.m_what = NONE_ATTR;
      return (false);
    }
  }
  /*
    If auth string is specified, change it to hash.
    Validate empty credentials for new user ex: CREATE USER u1;
    We skip authentication string generation if the issued statement was
    CREATE ROLE.
  */
  if (!is_role &&
      (Str->first_factor_auth_info.uses_identified_by_clause ||
       (Str->first_factor_auth_info.auth.length == 0 && !user_exists))) {
    inbuf = Str->first_factor_auth_info.auth.str;
    inbuflen = (unsigned)Str->first_factor_auth_info.auth.length;
    std::string gen_password;
    if (Str->first_factor_auth_info.has_password_generator) {
      thd->m_disable_password_validation = true;
      generate_random_password(&gen_password,
                               thd->variables.generated_random_password_length);
      inbuf = gen_password.c_str();
      inbuflen = gen_password.length();
      random_password_info p{std::string(Str->user.str),
                             std::string(Str->host.str), gen_password, 1};
      generated_passwords.push_back(p);
    }
    if (auth->generate_authentication_string(outbuf, &buflen, inbuf,
                                             inbuflen) ||
        auth_verify_password_history(thd, &Str->user, &Str->host,
                                     Str->alter_status.password_history_length,
                                     Str->alter_status.password_reuse_interval,
                                     auth, inbuf, inbuflen, outbuf, buflen,
                                     history_table, what_to_set.m_what)) {
      plugin_unlock(nullptr, plugin);
      what_to_set.m_what = NONE_ATTR;
      /*
        generate_authentication_string may return error status
        without setting actual error.
      */
      if (!thd->is_error()) {
        String error_user;
        log_user(thd, &error_user, Str, false);
        my_error(ER_CANNOT_USER, MYF(0), cmd, error_user.c_ptr_safe());
      }
      return (true);
    }
    /* Allow for password validation in case it was disabled before */
    thd->m_disable_password_validation = false;
    if (history_check_done) *history_check_done = true;
    if (buflen) {
      password = strmake_root(thd->mem_root, outbuf, buflen);
    } else
      password = const_cast<char *>("");
    /*
       Erase in memory copy of plain text password, unless we need it
       later to send to client as a result set.
    */
    if (Str->first_factor_auth_info.auth.length > 0) {
      if (user_exists && Str->uses_replace_clause) {
        assert(Str->first_factor_auth_info.auth.length < MAX_FIELD_WIDTH);
        new_password_length = Str->first_factor_auth_info.auth.length;
        strncpy(new_password, Str->first_factor_auth_info.auth.str,
                std::min(static_cast<size_t>(MAX_FIELD_WIDTH),
                         Str->first_factor_auth_info.auth.length));
      }
      my_cleanse(const_cast<char *>(Str->first_factor_auth_info.auth.str),
                 Str->first_factor_auth_info.auth.length);
    }
    /* Use the authentication_string field as password */
    Str->first_factor_auth_info.auth = {password, buflen};
    new_password_empty = buflen ? false : true;
  }

  /* Check iff the REPLACE clause is specified correctly for the user */
  if ((what_to_set.m_what & PLUGIN_ATTR) &&
      validate_password_require_current(thd, Str, acl_user, auth, new_password,
                                        new_password_length, is_privileged_user,
                                        user_exists)) {
    plugin_unlock(nullptr, plugin);
    what_to_set.m_what = NONE_ATTR;
    return (true);
  }

  /* Validate hash string */
  if (Str->first_factor_auth_info.uses_authentication_string_clause) {
    /*
      The statement CREATE ROLE calls mysql_create_user() with a set of
      lexicographic parameters: users_identified_by_password_caluse= false etc
      It also sets is_role= true. We don't have to check this parameter here
      since we're already know that the above parameters will be false
      but we place an extra assert here to remind us about the complex
      interdependencies if mysql_create_user() is refactored.
    */
    assert(!is_role);
    if (auth->validate_authentication_string(
            const_cast<char *>(Str->first_factor_auth_info.auth.str),
            (unsigned)Str->first_factor_auth_info.auth.length)) {
      my_error(ER_PASSWORD_FORMAT, MYF(0));
      plugin_unlock(nullptr, plugin);
      what_to_set.m_what = NONE_ATTR;
      return (true);
    }
    /*
      Call the password history validation so that it can store the incoming
      hash into the password history table.
      Here we can't check if the password was used since we don't have the
      cleartext password, but we still want to record it into the history table.
      Covers replication scenario too since the IDENTIFIED BY will get
      rewritten to IDENTIFIED ... WITH ... AS
    */
    if (auth_verify_password_history(
            thd, &Str->user, &Str->host,
            Str->alter_status.password_history_length,
            Str->alter_status.password_reuse_interval, auth, nullptr, 0,
            Str->first_factor_auth_info.auth.str,
            (unsigned)Str->first_factor_auth_info.auth.length, history_table,
            what_to_set.m_what)) {
      /* we should have an error generated here already */
      plugin_unlock(nullptr, plugin);
      what_to_set.m_what = NONE_ATTR;
      return (true);
    }
    if (history_check_done) *history_check_done = true;
  }

  if (user_exists && (what_to_set.m_user_attributes &
                      (acl_table::USER_ATTRIBUTE_RETAIN_PASSWORD |
                       acl_table::USER_ATTRIBUTE_DISCARD_PASSWORD))) {
    if (!(auth->authentication_flags & AUTH_FLAG_USES_INTERNAL_STORAGE)) {
      /* We do not support multiple passwords */
      if (Str->retain_current_password) {
        push_warning_printf(
            thd, Sql_condition::SL_WARNING,
            ER_WARNING_RETAIN_CURRENT_PASSWORD_CLAUSE_VOID,
            ER_THD(thd, ER_WARNING_RETAIN_CURRENT_PASSWORD_CLAUSE_VOID),
            Str->user.str, Str->host.str, plugin_decl(plugin)->name);
        what_to_set.m_user_attributes &=
            ~acl_table::USER_ATTRIBUTE_RETAIN_PASSWORD;
      }

      if (Str->discard_old_password) {
        push_warning_printf(
            thd, Sql_condition::SL_WARNING,
            ER_WARNING_DISCARD_OLD_PASSWORD_CLAUSE_VOID,
            ER_THD(thd, ER_WARNING_DISCARD_OLD_PASSWORD_CLAUSE_VOID),
            Str->user.str, Str->host.str, plugin_decl(plugin)->name);
        what_to_set.m_user_attributes &=
            ~acl_table::USER_ATTRIBUTE_DISCARD_PASSWORD;
      }
    } else if (what_to_set.m_user_attributes &
               acl_table::USER_ATTRIBUTE_RETAIN_PASSWORD) {
      if (current_password_empty) {
        my_error(ER_SECOND_PASSWORD_CANNOT_BE_EMPTY, MYF(0), Str->user.str,
                 Str->host.str);
        plugin_unlock(nullptr, plugin);
        what_to_set.m_what = NONE_ATTR;
        return true;
      }

      if (what_to_set.m_what & PLUGIN_ATTR && new_password_empty) {
        my_error(ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED, MYF(0), Str->user.str,
                 Str->host.str);
        plugin_unlock(nullptr, plugin);
        what_to_set.m_what = NONE_ATTR;
        return true;
      }
    }
  }

  if (user_exists && (what_to_set.m_what & PLUGIN_ATTR) &&
      !Str->first_factor_auth_info.auth.length &&
      (auth->authentication_flags & AUTH_FLAG_USES_INTERNAL_STORAGE)) {
    if (acl_user->credentials[SECOND_CRED].m_auth_string.length) {
      what_to_set.m_what |= USER_ATTRIBUTES;
      what_to_set.m_user_attributes |=
          acl_table::USER_ATTRIBUTE_DISCARD_PASSWORD;
    }
  }

  if (Str->alter_status.update_failed_login_attempts) {
    what_to_set.m_what |= USER_ATTRIBUTES;
    what_to_set.m_user_attributes |=
        acl_table::USER_ATTRIBUTE_FAILED_LOGIN_ATTEMPTS;
  }
  if (Str->alter_status.update_password_lock_time) {
    what_to_set.m_what |= USER_ATTRIBUTES;
    what_to_set.m_user_attributes |=
        acl_table::USER_ATTRIBUTE_PASSWORD_LOCK_TIME;
  }

  /*
    We issued a ALTER USER x ATTRIBUTE or COMMENT statement and need
    to update the user attributes.
  */
  if (thd->lex->alter_user_attribute !=
      enum_alter_user_attribute::ALTER_USER_COMMENT_NOT_USED) {
    what_to_set.m_what |= USER_ATTRIBUTES;
  }
  plugin_unlock(nullptr, plugin);

  if (check_for_authentication_policy(thd, Str, command,
                                      (acl_user ? acl_user->m_mfa : nullptr),
                                      policy_factors))
    return true;

  /* initialize MFA */
  if (Str->mfa_list.size()) {
    if (user_exists && command == SQLCOM_CREATE_USER) return false;
    /* Update Multi factor authentication details */
    I_multi_factor_auth *mfa = *i_mfa;
    LEX_MFA *tmp_lex_mfa;
    List_iterator<LEX_MFA> mfa_list_it(Str->mfa_list);
    mfa = new (&global_acl_memory) Multi_factor_auth_list(&global_acl_memory);
    while ((tmp_lex_mfa = mfa_list_it++))
      mfa->add_factor(new (&global_acl_memory) Multi_factor_auth_info(
          &global_acl_memory, tmp_lex_mfa));

    if (user_exists) {
      if (acl_user->m_mfa) {
        if (acl_user->m_mfa->is_alter_allowed(thd, Str)) return true;
        /*
          copy attributes from in memory copy of ACL_USER::m_mfa to new Multi
          factor authentication method interface.
        */
        acl_user->m_mfa->alter_mfa(mfa);
      } else {
        MEM_ROOT mr(PSI_NOT_INSTRUMENTED, 256);
        I_multi_factor_auth *tmp = new (&mr) Multi_factor_auth_list(&mr);
        if (tmp->is_alter_allowed(thd, Str)) {
          mr.Clear();
          return true;
        }
      }
      /* perform regestration step */
      mfa_list_it = Str->mfa_list;
      while ((tmp_lex_mfa = mfa_list_it++)) {
        if (tmp_lex_mfa->init_registration) {
          /* initiate registration step */
          if (mfa->init_registration(thd, tmp_lex_mfa->nth_factor)) return true;
          break;
        } else if (tmp_lex_mfa->finish_registration) {
          /* finish registration step */
          if (mfa->finish_registration(thd, Str, tmp_lex_mfa->nth_factor))
            return true;
        }
        if (mfa->is_passwordless()) {
          /* save auth string in LEX_USER::mfa_list before replacing */
          mfa->get_info_for_query_rewrite(thd, Str);
          /*
            On replica, ALTER USER ... FINISH REGISTRATION is converted to
            ALTER USER .. MODIFY 2 FACTOR IDENTIFIED WITH ..., thus copy
            plugin and auth string into first factor auth method
          */
          if (tmp_lex_mfa->modify_factor) {
            lex_string_strmake(
                thd->mem_root, &Str->first_factor_auth_info.plugin,
                tmp_lex_mfa->plugin.str, tmp_lex_mfa->plugin.length);
            lex_string_strmake(thd->mem_root, &Str->first_factor_auth_info.auth,
                               tmp_lex_mfa->auth.str, tmp_lex_mfa->auth.length);
          }
          what_to_set.m_what |= PLUGIN_ATTR;
          mfa = nullptr;
        }
      }
      /* if no mfa methods exists, set mfa to nullptr */
      if (mfa && mfa->get_multi_factor_auth_list()->get_mfa_list_size() == 0)
        mfa = nullptr;
    }
    if (mfa) {
      /* validate auth plugins in Multi factor authentication methods */
      if (mfa->validate_plugins_in_auth_chain(thd, policy_factors)) return true;
      /*
        Once alter is done check that new mfa methods are inline with
        authentication policy.
      */
      if (command == SQLCOM_ALTER_USER &&
          mfa->validate_against_authentication_policy(thd, policy_factors))
        return true;
      /*
        Fill in details related to Multi factor authentication methods into
        LEX_USER::mfa_list, to be consumed by query rewrite methods.
      */
      mfa->get_info_for_query_rewrite(thd, Str);
    }
    *i_mfa = mfa;
    /* update MFA details in user attributes */
    what_to_set.m_what |= USER_ATTRIBUTES;
    what_to_set.m_user_attributes |= acl_table::USER_ATTRIBUTE_UPDATE_MFA;
  } else {
    /* Update i_mfa in case Multi factor auth methods are not touched */
    if (user_exists && i_mfa) *i_mfa = acl_user->m_mfa;
  }
  return (false);
}