Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: Support more PASSWORD REQUIRE CURRENT options | tidb-test=pr/2363 #54683

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
91ccc2b
Support more PASSWORD REQUIRE CURRENT options
dveeden Jul 17, 2024
5efb8f3
Fix formatting
dveeden Jul 17, 2024
952c13f
Update executor/grant results
dveeden Jul 17, 2024
7b0cd2e
Update TestBootstrap
dveeden Jul 17, 2024
1ae90b7
Update TestMonitorTheSystemTableIncremental
dveeden Jul 17, 2024
8e445d6
Update TestFailedLoginTrackingBasic
dveeden Jul 17, 2024
56e4782
Update TestBootstrapWithError
dveeden Jul 17, 2024
2208adc
Update TestShowCreateUser
dveeden Jul 17, 2024
73281cc
Support REPLACE for password replacement with current password
dveeden Jul 18, 2024
7644793
Update for USER() ... IDENTIFIED BY...REPLACE...
dveeden Jul 18, 2024
0766eb4
Add new error
dveeden Jul 18, 2024
012e181
Update errdoc
dveeden Jul 18, 2024
891df81
Various updates
dveeden Jul 22, 2024
93d46a0
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Jul 22, 2024
2877007
Update errors.toml
dveeden Jul 22, 2024
407dcfb
Update comment
dveeden Jul 22, 2024
8432bed
Fix formatting
dveeden Jul 22, 2024
03cc38b
Check if user is not nil, add more logging
dveeden Jul 22, 2024
d165264
Fix table layout in comment
dveeden Jul 22, 2024
6247b35
Update TestMonitorTheSystemTableIncremental
dveeden Jul 22, 2024
d191090
Small changes
dveeden Jul 22, 2024
01b2345
Update test
dveeden Jul 22, 2024
1a04808
Fix TestAbnormalMySQLTable
dveeden Jul 22, 2024
9769783
De-duplicate code in CheckCurrentPassword
dveeden Jul 22, 2024
6cf82c1
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Jul 23, 2024
121ee16
Add parser testing
dveeden Jul 23, 2024
715d759
Another parser test
dveeden Jul 23, 2024
a439c49
Formatting fixes
dveeden Jul 23, 2024
69400d5
Fix CREATE USER and integration tests
dveeden Jul 24, 2024
2e6e352
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Jul 25, 2024
6def7e8
Do the same for SET PASSWORD
dveeden Jul 25, 2024
773a380
Remove unuseful line based on review
dveeden Jul 25, 2024
dfa9f53
Add tests for SET PASSWORD
dveeden Jul 25, 2024
ab4662a
Fix SET PASSWORD
dveeden Jul 25, 2024
11a3ea7
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Jul 26, 2024
cfb5873
Add more integration tests
dveeden Jul 26, 2024
9d0c6a6
Make sure UserSpec with an empty authstring but a non-empty replacest…
dveeden Jul 26, 2024
7b60fa2
Try to simplify parser changes
dveeden Jul 29, 2024
4ee9c90
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Aug 20, 2024
7fa2d96
Fix TestColumnTable
dveeden Aug 21, 2024
2796d37
Merge remote-tracking branch 'upstream/master' into password_require_…
dveeden Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion br/pkg/restore/snap_client/systable_restore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@ func TestCheckSysTableCompatibility(t *testing.T) {
//
// The above variables are in the file br/pkg/restore/systable_restore.go
func TestMonitorTheSystemTableIncremental(t *testing.T) {
require.Equal(t, int64(217), session.CurrentBootstrapVersion)
require.Equal(t, int64(218), session.CurrentBootstrapVersion)
}
15 changes: 15 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,11 @@ error = '''
Cannot use these credentials for '%s@%s' because they contradict the password history policy.
'''

["executor:3893"]
error = '''
Do not specify the current password while changing it for other users.
'''

["executor:3929"]
error = '''
Dynamic privilege '%s' is not registered with the server.
Expand Down Expand Up @@ -2696,6 +2701,16 @@ error = '''
%s is not granted to %s
'''

["privilege:3891"]
error = '''
Incorrect current password. Specify the correct password which has to be replaced.
'''

["privilege:3892"]
error = '''
Current password needs to be specified in the REPLACE clause in order to change it.
'''

["schema:1007"]
error = '''
Can't create database '%-.192s'; database exists
Expand Down
3 changes: 3 additions & 0 deletions pkg/errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,9 @@ const (
ErrInvalidJSONType = 3853
ErrCannotConvertString = 3854
ErrDependentByPartitionFunctional = 3855
ErrIncorrectCurrentPassword = 3891
ErrMissingCurrentPassword = 3892
ErrCurrentPasswordNotRequired = 3893
ErrInvalidJSONValueForFuncIndex = 3903
ErrJSONValueOutOfRangeForFuncIndex = 3904
ErrFunctionalIndexDataIsTooLong = 3907
Expand Down
3 changes: 3 additions & 0 deletions pkg/errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,9 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrDependentByPartitionFunctional: mysql.Message("Column '%s' has a partitioning function dependency and cannot be dropped or renamed", nil),
ErrCannotConvertString: mysql.Message("Cannot convert string '%.64s' from %s to %s", nil),
ErrInvalidJSONType: mysql.Message("Invalid JSON type in argument %d to function %s; an %s is required.", nil),
ErrIncorrectCurrentPassword: mysql.Message("Incorrect current password. Specify the correct password which has to be replaced.", nil),
ErrMissingCurrentPassword: mysql.Message("Current password needs to be specified in the REPLACE clause in order to change it.", nil),
ErrCurrentPasswordNotRequired: mysql.Message("Do not specify the current password while changing it for other users.", nil),
ErrInvalidJSONValueForFuncIndex: mysql.Message("Invalid JSON value for CAST for expression index '%s'", nil),
ErrJSONValueOutOfRangeForFuncIndex: mysql.Message("Out of range JSON value for CAST for expression index '%s'", nil),
ErrFunctionalIndexDataIsTooLong: mysql.Message("Data too long for expression index '%s'", nil),
Expand Down
2 changes: 1 addition & 1 deletion pkg/executor/infoschema_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ func TestColumnTable(t *testing.T) {
testkit.RowsWithSep("|",
"test|tbl1|col_2"))
tk.MustQuery(`select count(*) from information_schema.columns;`).Check(
testkit.RowsWithSep("|", "4983"))
testkit.RowsWithSep("|", "4984"))
}

func TestIndexUsageTable(t *testing.T) {
Expand Down
44 changes: 34 additions & 10 deletions pkg/executor/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -1745,11 +1745,20 @@ func (e *ShowExec) fetchShowCreateUser(ctx context.Context) error {
exec := e.Ctx().GetRestrictedSQLExecutor()

rows, _, err := exec.ExecRestrictedSQL(ctx, nil,
`SELECT plugin, Account_locked, user_attributes->>'$.metadata', Token_issuer,
Password_reuse_history, Password_reuse_time, Password_expired, Password_lifetime,
user_attributes->>'$.Password_locking.failed_login_attempts',
user_attributes->>'$.Password_locking.password_lock_time_days'
FROM %n.%n WHERE User=%? AND Host=%?`,
`SELECT
plugin,
Account_locked,
user_attributes->>'$.metadata',
Token_issuer,
Password_reuse_history,
Password_reuse_time,
Password_expired,
Password_lifetime,
Password_require_current,
user_attributes->>'$.Password_locking.failed_login_attempts',
user_attributes->>'$.Password_locking.password_lock_time_days'
FROM %n.%n
WHERE User=%? AND Host=%?`,
mysql.SystemDB, mysql.UserTable, userName, strings.ToLower(hostName))
if err != nil {
return errors.Trace(err)
Expand Down Expand Up @@ -1813,20 +1822,33 @@ func (e *ShowExec) fetchShowCreateUser(ctx context.Context) error {
passwordExpiredStr = fmt.Sprintf("PASSWORD EXPIRE INTERVAL %d DAY", passwordLifetime)
}

failedLoginAttempts := rows[0].GetString(8)
// Password_require_current
passwordRequireCurrent := rows[0].GetEnum(8).String()
var passwordRequireCurrentStr string
switch passwordRequireCurrent {
case "Y":
passwordRequireCurrentStr = "PASSWORD REQUIRE CURRENT"
case "N":
passwordRequireCurrentStr = "PASSWORD REQUIRE CURRENT OPTIONAL"
default:
passwordRequireCurrentStr = "PASSWORD REQUIRE CURRENT DEFAULT"
}

failedLoginAttempts := rows[0].GetString(9)
if len(failedLoginAttempts) > 0 {
failedLoginAttempts = " FAILED_LOGIN_ATTEMPTS " + failedLoginAttempts
}

passwordLockTimeDays := rows[0].GetString(9)
passwordLockTimeDays := rows[0].GetString(10)
if len(passwordLockTimeDays) > 0 {
if passwordLockTimeDays == "-1" {
passwordLockTimeDays = " PASSWORD_LOCK_TIME UNBOUNDED"
} else {
passwordLockTimeDays = " PASSWORD_LOCK_TIME " + passwordLockTimeDays
}
}
rows, _, err = exec.ExecRestrictedSQL(ctx, nil, `SELECT Priv FROM %n.%n WHERE User=%? AND Host=%?`, mysql.SystemDB, mysql.GlobalPrivTable, userName, hostName)
rows, _, err = exec.ExecRestrictedSQL(ctx, nil, `SELECT Priv FROM %n.%n WHERE User=%? AND Host=%?`,
mysql.SystemDB, mysql.GlobalPrivTable, userName, hostName)
if err != nil {
return errors.Trace(err)
}
Expand All @@ -1849,8 +1871,10 @@ func (e *ShowExec) fetchShowCreateUser(ctx context.Context) error {
}

// FIXME: the returned string is not escaped safely
showStr := fmt.Sprintf("CREATE USER '%s'@'%s' IDENTIFIED WITH '%s'%s REQUIRE %s%s %s ACCOUNT %s PASSWORD HISTORY %s PASSWORD REUSE INTERVAL %s%s%s%s",
e.User.Username, e.User.Hostname, authPlugin, authStr, require, tokenIssuer, passwordExpiredStr, accountLocked, passwordHistory, passwordReuseInterval, failedLoginAttempts, passwordLockTimeDays, userAttributes)
showStr := fmt.Sprintf(
"CREATE USER '%s'@'%s' IDENTIFIED WITH '%s'%s REQUIRE %s%s %s ACCOUNT %s PASSWORD HISTORY %s PASSWORD REUSE INTERVAL %s %s%s%s%s",
e.User.Username, e.User.Hostname, authPlugin, authStr, require, tokenIssuer, passwordExpiredStr, accountLocked,
passwordHistory, passwordReuseInterval, passwordRequireCurrentStr, failedLoginAttempts, passwordLockTimeDays, userAttributes)
e.appendRow([]any{showStr})
return nil
}
Expand Down
88 changes: 72 additions & 16 deletions pkg/executor/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,19 @@ type SimpleExec struct {
}

type passwordOrLockOptionsInfo struct {
lockAccount string
passwordExpired string
passwordLifetime any
passwordHistory int64
passwordHistoryChange bool
passwordReuseInterval int64
passwordReuseIntervalChange bool
failedLoginAttempts int64
passwordLockTime int64
failedLoginAttemptsChange bool
passwordLockTimeChange bool
lockAccount string
passwordExpired string
passwordLifetime any
passwordHistory int64
passwordHistoryChange bool
passwordReuseInterval int64
passwordReuseIntervalChange bool
failedLoginAttempts int64
passwordLockTime int64
failedLoginAttemptsChange bool
passwordLockTimeChange bool
passwordRequireCurrent string
passwordRequireCurrentChange bool
}

type passwordReuseInfo struct {
Expand Down Expand Up @@ -884,6 +886,15 @@ func (info *passwordOrLockOptionsInfo) loadOptions(plOption []*ast.PasswordOrLoc
case ast.PasswordReuseDefault:
info.passwordReuseInterval = notSpecified
info.passwordReuseIntervalChange = true
case ast.PasswordRequireCurrent:
info.passwordRequireCurrent = "Y"
info.passwordRequireCurrentChange = true
case ast.PasswordRequireCurrentOptional:
info.passwordRequireCurrent = "N"
info.passwordRequireCurrentChange = true
case ast.PasswordRequireCurrentDefault:
info.passwordRequireCurrent = ""
info.passwordRequireCurrentChange = true
}
}
return nil
Expand Down Expand Up @@ -1045,6 +1056,7 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm
passwordLockTimeChange: false,
passwordHistoryChange: false,
passwordReuseIntervalChange: false,
passwordRequireCurrent: "",
}
err = plOptions.loadOptions(s.PasswordOrLockOptions)
if err != nil {
Expand Down Expand Up @@ -1103,7 +1115,7 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm
passwordInit := true
// Get changed user password reuse info.
savePasswdHistory := whetherSavePasswordHistory(plOptions)
sqlTemplate := "INSERT INTO %n.%n (Host, User, authentication_string, plugin, user_attributes, Account_locked, Token_issuer, Password_expired, Password_lifetime, Password_reuse_time, Password_reuse_history) VALUES "
sqlTemplate := "INSERT INTO %n.%n (Host, User, authentication_string, plugin, user_attributes, Account_locked, Token_issuer, Password_expired, Password_lifetime, Password_reuse_time, Password_reuse_history, Password_require_current) VALUES "
valueTemplate := "(%?, %?, %?, %?, %?, %?, %?, %?, %?"

sqlescape.MustFormatSQL(sql, sqlTemplate, mysql.SystemDB, mysql.UserTable)
Expand Down Expand Up @@ -1202,6 +1214,12 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm
} else {
sqlescape.MustFormatSQL(sql, `, %?`, nil)
}
// Current password requirement per-user policy
if plOptions.passwordRequireCurrentChange {
sqlescape.MustFormatSQL(sql, `, %?`, plOptions.passwordRequireCurrent)
} else {
sqlescape.MustFormatSQL(sql, `, %?`, nil)
}
sqlescape.MustFormatSQL(sql, `)`)
// The empty password does not count in the password history and is subject to reuse at any time.
// AuthTiDBAuthToken is the token login method on the cloud,
Expand Down Expand Up @@ -1795,7 +1813,7 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
RequireAuthTokenOptions
)
authTokenOptionHandler := noNeedAuthTokenOptions
currentAuthPlugin, err := privilege.GetPrivilegeManager(e.Ctx()).GetAuthPlugin(spec.User.Username, spec.User.Hostname)
currentAuthPlugin, err := checker.GetAuthPlugin(spec.User.Username, spec.User.Hostname)
if err != nil {
return err
}
Expand All @@ -1808,7 +1826,20 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
value any
}
var fields []alterField

if spec.AuthOpt != nil {
// Only use `REPLACE <pwd>` when changing the current user
dveeden marked this conversation as resolved.
Show resolved Hide resolved
if user != nil &&
(spec.User.Username == user.AuthUsername &&
spec.User.Hostname == user.AuthHostname) {
err = checker.CheckCurrentPassword(spec.User.Username, spec.User.Hostname, spec.AuthOpt.ReplaceString, e.Ctx().GetSessionVars())
if err != nil {
return err
}
} else if spec.AuthOpt.ReplaceString != "" {
return exeerrors.ErrCurrentPasswordNotRequired
}

fields = append(fields, alterField{"password_last_changed=current_timestamp()", nil})
if spec.AuthOpt.AuthPlugin == "" {
spec.AuthOpt.AuthPlugin = currentAuthPlugin
Expand Down Expand Up @@ -1899,6 +1930,17 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
}
}

if plOptions.passwordRequireCurrentChange {
switch plOptions.passwordRequireCurrent {
case "Y":
fields = append(fields, alterField{"Password_require_current = 'Y'", ""})
case "N":
fields = append(fields, alterField{"Password_require_current = 'N'", ""})
case "":
fields = append(fields, alterField{"Password_require_current = NULL", ""})
}
}

passwordLockingInfo, err := readPasswordLockingInfo(ctx, sqlExecutor, spec.User.Username, spec.User.Hostname, &plOptions)
if err != nil {
return err
Expand Down Expand Up @@ -2008,7 +2050,8 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)

if len(privData) > 0 {
sql := new(strings.Builder)
sqlescape.MustFormatSQL(sql, "INSERT INTO %n.%n (Host, User, Priv) VALUES (%?,%?,%?) ON DUPLICATE KEY UPDATE Priv = values(Priv)", mysql.SystemDB, mysql.GlobalPrivTable, spec.User.Hostname, spec.User.Username, string(hack.String(privData)))
sqlescape.MustFormatSQL(sql, "INSERT INTO %n.%n (Host, User, Priv) VALUES (%?,%?,%?) ON DUPLICATE KEY UPDATE Priv = values(Priv)",
mysql.SystemDB, mysql.GlobalPrivTable, spec.User.Hostname, spec.User.Username, string(hack.String(privData)))
_, err := sqlExecutor.ExecuteInternal(ctx, sql.String())
if err != nil {
failedUsers = append(failedUsers, spec.User.String())
Expand Down Expand Up @@ -2474,6 +2517,7 @@ func (e *SimpleExec) executeSetPwd(ctx context.Context, s *ast.SetPwdStmt) error

var u, h string
disableSandboxMode := false
checker := privilege.GetPrivilegeManager(e.Ctx())
if s.User == nil || s.User.CurrentUser {
if e.Ctx().GetSessionVars().User == nil {
return errors.New("Session error is empty")
Expand All @@ -2484,7 +2528,6 @@ func (e *SimpleExec) executeSetPwd(ctx context.Context, s *ast.SetPwdStmt) error
u = s.User.Username
h = s.User.Hostname

checker := privilege.GetPrivilegeManager(e.Ctx())
activeRoles := e.Ctx().GetSessionVars().ActiveRoles
if checker != nil && !checker.RequestVerification(activeRoles, "", "", "", mysql.SuperPriv) {
currUser := e.Ctx().GetSessionVars().User
Expand All @@ -2506,7 +2549,20 @@ func (e *SimpleExec) executeSetPwd(ctx context.Context, s *ast.SetPwdStmt) error
disableSandboxMode = true
}

authplugin, err := privilege.GetPrivilegeManager(e.Ctx()).GetAuthPlugin(u, h)
// Check the current passsword against the policy if the user tries to change its own password
if e.Ctx().GetSessionVars().User != nil &&
(u == e.Ctx().GetSessionVars().User.AuthUsername) &&
(h == e.Ctx().GetSessionVars().User.AuthHostname) {
err = checker.CheckCurrentPassword(u, h, s.ReplaceString, e.Ctx().GetSessionVars())
if err != nil {
return err
}
} else if s.ReplaceString != "" {
// Don't allow the current password when changing another users password
return exeerrors.ErrCurrentPasswordNotRequired
}

authplugin, err := checker.GetAuthPlugin(u, h)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/executor/test/passwordtest/password_management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,15 +318,15 @@ func TestFailedLoginTrackingBasic(t *testing.T) {

tk.MustExec("CREATE USER 'u6'@'localhost' IDENTIFIED BY 'password' FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3;")
tk.MustQuery(" SHOW CREATE USER 'u6'@'localhost';").Check(
testkit.Rows("CREATE USER 'u6'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3"))
testkit.Rows("CREATE USER 'u6'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 3"))

tk.MustExec("CREATE USER 'u7'@'localhost' IDENTIFIED BY 'password';")
tk.MustQuery(" SHOW CREATE USER 'u7'@'localhost';").Check(
testkit.Rows("CREATE USER 'u7'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT"))
testkit.Rows("CREATE USER 'u7'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT"))

tk.MustExec("CREATE USER 'u8'@'localhost' IDENTIFIED BY 'password' FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME UNBOUNDED;")
tk.MustQuery(" SHOW CREATE USER 'u8'@'localhost';").Check(
testkit.Rows("CREATE USER 'u8'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME UNBOUNDED"))
testkit.Rows("CREATE USER 'u8'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME UNBOUNDED"))

tk.MustExec("ALTER USER 'u4'@'localhost' PASSWORD_LOCK_TIME 0 FAILED_LOGIN_ATTEMPTS 0")
tk.MustQuery("select user_attributes from mysql.user where user = 'u4' and host = 'localhost'").Check(testkit.Rows(`<nil>`))
Expand Down
Loading