From ccc82aa74ca9ea308267fb0e031952a9d746b48c Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 11:38:45 +0200 Subject: [PATCH 01/40] *: RENAME USER TODO: Add tests, check coverage and privileges including roles --- executor/simple.go | 141 +++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 3 + planner/core/planbuilder.go | 5 +- session/session.go | 3 +- 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 5ed8ced28af48..7f6d763dd04d0 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -138,6 +138,8 @@ func (e *SimpleExec) Next(ctx context.Context, req *chunk.Chunk) (err error) { err = e.executeAlterUser(x) case *ast.DropUserStmt: err = e.executeDropUser(x) + case *ast.RenameUserStmt: + err = e.executeRenameUser(x) case *ast.SetPwdStmt: err = e.executeSetPwd(x) case *ast.KillStmt: @@ -1048,6 +1050,143 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { return nil } +// Should cover same internal mysql.* tables as DROP USER, so this function is very similar +func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { + // Check privileges. + // Check `CREATE USER` privilege. + // TODO: Also allow UPDATE privilege for the mysql system schema? + if !config.GetGlobalConfig().Security.SkipGrantTable { + checker := privilege.GetPrivilegeManager(e.ctx) + if checker == nil { + return errors.New("miss privilege checker") + } + activeRoles := e.ctx.GetSessionVars().ActiveRoles + if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && + !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { + return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") + } + } + + failedUsers := make([]string, 0, len(s.UserToUsers)) + sysSession, err := e.getSysSession() + defer e.releaseSysSession(sysSession) + if err != nil { + return err + } + sqlExecutor := sysSession.(sqlexec.SQLExecutor) + + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "begin"); err != nil { + return err + } + + for _, userToUser := range s.UserToUsers { + oldUser, newUser := userToUser.OldUser, userToUser.NewUser + exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + if err != nil { + return err + } + if !exists { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + if err != nil { + return err + } + if exists { + failedUsers = append(failedUsers, newUser.String()) + break + } + + // begin a transaction to rename a user. + // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.global_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + continue + } + + // delete privileges from mysql.db + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.tables_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.role_edges + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? + // Since the FROM_USER/HOST is already done + break + } + + // delete relationship from mysql.default_roles + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.global_grants + // TODO: add global_grants into the parser + if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // WASHERE: + //TODO: need update columns_priv once we implement columns_priv functionality. + } + + if len(failedUsers) == 0 { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "commit"); err != nil { + return err + } + } else { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + return ErrCannotUser.GenWithStackByArgs("RENAME USER", strings.Join(failedUsers, ",")) + } + domain.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx) + return nil +} + +func renameUserHostInSystemTable(sqlExecutor sqlexec.SQLExecutor, tableName, usernameColumn, hostColumn string, users *ast.UserToUser) error { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `UPDATE %n.%n SET %n = %?, %n = %? WHERE %n = %? and %n = %?;`, + mysql.SystemDB, tableName, + usernameColumn, users.NewUser.Username, hostColumn, users.NewUser.Hostname, + usernameColumn, users.OldUser.Username, hostColumn, users.OldUser.Hostname) + _, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + return err +} + func (e *SimpleExec) executeDropUser(s *ast.DropUserStmt) error { // Check privileges. // Check `CREATE USER` privilege. @@ -1411,7 +1550,7 @@ func (e *SimpleExec) executeDropStats(s *ast.DropStatsStmt) (err error) { func (e *SimpleExec) autoNewTxn() bool { switch e.Statement.(type) { - case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt: + case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt, *ast.RenameUserStmt: return true } return false diff --git a/go.mod b/go.mod index 4ad29e29e6273..71c5b831e4df8 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.3 // indirect + honnef.co/go/tools v0.1.4 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index d4863b5d8b369..714f15c4882a1 100644 --- a/go.sum +++ b/go.sum @@ -500,6 +500,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -934,6 +935,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= +honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 6fc98bc522508..833baca70db6f 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -639,7 +639,8 @@ func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (Plan, error) { case *ast.BinlogStmt, *ast.FlushStmt, *ast.UseStmt, *ast.BRIEStmt, *ast.BeginStmt, *ast.CommitStmt, *ast.RollbackStmt, *ast.CreateUserStmt, *ast.SetPwdStmt, *ast.AlterInstanceStmt, *ast.GrantStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.RevokeStmt, *ast.KillStmt, *ast.DropStatsStmt, - *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt: + *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt, + *ast.RenameUserStmt: return b.buildSimple(node.(ast.StmtNode)) case ast.DDLNode: return b.buildDDL(ctx, x) @@ -2262,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt: + case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/session/session.go b/session/session.go index 8ca49b6a9e79d..a28cc86fcf8b0 100644 --- a/session/session.go +++ b/session/session.go @@ -2810,7 +2810,8 @@ func logStmt(execStmt *executor.ExecStmt, vars *variable.SessionVars) { switch stmt := execStmt.StmtNode.(type) { case *ast.CreateUserStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.SetPwdStmt, *ast.GrantStmt, *ast.RevokeStmt, *ast.AlterTableStmt, *ast.CreateDatabaseStmt, *ast.CreateIndexStmt, *ast.CreateTableStmt, - *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt: + *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt, + *ast.RenameUserStmt: user := vars.User schemaVersion := vars.TxnCtx.SchemaVersion if ss, ok := execStmt.StmtNode.(ast.SensitiveStmtNode); ok { From b8cbec79f96543fefcf671e360c4cefdbc54eb34 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 14:25:51 +0200 Subject: [PATCH 02/40] Reverted go.mod and go.sum --- go.mod | 2 +- go.sum | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 71c5b831e4df8..4ad29e29e6273 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.1.3 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 714f15c4882a1..d4863b5d8b369 100644 --- a/go.sum +++ b/go.sum @@ -500,7 +500,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -935,8 +934,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= -honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= From d3201566d2ab9356f0c0d6b424ab28245d90843d Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Thu, 6 May 2021 15:13:40 +0200 Subject: [PATCH 03/40] *: fix error reporting for RENAME USER when new user exists --- executor/simple.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/executor/simple.go b/executor/simple.go index 7f6d763dd04d0..2930f4080bafc 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1095,7 +1095,8 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { return err } if exists { - failedUsers = append(failedUsers, newUser.String()) + // MySQL reports the old user, even when the issue is the new user. + failedUsers = append(failedUsers, oldUser.String()) break } From 4bf8565f8aea7f77a99b0de122585ef84290c48f Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 7 May 2021 15:33:09 +0200 Subject: [PATCH 04/40] RENAME USER - wip issue with panic during atomic rename/swap --- executor/simple.go | 70 +- privilege/privileges/privileges_test.go | 1303 +---------------------- session/tidb.go | 2 +- 3 files changed, 72 insertions(+), 1303 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 2930f4080bafc..b045e9c0d19e6 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1052,20 +1052,6 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { // Should cover same internal mysql.* tables as DROP USER, so this function is very similar func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { - // Check privileges. - // Check `CREATE USER` privilege. - // TODO: Also allow UPDATE privilege for the mysql system schema? - if !config.GetGlobalConfig().Security.SkipGrantTable { - checker := privilege.GetPrivilegeManager(e.ctx) - if checker == nil { - return errors.New("miss privilege checker") - } - activeRoles := e.ctx.GetSessionVars().ActiveRoles - if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && - !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { - return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") - } - } failedUsers := make([]string, 0, len(s.UserToUsers)) sysSession, err := e.getSysSession() @@ -1081,22 +1067,22 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { for _, userToUser := range s.UserToUsers { oldUser, newUser := userToUser.OldUser, userToUser.NewUser - exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + exists, err := userExistsInternal(sqlExecutor, oldUser.Username, oldUser.Hostname) if err != nil { return err } if !exists { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" old did not exist") break } - exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + exists, err = userExistsInternal(sqlExecutor, newUser.Username, newUser.Hostname) if err != nil { return err } if exists { // MySQL reports the old user, even when the issue is the new user. - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" new did exist") break } @@ -1104,59 +1090,59 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } - // delete privileges from mysql.global_priv + // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } continue } - // delete privileges from mysql.db + // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") break } - // delete privileges from mysql.tables_priv + // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") break } - // delete relationship from mysql.role_edges + // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? // Since the FROM_USER/HOST is already done break } - // delete relationship from mysql.default_roles + // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") break } - // delete relationship from mysql.global_grants + // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } @@ -1343,6 +1329,24 @@ func userExists(ctx sessionctx.Context, name string, host string) (bool, error) return len(rows) > 0, nil } +// use the same internal executor to read within the same transaction, otherwise same as userExists +func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host string) (bool, error) { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `SELECT * FROM %n.%n WHERE User=%? AND Host=%?;`, mysql.SystemDB, mysql.UserTable, name, host) + recordSet, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + if err != nil { + return false, err + } + req := recordSet.NewChunk() + err = recordSet.Next(context.TODO(), req) + if err != nil { + return false, err + } + rows := req.NumRows() + recordSet.Close() + return rows > 0, nil +} + func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { var u, h string if s.User == nil { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 816fe5a59d0bd..9bafce67e9060 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,34 +14,18 @@ package privileges_test import ( - "bytes" "context" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "fmt" - "net/url" - "os" - "strings" "testing" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" - "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/planner/core" - "github.com/pingcap/tidb/privilege" - "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" - "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" - "github.com/pingcap/tidb/util" - "github.com/pingcap/tidb/util/sem" - "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" - "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -112,1112 +96,6 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } -func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) - mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) - se2 := newSession(c, s.store, s.dbName) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc = privilege.GetPrivilegeManager(se2) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) - mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - mustExec(c, rootSe, `create database test2`) - mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) - mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `use test;`) - _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestIssue22946(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "create database db1;") - mustExec(c, rootSe, "create database db2;") - mustExec(c, rootSe, "use test;") - mustExec(c, rootSe, "create table a(id int);") - mustExec(c, rootSe, "use db1;") - mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") - mustExec(c, rootSe, "use db2;") - mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") - mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") - mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") - mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") - mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") - mustExec(c, rootSe, "flush privileges;") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) - c.Assert(err, IsNil) - mustExec(c, rootSe, "use db1;") - _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) - se2 := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc2 := privilege.GetPrivilegeManager(se2) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) - mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) - mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE r_1, r_2;`) - mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) - - mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) - pc := privilege.GetPrivilegeManager(se) - activeRoles := se.GetSessionVars().ActiveRoles - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - mustExec(c, se, `SET ROLE NONE;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) - mustExec(c, se, `SET ROLE DEFAULT;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) - mustExec(c, se, `SET ROLE ALL;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) - mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) -} - -func (s *testPrivilegeSuite) TestShowGrants(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) - mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) - pc := privilege.GetPrivilegeManager(se) - - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) - - mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) - - // The order of privs is the same with AllGlobalPrivs - mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) - - // All privileges - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // All privileges with grant option - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) - - // Revoke grant option - mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // Add db scope privileges - mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT Index ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Add table scope privileges - mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 4) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, - `GRANT Update ON test.test TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Expected behavior: Usage still exists after revoking all privileges - mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) - - // Usage should not exist after dropping the user - // Which we need privileges to do so! - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `DROP USER 'show'@'localhost'`) - - // This should now return an error - _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, NotNil) - // cant show grants for non-existent - c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) - - // Test SHOW GRANTS with USING roles. - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) - mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles := make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) - mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) - mustExec(c, se, `create table test.b (id int)`) - mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 5) - mustExec(c, se, `DROP ROLE 'r1', 'r2'`) - mustExec(c, se, `DROP USER 'testrole'@'localhost'`) - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) - mustExec(c, se, `GRANT 'r2' TO 'r1'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles = make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) -} - -func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `USE test`) - mustExec(c, se, `CREATE USER 'column'@'%'`) - mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) - mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) - - pc := privilege.GetPrivilegeManager(se) - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) - c.Assert(err, IsNil) - c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") -} - -func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE todrop(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'drop'@'localhost';`) - mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) - - // ctx.GetSessionVars().User = "drop@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM todrop;`) - _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") - c.Assert(err, NotNil) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} - mustExec(c, se, `DROP TABLE todrop;`) -} - -func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { - - se := newSession(c, s.store, s.dbName) - - // high privileged user setting password for other user (passes) - mustExec(c, se, "CREATE USER 'superuser'") - mustExec(c, se, "CREATE USER 'nobodyuser'") - mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") - - // low privileged user trying to set password for other user (fails) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) - - // ctx.GetSessionVars().User = "selectusr@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM test.viewsecurity;`) - mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, "SELECT * FROM test.selectviewsecurity") - mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) - _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") - c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) -} - -func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) - mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) - mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'ar1'@'localhost'") - mustExec(c, se, "drop user 'ar2'@'localhost'") - }() - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `create role r_test1@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'r1'@'localhost';`) - mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) - mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) - mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) - mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) - mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) - mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) - mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) - mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) - mustExec(c, se, "flush privileges") - - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'r1'@'localhost'") - mustExec(c, se, "drop user 'r2'@'localhost'") - mustExec(c, se, "drop user 'r3'@'localhost'") - mustExec(c, se, "drop user 'r4'@'localhost'") - mustExec(c, se, "drop user 'r5'@'localhost'") - mustExec(c, se, "drop user 'r6'@'localhost'") - mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") - mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") - mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") - mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") - mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") - mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") - mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") - mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") - mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") - }() - - // test without ssl or ca - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl without ca - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl with signed but info wrong ca. - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test a all pass case - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { - var url url.URL - err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) - c.Assert(err, IsNil) - cert.URIs = append(cert.URIs, &url) - }) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) - - // test require but give nothing - se.GetSessionVars().TLSConnectionState = nil - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch cipher - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_256_GCM_SHA384) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher - c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test only subject or only issuer - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AZ"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Shijingshang"), - util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester2"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AU"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin2"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test disorder issuer or subject - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch san - c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) - - // test old data and broken data - c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) - -} - -func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { - cert := &x509.Certificate{Issuer: issuer, Subject: subject} - for _, o := range opt { - o(cert) - } - return &tls.ConnectionState{ - VerifiedChains: [][]*x509.Certificate{{cert}}, - CipherSuite: cipher, - } -} - -func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { - - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) - mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) - mustExec(c, se, `CREATE USER u4@localhost;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} - authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) - - se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "drop user 'u1'@'localhost'") - mustExec(c, se1, "drop user 'u2'@'localhost'") - mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") - mustExec(c, se1, "drop user u4@localhost") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "create role 'r1'@'localhost'") - mustExec(c, se2, "create role 'r2'@'localhost'") - mustExec(c, se2, "create role 'r3@example.com'@'localhost'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - - mustExec(c, se1, "drop user 'r1'@'localhost'") - mustExec(c, se1, "drop user 'r2'@'localhost'") - mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") -} - -func (s *testPrivilegeSuite) TestUseDB(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'usesuper'") - mustExec(c, se, "CREATE USER 'usenobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") - // without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(e, NotNil) - // with grant option - se = newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "use mysql") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) - - // try again after privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, IsNil) - - // test `use db` for role. - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE DATABASE app_db`) - mustExec(c, se, `CREATE ROLE 'app_developer'`) - mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) - mustExec(c, se, `CREATE USER 'dev'@'localhost'`) - mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) - mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use app_db") - c.Assert(err, IsNil) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, "CREATE USER 'hasgrant'") - mustExec(c, se, "CREATE USER 'withoutgrant'") - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") - mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") - // Without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - c.Assert(e, NotNil) - // With grant option - se = newSession(c, s.store, s.dbName) - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") -} - -func (s *testPrivilegeSuite) TestSetGlobal(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER setglobal_a@localhost`) - mustExec(c, se, `CREATE USER setglobal_b@localhost`) - mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `set global innodb_commit_concurrency=16`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tcd1, tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `DROP USER tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - - // should pass - mustExec(c, se, `GRANT tcd2 TO tcd1`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE tcd2;`) - mustExec(c, se, `CREATE USER tcd3`) - mustExec(c, se, `DROP USER tcd3`) -} - -func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `DROP USER IF EXISTS tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - mustExec(c, se, `GRANT ALL ON *.* to tcd1`) - mustExec(c, se, `DROP USER IF EXISTS tcd2`) - mustExec(c, se, `CREATE USER tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2`) - mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) - c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") - mustExec(c, se, `DROP USER tcd1, tcd2`) -} - -func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tsct1, tsct2`) - mustExec(c, se, `GRANT select ON mysql.* to tsct2`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SHOW CREATE TABLE mysql.user`) -} - -func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tr_insert`) - mustExec(c, se, `CREATE USER tr_update`) - mustExec(c, se, `CREATE USER tr_delete`) - mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) - mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) - mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) - mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) - - // Restrict the permission to INSERT only. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) - - // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. - _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") - - // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") - - // Plain INSERT should work. - mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) - - // Also check that having DELETE alone is insufficient for REPLACE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") - - // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") -} - -func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'asuper'") - mustExec(c, se, "CREATE USER 'anobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") - mustExec(c, se, "CREATE DATABASE atest") - mustExec(c, se, "use atest") - mustExec(c, se, "CREATE TABLE t1 (a int)") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "analyze table mysql.user") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - - _, err = se.ExecuteInternal(context.Background(), "select * from t1") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") - - // try again after SELECT privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - // Add INSERT privilege and it should work. - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(err, IsNil) - -} - -func (s *testPrivilegeSuite) TestSystemSchema(c *C) { - // This test tests no privilege check for INFORMATION_SCHEMA database. - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `select * from information_schema.tables`) - mustExec(c, se, `select * from information_schema.key_column_usage`) - _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - // Test performance_schema. - mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) - _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) - - // Test metric_schema. - mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) - _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) -} - -func (s *testPrivilegeSuite) TestAdminCommand(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) - mustExec(c, se, `CREATE TABLE t(a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) - mustExec(c, se, `CREATE DATABASE dbexists`) - mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) - - tests := []struct { - stmt string - stmtType string - }{ - { - "SELECT * FROM %s.%s", - "SELECT", - }, - { - "SHOW CREATE TABLE %s.%s", - "SHOW", - }, - { - "DELETE FROM %s.%s WHERE a=0", - "DELETE", - }, - { - "DELETE FROM %s.%s", - "DELETE", - }, - } - - for _, t := range tests { - - _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) - _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) - - // Check the error is the same whether table exists or not. - c.Assert(terror.ErrorEqual(err1, err2), IsTrue) - - // Check it is permission denied, not not found. - c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) - - } - -} - -func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { - // Create file. - path := "/tmp/load_data_priv.csv" - fp, err := os.Create(path) - c.Assert(err, IsNil) - c.Assert(fp, NotNil) - defer func() { - err = fp.Close() - c.Assert(err, IsNil) - err = os.Remove(path) - c.Assert(err, IsNil) - }() - _, err = fp.WriteString("1\n") - c.Assert(err, IsNil) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) - mustExec(c, se, `CREATE TABLE t_load(a int)`) - mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) - message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - -func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") -} - -func (s *testPrivilegeSuite) TestAuthHost(c *C) { - rootSe := newSession(c, s.store, s.dbName) - se := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) - mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") - mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") - c.Assert(err, NotNil) - - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") -} - -func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) - mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - pc := privilege.GetPrivilegeManager(se) - - ret := pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) - - mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 2) - - mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) -} - -func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("create user superadmin") - tk.MustExec("grant all privileges on *.* to 'superadmin'") - - // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 - c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) - - var buf bytes.Buffer - var res bytes.Buffer - buf.WriteString("select ") - i := 0 - for _, priv := range mysql.AllGlobalPrivs { - if i != 0 { - buf.WriteString(", ") - res.WriteString(" ") - } - buf.WriteString(mysql.Priv2UserCol[priv]) - res.WriteString("Y") - i++ - } - buf.WriteString(" from mysql.user where user = 'superadmin'") - tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) -} - -func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) - mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.FieldList("fieldlistt1") - message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -1241,155 +119,42 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } -func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { +func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER notsuper") - mustExec(c, rootSe, "CREATE USER otheruser") - mustExec(c, rootSe, "CREATE ROLE anyrolename") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") - - // test SYSTEM_VARIABLES_ADMIN - _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // test ROLE_ADMIN - _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") - mustExec(c, se, "GRANT anyrolename TO otheruser") - - // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped - mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - - // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN - mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. - // confirm that a dynamic privilege can be inherited from a role. - mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") - mustExec(c, rootSe, "GRANT anyrolename TO notsuper") - - // It's not a default role, this should initially fail: - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, se, "SET ROLE anyrolename") - mustExec(c, se, "SET GLOBAL wait_timeout = 87000") -} - -func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER varuser1") - mustExec(c, rootSe, "CREATE USER varuser2") - mustExec(c, rootSe, "CREATE USER varuser3") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") + mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") + mustExec(c, rootSe, "CREATE USER ru3") + mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") + mustExec(c, rootSe, "CREATE USER ru6@localhost") + mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) - _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { - // This provides an integration test of the tests in util/security/security_test.go - cloudAdminSe := newSession(c, s.store, s.dbName) - mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") - mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") - mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") - mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") - mustExec(c, cloudAdminSe, "CREATE USER uroot") - mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. - c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) - urootSe := newSession(c, s.store, s.dbName) - mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") - c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) - - sem.Enable() - defer sem.Disable() - - _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") - c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") - - _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") - - _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") - c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") - - mustExec(c, cloudAdminSe, "USE metrics_schema") - mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") - mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER uroot1, uroot2, uroot3") - tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot1", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - sem.Enable() - defer sem.Disable() - - // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) - // 36 = a UUID. Normally it is an IP address. - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) - - // That is unless we have the RESTRICTED_TABLES_ADMIN privilege - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot2", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - // flip from is NOT NULL etc - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) -} -func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { - // Without TiKV the status var list does not include tidb_gc_leader_desc - // So we can only test that the dynamic privilege is grantable. - // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' - // and verify if it appears. - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER unostatus, ustatus") - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") - tk.Se.Auth(&auth.UserIdentity{ - Username: "unostatus", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) + c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + // Workaround for *errors.withStack type + errString := err.Error() + c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") + c.Assert(err, IsNil) + _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + c.Assert(err, IsNil) + mustExec(c, rootSe, "DROP USER ru6@localhost") + mustExec(c, rootSe, "DROP USER ru3") + mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") } diff --git a/session/tidb.go b/session/tidb.go index 85732b457f7a6..583c5074e6805 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -239,7 +239,7 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s sessVars := se.sessionVars if meetsErr != nil { if !sessVars.InTxn() { - logutil.BgLogger().Info("rollbackTxn for ddl/autocommit failed") + logutil.BgLogger().Info("rollbackTxn called due to ddl/autocommit failure") se.RollbackTxn(ctx) recordAbortTxnDuration(sessVars) } else if se.txn.Valid() && se.txn.IsPessimistic() && executor.ErrDeadlock.Equal(meetsErr) { From 6f161ba6f2b76666809539ec956660dd5eee5b90 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 15:59:26 +0200 Subject: [PATCH 05/40] Fixed panic due to missing TimeZone in ExecuteInternal. --- executor/mem_reader.go | 9 ++++++++- privilege/privileges/privileges_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/executor/mem_reader.go b/executor/mem_reader.go index f6023c93c5b1a..27ecd6445a3d6 100644 --- a/executor/mem_reader.go +++ b/executor/mem_reader.go @@ -14,6 +14,8 @@ package executor import ( + "time" + "github.com/pingcap/errors" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" @@ -241,7 +243,12 @@ func (m *memTableReader) decodeRowData(handle kv.Handle, value []byte) ([]types. ds := make([]types.Datum, 0, len(m.columns)) for _, col := range m.columns { offset := m.colIDs[col.ID] - d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, m.ctx.GetSessionVars().TimeZone) + loc := m.ctx.GetSessionVars().TimeZone + if loc == nil { + // TODO: Warn and fix in upper layer, due to ctx not set correctly? + loc = time.UTC + } + d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, loc) if err != nil { return nil, err } diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 9bafce67e9060..93ab405208389 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "testing" + "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" @@ -149,10 +150,11 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone + se1.GetSessionVars().TimeZone = time.UTC + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") From 22017fdaa00aaf162c4ae154261133910ba22231 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 19:26:47 +0200 Subject: [PATCH 06/40] RENAME USER fixed unused variable And restored the temporarily removed test case (to speed up development of my patch) --- executor/simple.go | 13 +- privilege/privileges/privileges_test.go | 1277 ++++++++++++++++++++++- 2 files changed, 1283 insertions(+), 7 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index b045e9c0d19e6..94fa7076010b2 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1339,12 +1339,15 @@ func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host strin } req := recordSet.NewChunk() err = recordSet.Next(context.TODO(), req) - if err != nil { - return false, err + var rows int = 0 + if err == nil { + rows = req.NumRows() + } + errClose := recordSet.Close() + if errClose != nil { + return false, errClose } - rows := req.NumRows() - recordSet.Close() - return rows > 0, nil + return rows > 0, err } func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 93ab405208389..61a3db22cb82a 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,19 +14,35 @@ package privileges_test import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "fmt" + "net/url" + "os" + "strings" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/privilege" + "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/sem" + "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" + "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -97,6 +113,1112 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } +func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) + mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) + se2 := newSession(c, s.store, s.dbName) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc = privilege.GetPrivilegeManager(se2) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) + mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + mustExec(c, rootSe, `create database test2`) + mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) + mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `use test;`) + _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestIssue22946(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "create database db1;") + mustExec(c, rootSe, "create database db2;") + mustExec(c, rootSe, "use test;") + mustExec(c, rootSe, "create table a(id int);") + mustExec(c, rootSe, "use db1;") + mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") + mustExec(c, rootSe, "use db2;") + mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") + mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") + mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") + mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") + mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") + mustExec(c, rootSe, "flush privileges;") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) + c.Assert(err, IsNil) + mustExec(c, rootSe, "use db1;") + _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) + se2 := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc2 := privilege.GetPrivilegeManager(se2) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) + mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) + mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE r_1, r_2;`) + mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) + + mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) + pc := privilege.GetPrivilegeManager(se) + activeRoles := se.GetSessionVars().ActiveRoles + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + mustExec(c, se, `SET ROLE NONE;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) + mustExec(c, se, `SET ROLE DEFAULT;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) + mustExec(c, se, `SET ROLE ALL;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) + mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) +} + +func (s *testPrivilegeSuite) TestShowGrants(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) + mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) + pc := privilege.GetPrivilegeManager(se) + + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) + + mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) + + // The order of privs is the same with AllGlobalPrivs + mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) + + // All privileges + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // All privileges with grant option + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) + + // Revoke grant option + mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // Add db scope privileges + mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT Index ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Add table scope privileges + mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 4) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, + `GRANT Update ON test.test TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Expected behavior: Usage still exists after revoking all privileges + mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) + + // Usage should not exist after dropping the user + // Which we need privileges to do so! + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `DROP USER 'show'@'localhost'`) + + // This should now return an error + _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, NotNil) + // cant show grants for non-existent + c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) + + // Test SHOW GRANTS with USING roles. + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) + mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles := make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) + mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) + mustExec(c, se, `create table test.b (id int)`) + mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 5) + mustExec(c, se, `DROP ROLE 'r1', 'r2'`) + mustExec(c, se, `DROP USER 'testrole'@'localhost'`) + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) + mustExec(c, se, `GRANT 'r2' TO 'r1'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles = make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) +} + +func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `USE test`) + mustExec(c, se, `CREATE USER 'column'@'%'`) + mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) + mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) + + pc := privilege.GetPrivilegeManager(se) + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) + c.Assert(err, IsNil) + c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") +} + +func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE todrop(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'drop'@'localhost';`) + mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) + + // ctx.GetSessionVars().User = "drop@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM todrop;`) + _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") + c.Assert(err, NotNil) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} + mustExec(c, se, `DROP TABLE todrop;`) +} + +func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { + + se := newSession(c, s.store, s.dbName) + + // high privileged user setting password for other user (passes) + mustExec(c, se, "CREATE USER 'superuser'") + mustExec(c, se, "CREATE USER 'nobodyuser'") + mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") + + // low privileged user trying to set password for other user (fails) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) + + // ctx.GetSessionVars().User = "selectusr@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM test.viewsecurity;`) + mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, "SELECT * FROM test.selectviewsecurity") + mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) + _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") + c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) +} + +func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) + mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) + mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'ar1'@'localhost'") + mustExec(c, se, "drop user 'ar2'@'localhost'") + }() + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `create role r_test1@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'r1'@'localhost';`) + mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) + mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) + mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) + mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) + mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) + mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) + mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) + mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) + mustExec(c, se, "flush privileges") + + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'r1'@'localhost'") + mustExec(c, se, "drop user 'r2'@'localhost'") + mustExec(c, se, "drop user 'r3'@'localhost'") + mustExec(c, se, "drop user 'r4'@'localhost'") + mustExec(c, se, "drop user 'r5'@'localhost'") + mustExec(c, se, "drop user 'r6'@'localhost'") + mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") + mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") + mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") + mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") + mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") + mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") + mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") + mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") + mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") + }() + + // test without ssl or ca + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl without ca + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl with signed but info wrong ca. + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test a all pass case + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { + var url url.URL + err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) + c.Assert(err, IsNil) + cert.URIs = append(cert.URIs, &url) + }) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) + + // test require but give nothing + se.GetSessionVars().TLSConnectionState = nil + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch cipher + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_256_GCM_SHA384) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher + c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test only subject or only issuer + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AZ"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Shijingshang"), + util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester2"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AU"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin2"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test disorder issuer or subject + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch san + c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) + + // test old data and broken data + c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) + +} + +func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { + cert := &x509.Certificate{Issuer: issuer, Subject: subject} + for _, o := range opt { + o(cert) + } + return &tls.ConnectionState{ + VerifiedChains: [][]*x509.Certificate{{cert}}, + CipherSuite: cipher, + } +} + +func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { + + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) + mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) + mustExec(c, se, `CREATE USER u4@localhost;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} + authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "drop user 'u1'@'localhost'") + mustExec(c, se1, "drop user 'u2'@'localhost'") + mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") + mustExec(c, se1, "drop user u4@localhost") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "create role 'r1'@'localhost'") + mustExec(c, se2, "create role 'r2'@'localhost'") + mustExec(c, se2, "create role 'r3@example.com'@'localhost'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + + mustExec(c, se1, "drop user 'r1'@'localhost'") + mustExec(c, se1, "drop user 'r2'@'localhost'") + mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") +} + +func (s *testPrivilegeSuite) TestUseDB(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'usesuper'") + mustExec(c, se, "CREATE USER 'usenobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") + // without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(e, NotNil) + // with grant option + se = newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "use mysql") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) + + // try again after privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, IsNil) + + // test `use db` for role. + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE DATABASE app_db`) + mustExec(c, se, `CREATE ROLE 'app_developer'`) + mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) + mustExec(c, se, `CREATE USER 'dev'@'localhost'`) + mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) + mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use app_db") + c.Assert(err, IsNil) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, "CREATE USER 'hasgrant'") + mustExec(c, se, "CREATE USER 'withoutgrant'") + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") + mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") + // Without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + c.Assert(e, NotNil) + // With grant option + se = newSession(c, s.store, s.dbName) + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") +} + +func (s *testPrivilegeSuite) TestSetGlobal(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER setglobal_a@localhost`) + mustExec(c, se, `CREATE USER setglobal_b@localhost`) + mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `set global innodb_commit_concurrency=16`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tcd1, tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `DROP USER tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + + // should pass + mustExec(c, se, `GRANT tcd2 TO tcd1`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE tcd2;`) + mustExec(c, se, `CREATE USER tcd3`) + mustExec(c, se, `DROP USER tcd3`) +} + +func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `DROP USER IF EXISTS tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + mustExec(c, se, `GRANT ALL ON *.* to tcd1`) + mustExec(c, se, `DROP USER IF EXISTS tcd2`) + mustExec(c, se, `CREATE USER tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2`) + mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) + c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") + mustExec(c, se, `DROP USER tcd1, tcd2`) +} + +func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tsct1, tsct2`) + mustExec(c, se, `GRANT select ON mysql.* to tsct2`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SHOW CREATE TABLE mysql.user`) +} + +func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tr_insert`) + mustExec(c, se, `CREATE USER tr_update`) + mustExec(c, se, `CREATE USER tr_delete`) + mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) + mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) + mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) + mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) + + // Restrict the permission to INSERT only. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) + + // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. + _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") + + // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") + + // Plain INSERT should work. + mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) + + // Also check that having DELETE alone is insufficient for REPLACE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") + + // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") +} + +func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'asuper'") + mustExec(c, se, "CREATE USER 'anobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") + mustExec(c, se, "CREATE DATABASE atest") + mustExec(c, se, "use atest") + mustExec(c, se, "CREATE TABLE t1 (a int)") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "analyze table mysql.user") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + + _, err = se.ExecuteInternal(context.Background(), "select * from t1") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") + + // try again after SELECT privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + // Add INSERT privilege and it should work. + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(err, IsNil) + +} + +func (s *testPrivilegeSuite) TestSystemSchema(c *C) { + // This test tests no privilege check for INFORMATION_SCHEMA database. + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `select * from information_schema.tables`) + mustExec(c, se, `select * from information_schema.key_column_usage`) + _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + // Test performance_schema. + mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) + _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) + + // Test metric_schema. + mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) + _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) +} + +func (s *testPrivilegeSuite) TestAdminCommand(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) + mustExec(c, se, `CREATE TABLE t(a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) + mustExec(c, se, `CREATE DATABASE dbexists`) + mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) + + tests := []struct { + stmt string + stmtType string + }{ + { + "SELECT * FROM %s.%s", + "SELECT", + }, + { + "SHOW CREATE TABLE %s.%s", + "SHOW", + }, + { + "DELETE FROM %s.%s WHERE a=0", + "DELETE", + }, + { + "DELETE FROM %s.%s", + "DELETE", + }, + } + + for _, t := range tests { + + _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) + _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) + + // Check the error is the same whether table exists or not. + c.Assert(terror.ErrorEqual(err1, err2), IsTrue) + + // Check it is permission denied, not not found. + c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) + + } + +} + +func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { + // Create file. + path := "/tmp/load_data_priv.csv" + fp, err := os.Create(path) + c.Assert(err, IsNil) + c.Assert(fp, NotNil) + defer func() { + err = fp.Close() + c.Assert(err, IsNil) + err = os.Remove(path) + c.Assert(err, IsNil) + }() + _, err = fp.WriteString("1\n") + c.Assert(err, IsNil) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) + mustExec(c, se, `CREATE TABLE t_load(a int)`) + mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) + message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + +func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") +} + +func (s *testPrivilegeSuite) TestAuthHost(c *C) { + rootSe := newSession(c, s.store, s.dbName) + se := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) + mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") + mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") + c.Assert(err, NotNil) + + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") +} + +func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) + mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + pc := privilege.GetPrivilegeManager(se) + + ret := pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) + + mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 2) + + mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) +} + +func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create user superadmin") + tk.MustExec("grant all privileges on *.* to 'superadmin'") + + // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 + c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) + + var buf bytes.Buffer + var res bytes.Buffer + buf.WriteString("select ") + i := 0 + for _, priv := range mysql.AllGlobalPrivs { + if i != 0 { + buf.WriteString(", ") + res.WriteString(" ") + } + buf.WriteString(mysql.Priv2UserCol[priv]) + res.WriteString("Y") + i++ + } + buf.WriteString(" from mysql.user where user = 'superadmin'") + tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) +} + +func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) + mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.FieldList("fieldlistt1") + message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -120,6 +1242,159 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } +func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER notsuper") + mustExec(c, rootSe, "CREATE USER otheruser") + mustExec(c, rootSe, "CREATE ROLE anyrolename") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") + + // test SYSTEM_VARIABLES_ADMIN + _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // test ROLE_ADMIN + _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") + mustExec(c, se, "GRANT anyrolename TO otheruser") + + // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped + mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + + // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN + mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. + // confirm that a dynamic privilege can be inherited from a role. + mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") + mustExec(c, rootSe, "GRANT anyrolename TO notsuper") + + // It's not a default role, this should initially fail: + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, se, "SET ROLE anyrolename") + mustExec(c, se, "SET GLOBAL wait_timeout = 87000") +} + +func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER varuser1") + mustExec(c, rootSe, "CREATE USER varuser2") + mustExec(c, rootSe, "CREATE USER varuser3") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { + // This provides an integration test of the tests in util/security/security_test.go + cloudAdminSe := newSession(c, s.store, s.dbName) + mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") + mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") + mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") + mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") + mustExec(c, cloudAdminSe, "CREATE USER uroot") + mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. + c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) + urootSe := newSession(c, s.store, s.dbName) + mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") + c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) + + sem.Enable() + defer sem.Disable() + + _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") + c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") + + _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") + + _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") + c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") + + mustExec(c, cloudAdminSe, "USE metrics_schema") + mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") + mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER uroot1, uroot2, uroot3") + tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot1", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + sem.Enable() + defer sem.Disable() + + // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) + // 36 = a UUID. Normally it is an IP address. + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) + + // That is unless we have the RESTRICTED_TABLES_ADMIN privilege + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot2", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + // flip from is NOT NULL etc + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { + // Without TiKV the status var list does not include tidb_gc_leader_desc + // So we can only test that the dynamic privilege is grantable. + // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' + // and verify if it appears. + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER unostatus, ustatus") + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") + tk.Se.Auth(&auth.UserIdentity{ + Username: "unostatus", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) +} + func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") @@ -128,9 +1403,7 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") - se1 := newSession(c, s.store, s.dbName) - c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") From 06be996291e952c308911ecc393599475dc85ae8 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 08:31:47 +0200 Subject: [PATCH 07/40] RENAME USER code cleanup --- executor/simple.go | 23 +++++++++++------------ planner/core/planbuilder.go | 2 +- privilege/privileges/privileges_test.go | 12 +++++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 94fa7076010b2..5b1886d0e7891 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1090,13 +1090,13 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.UserTable+" error") break } // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.GlobalPrivTable+" error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } @@ -1105,49 +1105,48 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DBTable+" error") break } // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.TablePrivTable+" error") break } // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") - // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? - // Since the FROM_USER/HOST is already done + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (from) error") break } // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" error") break } // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.global_grants error") break } - // WASHERE: //TODO: need update columns_priv once we implement columns_priv functionality. + // When that is added, please refactor both executeRenameUser and executeDropUser to use an array of tables + // to loop over, so it is easier to maintain. } if len(failedUsers) == 0 { diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 833baca70db6f..f6572176d1292 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2263,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? + case *ast.AlterUserStmt, *ast.RenameUserStmt: err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 61a3db22cb82a..2681c0374847d 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1398,13 +1398,12 @@ func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") - mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") mustExec(c, rootSe, "CREATE USER ru3") - mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") - mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + + // Check privileges (need CREATE USER) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") @@ -1415,20 +1414,27 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err, IsNil) + + // Test a few single rename (both Username and Hostname) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") c.Assert(err, IsNil) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") c.Assert(err, IsNil) + // Including negative tests, i.e. non existing from user and existing to user _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + + // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone se1.GetSessionVars().TimeZone = time.UTC _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) + + // Cleanup mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") From 807bc8624d0b2ffd6738c533ee983fcaea9ac70d Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 10:45:45 +0200 Subject: [PATCH 08/40] RENAME USER added VisitInfo unit test --- planner/core/logical_plan_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 11a116bb4fac8..b7ba34118869f 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -1296,6 +1296,12 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { {mysql.ExtendedPriv, "", "", "", ErrSpecificAccessDenied, false, "BACKUP_ADMIN", true}, }, }, + { + sql: "RENAME USER user1 to user1_tmp", + ans: []visitInfo{ + {mysql.CreateUserPriv, "", "", "", ErrSpecificAccessDenied, false, "", false}, + }, + }, } for _, tt := range tests { From 7e2410198f29d9f9783ce747960d3a17a0d8e46e Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 14:09:14 +0200 Subject: [PATCH 09/40] RENAME USER, minor test case change Just added multi user rename, with failure on the second TO user. --- privilege/privileges/privileges_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 2681c0374847d..ff17574067e64 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1427,6 +1427,10 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone From 14babbd3ddde75c87fa65c46c1825187b44d085c Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 11:38:45 +0200 Subject: [PATCH 10/40] *: RENAME USER TODO: Add tests, check coverage and privileges including roles --- executor/simple.go | 141 +++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 3 + planner/core/planbuilder.go | 5 +- session/session.go | 3 +- 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 65df5ca43117f..be85baa219b95 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -138,6 +138,8 @@ func (e *SimpleExec) Next(ctx context.Context, req *chunk.Chunk) (err error) { err = e.executeAlterUser(x) case *ast.DropUserStmt: err = e.executeDropUser(x) + case *ast.RenameUserStmt: + err = e.executeRenameUser(x) case *ast.SetPwdStmt: err = e.executeSetPwd(x) case *ast.KillStmt: @@ -1048,6 +1050,143 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { return nil } +// Should cover same internal mysql.* tables as DROP USER, so this function is very similar +func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { + // Check privileges. + // Check `CREATE USER` privilege. + // TODO: Also allow UPDATE privilege for the mysql system schema? + if !config.GetGlobalConfig().Security.SkipGrantTable { + checker := privilege.GetPrivilegeManager(e.ctx) + if checker == nil { + return errors.New("miss privilege checker") + } + activeRoles := e.ctx.GetSessionVars().ActiveRoles + if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && + !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { + return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") + } + } + + failedUsers := make([]string, 0, len(s.UserToUsers)) + sysSession, err := e.getSysSession() + defer e.releaseSysSession(sysSession) + if err != nil { + return err + } + sqlExecutor := sysSession.(sqlexec.SQLExecutor) + + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "begin"); err != nil { + return err + } + + for _, userToUser := range s.UserToUsers { + oldUser, newUser := userToUser.OldUser, userToUser.NewUser + exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + if err != nil { + return err + } + if !exists { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + if err != nil { + return err + } + if exists { + failedUsers = append(failedUsers, newUser.String()) + break + } + + // begin a transaction to rename a user. + // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.global_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + continue + } + + // delete privileges from mysql.db + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.tables_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.role_edges + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? + // Since the FROM_USER/HOST is already done + break + } + + // delete relationship from mysql.default_roles + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.global_grants + // TODO: add global_grants into the parser + if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // WASHERE: + //TODO: need update columns_priv once we implement columns_priv functionality. + } + + if len(failedUsers) == 0 { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "commit"); err != nil { + return err + } + } else { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + return ErrCannotUser.GenWithStackByArgs("RENAME USER", strings.Join(failedUsers, ",")) + } + domain.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx) + return nil +} + +func renameUserHostInSystemTable(sqlExecutor sqlexec.SQLExecutor, tableName, usernameColumn, hostColumn string, users *ast.UserToUser) error { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `UPDATE %n.%n SET %n = %?, %n = %? WHERE %n = %? and %n = %?;`, + mysql.SystemDB, tableName, + usernameColumn, users.NewUser.Username, hostColumn, users.NewUser.Hostname, + usernameColumn, users.OldUser.Username, hostColumn, users.OldUser.Hostname) + _, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + return err +} + func (e *SimpleExec) executeDropUser(s *ast.DropUserStmt) error { // Check privileges. // Check `CREATE USER` privilege. @@ -1411,7 +1550,7 @@ func (e *SimpleExec) executeDropStats(s *ast.DropStatsStmt) (err error) { func (e *SimpleExec) autoNewTxn() bool { switch e.Statement.(type) { - case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt: + case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt, *ast.RenameUserStmt: return true } return false diff --git a/go.mod b/go.mod index bf927f9cc55ce..e4080f4eb61d4 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.3 // indirect + honnef.co/go/tools v0.1.4 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 74b4f623789b8..6efd1e46d73bc 100644 --- a/go.sum +++ b/go.sum @@ -500,6 +500,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -934,6 +935,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= +honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 6fc98bc522508..833baca70db6f 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -639,7 +639,8 @@ func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (Plan, error) { case *ast.BinlogStmt, *ast.FlushStmt, *ast.UseStmt, *ast.BRIEStmt, *ast.BeginStmt, *ast.CommitStmt, *ast.RollbackStmt, *ast.CreateUserStmt, *ast.SetPwdStmt, *ast.AlterInstanceStmt, *ast.GrantStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.RevokeStmt, *ast.KillStmt, *ast.DropStatsStmt, - *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt: + *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt, + *ast.RenameUserStmt: return b.buildSimple(node.(ast.StmtNode)) case ast.DDLNode: return b.buildDDL(ctx, x) @@ -2262,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt: + case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/session/session.go b/session/session.go index 94582fbcb6886..a06f6d3057022 100644 --- a/session/session.go +++ b/session/session.go @@ -2823,7 +2823,8 @@ func logStmt(execStmt *executor.ExecStmt, vars *variable.SessionVars) { switch stmt := execStmt.StmtNode.(type) { case *ast.CreateUserStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.SetPwdStmt, *ast.GrantStmt, *ast.RevokeStmt, *ast.AlterTableStmt, *ast.CreateDatabaseStmt, *ast.CreateIndexStmt, *ast.CreateTableStmt, - *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt: + *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt, + *ast.RenameUserStmt: user := vars.User schemaVersion := vars.TxnCtx.SchemaVersion if ss, ok := execStmt.StmtNode.(ast.SensitiveStmtNode); ok { From 4c743042aa96bf281ead22886748058aa71ccb4e Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 14:25:51 +0200 Subject: [PATCH 11/40] Reverted go.mod and go.sum --- go.mod | 2 +- go.sum | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e4080f4eb61d4..bf927f9cc55ce 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.1.3 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 6efd1e46d73bc..74b4f623789b8 100644 --- a/go.sum +++ b/go.sum @@ -500,7 +500,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -935,8 +934,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= -honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= From cd749319a845375936c2ec94cb461ba32c4b58a4 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Thu, 6 May 2021 15:13:40 +0200 Subject: [PATCH 12/40] *: fix error reporting for RENAME USER when new user exists --- executor/simple.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/executor/simple.go b/executor/simple.go index be85baa219b95..92e143e7a574c 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1095,7 +1095,8 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { return err } if exists { - failedUsers = append(failedUsers, newUser.String()) + // MySQL reports the old user, even when the issue is the new user. + failedUsers = append(failedUsers, oldUser.String()) break } From f211e1514dd9959d38ac3a4a595208448fecfa5a Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 7 May 2021 15:33:09 +0200 Subject: [PATCH 13/40] RENAME USER - wip issue with panic during atomic rename/swap --- executor/simple.go | 70 +- privilege/privileges/privileges_test.go | 1303 +---------------------- session/tidb.go | 2 +- 3 files changed, 72 insertions(+), 1303 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 92e143e7a574c..848208cbf32c2 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1052,20 +1052,6 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { // Should cover same internal mysql.* tables as DROP USER, so this function is very similar func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { - // Check privileges. - // Check `CREATE USER` privilege. - // TODO: Also allow UPDATE privilege for the mysql system schema? - if !config.GetGlobalConfig().Security.SkipGrantTable { - checker := privilege.GetPrivilegeManager(e.ctx) - if checker == nil { - return errors.New("miss privilege checker") - } - activeRoles := e.ctx.GetSessionVars().ActiveRoles - if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && - !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { - return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") - } - } failedUsers := make([]string, 0, len(s.UserToUsers)) sysSession, err := e.getSysSession() @@ -1081,22 +1067,22 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { for _, userToUser := range s.UserToUsers { oldUser, newUser := userToUser.OldUser, userToUser.NewUser - exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + exists, err := userExistsInternal(sqlExecutor, oldUser.Username, oldUser.Hostname) if err != nil { return err } if !exists { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" old did not exist") break } - exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + exists, err = userExistsInternal(sqlExecutor, newUser.Username, newUser.Hostname) if err != nil { return err } if exists { // MySQL reports the old user, even when the issue is the new user. - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" new did exist") break } @@ -1104,59 +1090,59 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } - // delete privileges from mysql.global_priv + // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } continue } - // delete privileges from mysql.db + // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") break } - // delete privileges from mysql.tables_priv + // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") break } - // delete relationship from mysql.role_edges + // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? // Since the FROM_USER/HOST is already done break } - // delete relationship from mysql.default_roles + // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") break } - // delete relationship from mysql.global_grants + // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } @@ -1343,6 +1329,24 @@ func userExists(ctx sessionctx.Context, name string, host string) (bool, error) return len(rows) > 0, nil } +// use the same internal executor to read within the same transaction, otherwise same as userExists +func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host string) (bool, error) { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `SELECT * FROM %n.%n WHERE User=%? AND Host=%?;`, mysql.SystemDB, mysql.UserTable, name, host) + recordSet, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + if err != nil { + return false, err + } + req := recordSet.NewChunk() + err = recordSet.Next(context.TODO(), req) + if err != nil { + return false, err + } + rows := req.NumRows() + recordSet.Close() + return rows > 0, nil +} + func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { var u, h string if s.User == nil { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 2efb565b3ed2b..cf8b1f02a3932 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,34 +14,18 @@ package privileges_test import ( - "bytes" "context" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "fmt" - "net/url" - "os" - "strings" "testing" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" - "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/planner/core" - "github.com/pingcap/tidb/privilege" - "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" - "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" - "github.com/pingcap/tidb/util" - "github.com/pingcap/tidb/util/sem" - "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" - "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -112,1112 +96,6 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } -func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) - mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) - se2 := newSession(c, s.store, s.dbName) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc = privilege.GetPrivilegeManager(se2) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) - mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - mustExec(c, rootSe, `create database test2`) - mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) - mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `use test;`) - _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestIssue22946(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "create database db1;") - mustExec(c, rootSe, "create database db2;") - mustExec(c, rootSe, "use test;") - mustExec(c, rootSe, "create table a(id int);") - mustExec(c, rootSe, "use db1;") - mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") - mustExec(c, rootSe, "use db2;") - mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") - mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") - mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") - mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") - mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") - mustExec(c, rootSe, "flush privileges;") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) - c.Assert(err, IsNil) - mustExec(c, rootSe, "use db1;") - _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) - se2 := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc2 := privilege.GetPrivilegeManager(se2) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) - mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) - mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE r_1, r_2;`) - mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) - - mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) - pc := privilege.GetPrivilegeManager(se) - activeRoles := se.GetSessionVars().ActiveRoles - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - mustExec(c, se, `SET ROLE NONE;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) - mustExec(c, se, `SET ROLE DEFAULT;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) - mustExec(c, se, `SET ROLE ALL;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) - mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) -} - -func (s *testPrivilegeSuite) TestShowGrants(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) - mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) - pc := privilege.GetPrivilegeManager(se) - - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) - - mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) - - // The order of privs is the same with AllGlobalPrivs - mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) - - // All privileges - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // All privileges with grant option - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) - - // Revoke grant option - mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // Add db scope privileges - mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT Index ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Add table scope privileges - mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 4) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, - `GRANT Update ON test.test TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Expected behavior: Usage still exists after revoking all privileges - mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) - - // Usage should not exist after dropping the user - // Which we need privileges to do so! - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `DROP USER 'show'@'localhost'`) - - // This should now return an error - _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, NotNil) - // cant show grants for non-existent - c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) - - // Test SHOW GRANTS with USING roles. - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) - mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles := make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) - mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) - mustExec(c, se, `create table test.b (id int)`) - mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 5) - mustExec(c, se, `DROP ROLE 'r1', 'r2'`) - mustExec(c, se, `DROP USER 'testrole'@'localhost'`) - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) - mustExec(c, se, `GRANT 'r2' TO 'r1'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles = make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) -} - -func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `USE test`) - mustExec(c, se, `CREATE USER 'column'@'%'`) - mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) - mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) - - pc := privilege.GetPrivilegeManager(se) - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) - c.Assert(err, IsNil) - c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") -} - -func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE todrop(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'drop'@'localhost';`) - mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) - - // ctx.GetSessionVars().User = "drop@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM todrop;`) - _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") - c.Assert(err, NotNil) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} - mustExec(c, se, `DROP TABLE todrop;`) -} - -func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { - - se := newSession(c, s.store, s.dbName) - - // high privileged user setting password for other user (passes) - mustExec(c, se, "CREATE USER 'superuser'") - mustExec(c, se, "CREATE USER 'nobodyuser'") - mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") - - // low privileged user trying to set password for other user (fails) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) - - // ctx.GetSessionVars().User = "selectusr@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM test.viewsecurity;`) - mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, "SELECT * FROM test.selectviewsecurity") - mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) - _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") - c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) -} - -func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) - mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) - mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'ar1'@'localhost'") - mustExec(c, se, "drop user 'ar2'@'localhost'") - }() - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `create role r_test1@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'r1'@'localhost';`) - mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) - mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) - mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) - mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) - mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) - mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) - mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) - mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) - mustExec(c, se, "flush privileges") - - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'r1'@'localhost'") - mustExec(c, se, "drop user 'r2'@'localhost'") - mustExec(c, se, "drop user 'r3'@'localhost'") - mustExec(c, se, "drop user 'r4'@'localhost'") - mustExec(c, se, "drop user 'r5'@'localhost'") - mustExec(c, se, "drop user 'r6'@'localhost'") - mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") - mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") - mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") - mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") - mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") - mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") - mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") - mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") - mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") - }() - - // test without ssl or ca - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl without ca - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl with signed but info wrong ca. - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test a all pass case - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { - var url url.URL - err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) - c.Assert(err, IsNil) - cert.URIs = append(cert.URIs, &url) - }) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) - - // test require but give nothing - se.GetSessionVars().TLSConnectionState = nil - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch cipher - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_256_GCM_SHA384) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher - c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test only subject or only issuer - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AZ"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Shijingshang"), - util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester2"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AU"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin2"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test disorder issuer or subject - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch san - c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) - - // test old data and broken data - c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) - -} - -func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { - cert := &x509.Certificate{Issuer: issuer, Subject: subject} - for _, o := range opt { - o(cert) - } - return &tls.ConnectionState{ - VerifiedChains: [][]*x509.Certificate{{cert}}, - CipherSuite: cipher, - } -} - -func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { - - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) - mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) - mustExec(c, se, `CREATE USER u4@localhost;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} - authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) - - se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "drop user 'u1'@'localhost'") - mustExec(c, se1, "drop user 'u2'@'localhost'") - mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") - mustExec(c, se1, "drop user u4@localhost") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "create role 'r1'@'localhost'") - mustExec(c, se2, "create role 'r2'@'localhost'") - mustExec(c, se2, "create role 'r3@example.com'@'localhost'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - - mustExec(c, se1, "drop user 'r1'@'localhost'") - mustExec(c, se1, "drop user 'r2'@'localhost'") - mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") -} - -func (s *testPrivilegeSuite) TestUseDB(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'usesuper'") - mustExec(c, se, "CREATE USER 'usenobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") - // without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(e, NotNil) - // with grant option - se = newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "use mysql") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) - - // try again after privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, IsNil) - - // test `use db` for role. - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE DATABASE app_db`) - mustExec(c, se, `CREATE ROLE 'app_developer'`) - mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) - mustExec(c, se, `CREATE USER 'dev'@'localhost'`) - mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) - mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use app_db") - c.Assert(err, IsNil) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, "CREATE USER 'hasgrant'") - mustExec(c, se, "CREATE USER 'withoutgrant'") - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") - mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") - // Without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - c.Assert(e, NotNil) - // With grant option - se = newSession(c, s.store, s.dbName) - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") -} - -func (s *testPrivilegeSuite) TestSetGlobal(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER setglobal_a@localhost`) - mustExec(c, se, `CREATE USER setglobal_b@localhost`) - mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `set global innodb_commit_concurrency=16`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tcd1, tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `DROP USER tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - - // should pass - mustExec(c, se, `GRANT tcd2 TO tcd1`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE tcd2;`) - mustExec(c, se, `CREATE USER tcd3`) - mustExec(c, se, `DROP USER tcd3`) -} - -func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `DROP USER IF EXISTS tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - mustExec(c, se, `GRANT ALL ON *.* to tcd1`) - mustExec(c, se, `DROP USER IF EXISTS tcd2`) - mustExec(c, se, `CREATE USER tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2`) - mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) - c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") - mustExec(c, se, `DROP USER tcd1, tcd2`) -} - -func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tsct1, tsct2`) - mustExec(c, se, `GRANT select ON mysql.* to tsct2`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SHOW CREATE TABLE mysql.user`) -} - -func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tr_insert`) - mustExec(c, se, `CREATE USER tr_update`) - mustExec(c, se, `CREATE USER tr_delete`) - mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) - mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) - mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) - mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) - - // Restrict the permission to INSERT only. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) - - // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. - _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") - - // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") - - // Plain INSERT should work. - mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) - - // Also check that having DELETE alone is insufficient for REPLACE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") - - // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") -} - -func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'asuper'") - mustExec(c, se, "CREATE USER 'anobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") - mustExec(c, se, "CREATE DATABASE atest") - mustExec(c, se, "use atest") - mustExec(c, se, "CREATE TABLE t1 (a int)") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "analyze table mysql.user") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - - _, err = se.ExecuteInternal(context.Background(), "select * from t1") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") - - // try again after SELECT privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - // Add INSERT privilege and it should work. - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(err, IsNil) - -} - -func (s *testPrivilegeSuite) TestSystemSchema(c *C) { - // This test tests no privilege check for INFORMATION_SCHEMA database. - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `select * from information_schema.tables`) - mustExec(c, se, `select * from information_schema.key_column_usage`) - _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - // Test performance_schema. - mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) - _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) - - // Test metric_schema. - mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) - _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) -} - -func (s *testPrivilegeSuite) TestAdminCommand(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) - mustExec(c, se, `CREATE TABLE t(a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) - mustExec(c, se, `CREATE DATABASE dbexists`) - mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) - - tests := []struct { - stmt string - stmtType string - }{ - { - "SELECT * FROM %s.%s", - "SELECT", - }, - { - "SHOW CREATE TABLE %s.%s", - "SHOW", - }, - { - "DELETE FROM %s.%s WHERE a=0", - "DELETE", - }, - { - "DELETE FROM %s.%s", - "DELETE", - }, - } - - for _, t := range tests { - - _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) - _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) - - // Check the error is the same whether table exists or not. - c.Assert(terror.ErrorEqual(err1, err2), IsTrue) - - // Check it is permission denied, not not found. - c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) - - } - -} - -func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { - // Create file. - path := "/tmp/load_data_priv.csv" - fp, err := os.Create(path) - c.Assert(err, IsNil) - c.Assert(fp, NotNil) - defer func() { - err = fp.Close() - c.Assert(err, IsNil) - err = os.Remove(path) - c.Assert(err, IsNil) - }() - _, err = fp.WriteString("1\n") - c.Assert(err, IsNil) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) - mustExec(c, se, `CREATE TABLE t_load(a int)`) - mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) - message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - -func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") -} - -func (s *testPrivilegeSuite) TestAuthHost(c *C) { - rootSe := newSession(c, s.store, s.dbName) - se := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) - mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") - mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") - c.Assert(err, NotNil) - - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") -} - -func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) - mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - pc := privilege.GetPrivilegeManager(se) - - ret := pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) - - mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 2) - - mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) -} - -func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("create user superadmin") - tk.MustExec("grant all privileges on *.* to 'superadmin'") - - // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 - c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) - - var buf bytes.Buffer - var res bytes.Buffer - buf.WriteString("select ") - i := 0 - for _, priv := range mysql.AllGlobalPrivs { - if i != 0 { - buf.WriteString(", ") - res.WriteString(" ") - } - buf.WriteString(mysql.Priv2UserCol[priv]) - res.WriteString("Y") - i++ - } - buf.WriteString(" from mysql.user where user = 'superadmin'") - tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) -} - -func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) - mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.FieldList("fieldlistt1") - message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -1241,157 +119,44 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } -func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { +func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER notsuper") - mustExec(c, rootSe, "CREATE USER otheruser") - mustExec(c, rootSe, "CREATE ROLE anyrolename") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") - - // test SYSTEM_VARIABLES_ADMIN - _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // test ROLE_ADMIN - _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") - mustExec(c, se, "GRANT anyrolename TO otheruser") - - // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped - mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - - // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN - mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. - // confirm that a dynamic privilege can be inherited from a role. - mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") - mustExec(c, rootSe, "GRANT anyrolename TO notsuper") - - // It's not a default role, this should initially fail: - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, se, "SET ROLE anyrolename") - mustExec(c, se, "SET GLOBAL wait_timeout = 87000") -} - -func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER varuser1") - mustExec(c, rootSe, "CREATE USER varuser2") - mustExec(c, rootSe, "CREATE USER varuser3") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") + mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") + mustExec(c, rootSe, "CREATE USER ru3") + mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") + mustExec(c, rootSe, "CREATE USER ru6@localhost") + mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) - _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { - // This provides an integration test of the tests in util/security/security_test.go - cloudAdminSe := newSession(c, s.store, s.dbName) - mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") - mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") - mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") - mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") - mustExec(c, cloudAdminSe, "CREATE USER uroot") - mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. - c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) - urootSe := newSession(c, s.store, s.dbName) - mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") - c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) - - sem.Enable() - defer sem.Disable() - - _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") - c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") - - _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") - - _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") - c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") - - mustExec(c, cloudAdminSe, "USE metrics_schema") - mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") - mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER uroot1, uroot2, uroot3") - tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot1", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - sem.Enable() - defer sem.Disable() - - // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) - // 36 = a UUID. Normally it is an IP address. - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) - - // That is unless we have the RESTRICTED_TABLES_ADMIN privilege - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot2", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - // flip from is NOT NULL etc - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { - // Without TiKV the status var list does not include tidb_gc_leader_desc - // So we can only test that the dynamic privilege is grantable. - // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' - // and verify if it appears. - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER unostatus, ustatus") - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") - tk.Se.Auth(&auth.UserIdentity{ - Username: "unostatus", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) + c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + // Workaround for *errors.withStack type + errString := err.Error() + c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") + c.Assert(err, IsNil) + _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + c.Assert(err, IsNil) + mustExec(c, rootSe, "DROP USER ru6@localhost") + mustExec(c, rootSe, "DROP USER ru3") + mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") } // TestViewDefiner tests that default roles are correctly applied in the algorithm definer diff --git a/session/tidb.go b/session/tidb.go index 85732b457f7a6..583c5074e6805 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -239,7 +239,7 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s sessVars := se.sessionVars if meetsErr != nil { if !sessVars.InTxn() { - logutil.BgLogger().Info("rollbackTxn for ddl/autocommit failed") + logutil.BgLogger().Info("rollbackTxn called due to ddl/autocommit failure") se.RollbackTxn(ctx) recordAbortTxnDuration(sessVars) } else if se.txn.Valid() && se.txn.IsPessimistic() && executor.ErrDeadlock.Equal(meetsErr) { From a8a2161267c420ddb04c9fe7a20f681be04298c8 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 15:59:26 +0200 Subject: [PATCH 14/40] Fixed panic due to missing TimeZone in ExecuteInternal. --- executor/mem_reader.go | 9 ++++++++- privilege/privileges/privileges_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/executor/mem_reader.go b/executor/mem_reader.go index f6023c93c5b1a..27ecd6445a3d6 100644 --- a/executor/mem_reader.go +++ b/executor/mem_reader.go @@ -14,6 +14,8 @@ package executor import ( + "time" + "github.com/pingcap/errors" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" @@ -241,7 +243,12 @@ func (m *memTableReader) decodeRowData(handle kv.Handle, value []byte) ([]types. ds := make([]types.Datum, 0, len(m.columns)) for _, col := range m.columns { offset := m.colIDs[col.ID] - d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, m.ctx.GetSessionVars().TimeZone) + loc := m.ctx.GetSessionVars().TimeZone + if loc == nil { + // TODO: Warn and fix in upper layer, due to ctx not set correctly? + loc = time.UTC + } + d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, loc) if err != nil { return nil, err } diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index cf8b1f02a3932..7a8bfd075492b 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "testing" + "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" @@ -149,10 +150,11 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone + se1.GetSessionVars().TimeZone = time.UTC + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") From 690bea93aca232543104383eba0de1228ca6a694 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 19:26:47 +0200 Subject: [PATCH 15/40] RENAME USER fixed unused variable And restored the temporarily removed test case (to speed up development of my patch) --- executor/simple.go | 13 +- privilege/privileges/privileges_test.go | 1277 ++++++++++++++++++++++- 2 files changed, 1283 insertions(+), 7 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 848208cbf32c2..ae1e2b0e93fe6 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1339,12 +1339,15 @@ func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host strin } req := recordSet.NewChunk() err = recordSet.Next(context.TODO(), req) - if err != nil { - return false, err + var rows int = 0 + if err == nil { + rows = req.NumRows() + } + errClose := recordSet.Close() + if errClose != nil { + return false, errClose } - rows := req.NumRows() - recordSet.Close() - return rows > 0, nil + return rows > 0, err } func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 7a8bfd075492b..a7e34e6754468 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,19 +14,35 @@ package privileges_test import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "fmt" + "net/url" + "os" + "strings" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/privilege" + "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/sem" + "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" + "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -97,6 +113,1112 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } +func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) + mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) + se2 := newSession(c, s.store, s.dbName) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc = privilege.GetPrivilegeManager(se2) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) + mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + mustExec(c, rootSe, `create database test2`) + mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) + mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `use test;`) + _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestIssue22946(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "create database db1;") + mustExec(c, rootSe, "create database db2;") + mustExec(c, rootSe, "use test;") + mustExec(c, rootSe, "create table a(id int);") + mustExec(c, rootSe, "use db1;") + mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") + mustExec(c, rootSe, "use db2;") + mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") + mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") + mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") + mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") + mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") + mustExec(c, rootSe, "flush privileges;") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) + c.Assert(err, IsNil) + mustExec(c, rootSe, "use db1;") + _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) + se2 := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc2 := privilege.GetPrivilegeManager(se2) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) + mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) + mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE r_1, r_2;`) + mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) + + mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) + pc := privilege.GetPrivilegeManager(se) + activeRoles := se.GetSessionVars().ActiveRoles + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + mustExec(c, se, `SET ROLE NONE;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) + mustExec(c, se, `SET ROLE DEFAULT;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) + mustExec(c, se, `SET ROLE ALL;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) + mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) +} + +func (s *testPrivilegeSuite) TestShowGrants(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) + mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) + pc := privilege.GetPrivilegeManager(se) + + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) + + mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) + + // The order of privs is the same with AllGlobalPrivs + mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) + + // All privileges + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // All privileges with grant option + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) + + // Revoke grant option + mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // Add db scope privileges + mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT Index ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Add table scope privileges + mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 4) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, + `GRANT Update ON test.test TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Expected behavior: Usage still exists after revoking all privileges + mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) + + // Usage should not exist after dropping the user + // Which we need privileges to do so! + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `DROP USER 'show'@'localhost'`) + + // This should now return an error + _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, NotNil) + // cant show grants for non-existent + c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) + + // Test SHOW GRANTS with USING roles. + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) + mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles := make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) + mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) + mustExec(c, se, `create table test.b (id int)`) + mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 5) + mustExec(c, se, `DROP ROLE 'r1', 'r2'`) + mustExec(c, se, `DROP USER 'testrole'@'localhost'`) + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) + mustExec(c, se, `GRANT 'r2' TO 'r1'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles = make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) +} + +func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `USE test`) + mustExec(c, se, `CREATE USER 'column'@'%'`) + mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) + mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) + + pc := privilege.GetPrivilegeManager(se) + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) + c.Assert(err, IsNil) + c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") +} + +func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE todrop(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'drop'@'localhost';`) + mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) + + // ctx.GetSessionVars().User = "drop@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM todrop;`) + _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") + c.Assert(err, NotNil) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} + mustExec(c, se, `DROP TABLE todrop;`) +} + +func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { + + se := newSession(c, s.store, s.dbName) + + // high privileged user setting password for other user (passes) + mustExec(c, se, "CREATE USER 'superuser'") + mustExec(c, se, "CREATE USER 'nobodyuser'") + mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") + + // low privileged user trying to set password for other user (fails) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) + + // ctx.GetSessionVars().User = "selectusr@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM test.viewsecurity;`) + mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, "SELECT * FROM test.selectviewsecurity") + mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) + _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") + c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) +} + +func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) + mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) + mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'ar1'@'localhost'") + mustExec(c, se, "drop user 'ar2'@'localhost'") + }() + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `create role r_test1@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'r1'@'localhost';`) + mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) + mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) + mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) + mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) + mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) + mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) + mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) + mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) + mustExec(c, se, "flush privileges") + + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'r1'@'localhost'") + mustExec(c, se, "drop user 'r2'@'localhost'") + mustExec(c, se, "drop user 'r3'@'localhost'") + mustExec(c, se, "drop user 'r4'@'localhost'") + mustExec(c, se, "drop user 'r5'@'localhost'") + mustExec(c, se, "drop user 'r6'@'localhost'") + mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") + mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") + mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") + mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") + mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") + mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") + mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") + mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") + mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") + }() + + // test without ssl or ca + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl without ca + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl with signed but info wrong ca. + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test a all pass case + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { + var url url.URL + err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) + c.Assert(err, IsNil) + cert.URIs = append(cert.URIs, &url) + }) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) + + // test require but give nothing + se.GetSessionVars().TLSConnectionState = nil + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch cipher + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_256_GCM_SHA384) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher + c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test only subject or only issuer + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AZ"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Shijingshang"), + util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester2"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AU"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin2"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test disorder issuer or subject + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch san + c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) + + // test old data and broken data + c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) + +} + +func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { + cert := &x509.Certificate{Issuer: issuer, Subject: subject} + for _, o := range opt { + o(cert) + } + return &tls.ConnectionState{ + VerifiedChains: [][]*x509.Certificate{{cert}}, + CipherSuite: cipher, + } +} + +func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { + + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) + mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) + mustExec(c, se, `CREATE USER u4@localhost;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} + authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "drop user 'u1'@'localhost'") + mustExec(c, se1, "drop user 'u2'@'localhost'") + mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") + mustExec(c, se1, "drop user u4@localhost") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "create role 'r1'@'localhost'") + mustExec(c, se2, "create role 'r2'@'localhost'") + mustExec(c, se2, "create role 'r3@example.com'@'localhost'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + + mustExec(c, se1, "drop user 'r1'@'localhost'") + mustExec(c, se1, "drop user 'r2'@'localhost'") + mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") +} + +func (s *testPrivilegeSuite) TestUseDB(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'usesuper'") + mustExec(c, se, "CREATE USER 'usenobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") + // without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(e, NotNil) + // with grant option + se = newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "use mysql") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) + + // try again after privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, IsNil) + + // test `use db` for role. + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE DATABASE app_db`) + mustExec(c, se, `CREATE ROLE 'app_developer'`) + mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) + mustExec(c, se, `CREATE USER 'dev'@'localhost'`) + mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) + mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use app_db") + c.Assert(err, IsNil) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, "CREATE USER 'hasgrant'") + mustExec(c, se, "CREATE USER 'withoutgrant'") + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") + mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") + // Without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + c.Assert(e, NotNil) + // With grant option + se = newSession(c, s.store, s.dbName) + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") +} + +func (s *testPrivilegeSuite) TestSetGlobal(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER setglobal_a@localhost`) + mustExec(c, se, `CREATE USER setglobal_b@localhost`) + mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `set global innodb_commit_concurrency=16`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tcd1, tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `DROP USER tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + + // should pass + mustExec(c, se, `GRANT tcd2 TO tcd1`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE tcd2;`) + mustExec(c, se, `CREATE USER tcd3`) + mustExec(c, se, `DROP USER tcd3`) +} + +func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `DROP USER IF EXISTS tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + mustExec(c, se, `GRANT ALL ON *.* to tcd1`) + mustExec(c, se, `DROP USER IF EXISTS tcd2`) + mustExec(c, se, `CREATE USER tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2`) + mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) + c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") + mustExec(c, se, `DROP USER tcd1, tcd2`) +} + +func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tsct1, tsct2`) + mustExec(c, se, `GRANT select ON mysql.* to tsct2`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SHOW CREATE TABLE mysql.user`) +} + +func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tr_insert`) + mustExec(c, se, `CREATE USER tr_update`) + mustExec(c, se, `CREATE USER tr_delete`) + mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) + mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) + mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) + mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) + + // Restrict the permission to INSERT only. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) + + // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. + _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") + + // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") + + // Plain INSERT should work. + mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) + + // Also check that having DELETE alone is insufficient for REPLACE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") + + // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") +} + +func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'asuper'") + mustExec(c, se, "CREATE USER 'anobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") + mustExec(c, se, "CREATE DATABASE atest") + mustExec(c, se, "use atest") + mustExec(c, se, "CREATE TABLE t1 (a int)") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "analyze table mysql.user") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + + _, err = se.ExecuteInternal(context.Background(), "select * from t1") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") + + // try again after SELECT privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + // Add INSERT privilege and it should work. + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(err, IsNil) + +} + +func (s *testPrivilegeSuite) TestSystemSchema(c *C) { + // This test tests no privilege check for INFORMATION_SCHEMA database. + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `select * from information_schema.tables`) + mustExec(c, se, `select * from information_schema.key_column_usage`) + _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + // Test performance_schema. + mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) + _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) + + // Test metric_schema. + mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) + _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) +} + +func (s *testPrivilegeSuite) TestAdminCommand(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) + mustExec(c, se, `CREATE TABLE t(a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) + mustExec(c, se, `CREATE DATABASE dbexists`) + mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) + + tests := []struct { + stmt string + stmtType string + }{ + { + "SELECT * FROM %s.%s", + "SELECT", + }, + { + "SHOW CREATE TABLE %s.%s", + "SHOW", + }, + { + "DELETE FROM %s.%s WHERE a=0", + "DELETE", + }, + { + "DELETE FROM %s.%s", + "DELETE", + }, + } + + for _, t := range tests { + + _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) + _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) + + // Check the error is the same whether table exists or not. + c.Assert(terror.ErrorEqual(err1, err2), IsTrue) + + // Check it is permission denied, not not found. + c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) + + } + +} + +func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { + // Create file. + path := "/tmp/load_data_priv.csv" + fp, err := os.Create(path) + c.Assert(err, IsNil) + c.Assert(fp, NotNil) + defer func() { + err = fp.Close() + c.Assert(err, IsNil) + err = os.Remove(path) + c.Assert(err, IsNil) + }() + _, err = fp.WriteString("1\n") + c.Assert(err, IsNil) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) + mustExec(c, se, `CREATE TABLE t_load(a int)`) + mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) + message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + +func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") +} + +func (s *testPrivilegeSuite) TestAuthHost(c *C) { + rootSe := newSession(c, s.store, s.dbName) + se := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) + mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") + mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") + c.Assert(err, NotNil) + + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") +} + +func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) + mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + pc := privilege.GetPrivilegeManager(se) + + ret := pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) + + mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 2) + + mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) +} + +func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create user superadmin") + tk.MustExec("grant all privileges on *.* to 'superadmin'") + + // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 + c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) + + var buf bytes.Buffer + var res bytes.Buffer + buf.WriteString("select ") + i := 0 + for _, priv := range mysql.AllGlobalPrivs { + if i != 0 { + buf.WriteString(", ") + res.WriteString(" ") + } + buf.WriteString(mysql.Priv2UserCol[priv]) + res.WriteString("Y") + i++ + } + buf.WriteString(" from mysql.user where user = 'superadmin'") + tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) +} + +func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) + mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.FieldList("fieldlistt1") + message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -120,6 +1242,159 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } +func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER notsuper") + mustExec(c, rootSe, "CREATE USER otheruser") + mustExec(c, rootSe, "CREATE ROLE anyrolename") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") + + // test SYSTEM_VARIABLES_ADMIN + _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // test ROLE_ADMIN + _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") + mustExec(c, se, "GRANT anyrolename TO otheruser") + + // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped + mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + + // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN + mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. + // confirm that a dynamic privilege can be inherited from a role. + mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") + mustExec(c, rootSe, "GRANT anyrolename TO notsuper") + + // It's not a default role, this should initially fail: + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, se, "SET ROLE anyrolename") + mustExec(c, se, "SET GLOBAL wait_timeout = 87000") +} + +func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER varuser1") + mustExec(c, rootSe, "CREATE USER varuser2") + mustExec(c, rootSe, "CREATE USER varuser3") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { + // This provides an integration test of the tests in util/security/security_test.go + cloudAdminSe := newSession(c, s.store, s.dbName) + mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") + mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") + mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") + mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") + mustExec(c, cloudAdminSe, "CREATE USER uroot") + mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. + c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) + urootSe := newSession(c, s.store, s.dbName) + mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") + c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) + + sem.Enable() + defer sem.Disable() + + _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") + c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") + + _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") + + _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") + c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") + + mustExec(c, cloudAdminSe, "USE metrics_schema") + mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") + mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER uroot1, uroot2, uroot3") + tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot1", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + sem.Enable() + defer sem.Disable() + + // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) + // 36 = a UUID. Normally it is an IP address. + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) + + // That is unless we have the RESTRICTED_TABLES_ADMIN privilege + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot2", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + // flip from is NOT NULL etc + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { + // Without TiKV the status var list does not include tidb_gc_leader_desc + // So we can only test that the dynamic privilege is grantable. + // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' + // and verify if it appears. + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER unostatus, ustatus") + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") + tk.Se.Auth(&auth.UserIdentity{ + Username: "unostatus", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) +} + func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") @@ -128,9 +1403,7 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") - se1 := newSession(c, s.store, s.dbName) - c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") From 5a4ccabb85613aa9c89e79575f51e360e023c1d8 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 08:31:47 +0200 Subject: [PATCH 16/40] RENAME USER code cleanup --- executor/simple.go | 23 +++++++++++------------ planner/core/planbuilder.go | 2 +- privilege/privileges/privileges_test.go | 12 +++++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index ae1e2b0e93fe6..18803643c92fd 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1090,13 +1090,13 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.UserTable+" error") break } // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.GlobalPrivTable+" error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } @@ -1105,49 +1105,48 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DBTable+" error") break } // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.TablePrivTable+" error") break } // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") - // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? - // Since the FROM_USER/HOST is already done + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (from) error") break } // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" error") break } // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.global_grants error") break } - // WASHERE: //TODO: need update columns_priv once we implement columns_priv functionality. + // When that is added, please refactor both executeRenameUser and executeDropUser to use an array of tables + // to loop over, so it is easier to maintain. } if len(failedUsers) == 0 { diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 833baca70db6f..f6572176d1292 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2263,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? + case *ast.AlterUserStmt, *ast.RenameUserStmt: err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index a7e34e6754468..0c682ec5192cf 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1398,13 +1398,12 @@ func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") - mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") mustExec(c, rootSe, "CREATE USER ru3") - mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") - mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + + // Check privileges (need CREATE USER) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") @@ -1415,20 +1414,27 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err, IsNil) + + // Test a few single rename (both Username and Hostname) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") c.Assert(err, IsNil) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") c.Assert(err, IsNil) + // Including negative tests, i.e. non existing from user and existing to user _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + + // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone se1.GetSessionVars().TimeZone = time.UTC _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) + + // Cleanup mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") From 0f99e3d9244f2c32749b05e2866210e3bfdc54ce Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 10:45:45 +0200 Subject: [PATCH 17/40] RENAME USER added VisitInfo unit test --- planner/core/logical_plan_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 11a116bb4fac8..b7ba34118869f 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -1296,6 +1296,12 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { {mysql.ExtendedPriv, "", "", "", ErrSpecificAccessDenied, false, "BACKUP_ADMIN", true}, }, }, + { + sql: "RENAME USER user1 to user1_tmp", + ans: []visitInfo{ + {mysql.CreateUserPriv, "", "", "", ErrSpecificAccessDenied, false, "", false}, + }, + }, } for _, tt := range tests { From f304980782fa4b7b6dd763dca3706aa668197245 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 14:09:14 +0200 Subject: [PATCH 18/40] RENAME USER, minor test case change Just added multi user rename, with failure on the second TO user. --- privilege/privileges/privileges_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 0c682ec5192cf..6f64421984512 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1427,6 +1427,10 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone From d4f3a92fa6ffc3d318971a9f57e9fcc7601413d0 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 11:38:45 +0200 Subject: [PATCH 19/40] *: RENAME USER TODO: Add tests, check coverage and privileges including roles --- executor/simple.go | 141 +++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 2 + planner/core/planbuilder.go | 5 +- session/session.go | 3 +- 5 files changed, 148 insertions(+), 5 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 65df5ca43117f..be85baa219b95 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -138,6 +138,8 @@ func (e *SimpleExec) Next(ctx context.Context, req *chunk.Chunk) (err error) { err = e.executeAlterUser(x) case *ast.DropUserStmt: err = e.executeDropUser(x) + case *ast.RenameUserStmt: + err = e.executeRenameUser(x) case *ast.SetPwdStmt: err = e.executeSetPwd(x) case *ast.KillStmt: @@ -1048,6 +1050,143 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { return nil } +// Should cover same internal mysql.* tables as DROP USER, so this function is very similar +func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { + // Check privileges. + // Check `CREATE USER` privilege. + // TODO: Also allow UPDATE privilege for the mysql system schema? + if !config.GetGlobalConfig().Security.SkipGrantTable { + checker := privilege.GetPrivilegeManager(e.ctx) + if checker == nil { + return errors.New("miss privilege checker") + } + activeRoles := e.ctx.GetSessionVars().ActiveRoles + if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && + !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { + return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") + } + } + + failedUsers := make([]string, 0, len(s.UserToUsers)) + sysSession, err := e.getSysSession() + defer e.releaseSysSession(sysSession) + if err != nil { + return err + } + sqlExecutor := sysSession.(sqlexec.SQLExecutor) + + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "begin"); err != nil { + return err + } + + for _, userToUser := range s.UserToUsers { + oldUser, newUser := userToUser.OldUser, userToUser.NewUser + exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + if err != nil { + return err + } + if !exists { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + if err != nil { + return err + } + if exists { + failedUsers = append(failedUsers, newUser.String()) + break + } + + // begin a transaction to rename a user. + // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.global_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + continue + } + + // delete privileges from mysql.db + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete privileges from mysql.tables_priv + if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.role_edges + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? + // Since the FROM_USER/HOST is already done + break + } + + // delete relationship from mysql.default_roles + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // delete relationship from mysql.global_grants + // TODO: add global_grants into the parser + if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { + failedUsers = append(failedUsers, oldUser.String()) + break + } + + // WASHERE: + //TODO: need update columns_priv once we implement columns_priv functionality. + } + + if len(failedUsers) == 0 { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "commit"); err != nil { + return err + } + } else { + if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { + return err + } + return ErrCannotUser.GenWithStackByArgs("RENAME USER", strings.Join(failedUsers, ",")) + } + domain.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx) + return nil +} + +func renameUserHostInSystemTable(sqlExecutor sqlexec.SQLExecutor, tableName, usernameColumn, hostColumn string, users *ast.UserToUser) error { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `UPDATE %n.%n SET %n = %?, %n = %? WHERE %n = %? and %n = %?;`, + mysql.SystemDB, tableName, + usernameColumn, users.NewUser.Username, hostColumn, users.NewUser.Hostname, + usernameColumn, users.OldUser.Username, hostColumn, users.OldUser.Hostname) + _, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + return err +} + func (e *SimpleExec) executeDropUser(s *ast.DropUserStmt) error { // Check privileges. // Check `CREATE USER` privilege. @@ -1411,7 +1550,7 @@ func (e *SimpleExec) executeDropStats(s *ast.DropStatsStmt) (err error) { func (e *SimpleExec) autoNewTxn() bool { switch e.Statement.(type) { - case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt: + case *ast.CreateUserStmt, *ast.AlterUserStmt, *ast.DropUserStmt, *ast.RenameUserStmt: return true } return false diff --git a/go.mod b/go.mod index bf927f9cc55ce..e4080f4eb61d4 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.3 // indirect + honnef.co/go/tools v0.1.4 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index a3ebad580db64..6efd1e46d73bc 100644 --- a/go.sum +++ b/go.sum @@ -935,6 +935,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= +honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 6fc98bc522508..833baca70db6f 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -639,7 +639,8 @@ func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (Plan, error) { case *ast.BinlogStmt, *ast.FlushStmt, *ast.UseStmt, *ast.BRIEStmt, *ast.BeginStmt, *ast.CommitStmt, *ast.RollbackStmt, *ast.CreateUserStmt, *ast.SetPwdStmt, *ast.AlterInstanceStmt, *ast.GrantStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.RevokeStmt, *ast.KillStmt, *ast.DropStatsStmt, - *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt: + *ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt, + *ast.RenameUserStmt: return b.buildSimple(node.(ast.StmtNode)) case ast.DDLNode: return b.buildDDL(ctx, x) @@ -2262,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt: + case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/session/session.go b/session/session.go index 94582fbcb6886..a06f6d3057022 100644 --- a/session/session.go +++ b/session/session.go @@ -2823,7 +2823,8 @@ func logStmt(execStmt *executor.ExecStmt, vars *variable.SessionVars) { switch stmt := execStmt.StmtNode.(type) { case *ast.CreateUserStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.SetPwdStmt, *ast.GrantStmt, *ast.RevokeStmt, *ast.AlterTableStmt, *ast.CreateDatabaseStmt, *ast.CreateIndexStmt, *ast.CreateTableStmt, - *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt: + *ast.DropDatabaseStmt, *ast.DropIndexStmt, *ast.DropTableStmt, *ast.RenameTableStmt, *ast.TruncateTableStmt, + *ast.RenameUserStmt: user := vars.User schemaVersion := vars.TxnCtx.SchemaVersion if ss, ok := execStmt.StmtNode.(ast.SensitiveStmtNode); ok { From 1e9b1a5c48bfc476751b85b748d9b05dd7399db8 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 14:25:51 +0200 Subject: [PATCH 20/40] Reverted go.mod and go.sum --- go.mod | 2 +- go.sum | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e4080f4eb61d4..bf927f9cc55ce 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.1.3 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 6efd1e46d73bc..74b4f623789b8 100644 --- a/go.sum +++ b/go.sum @@ -500,7 +500,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -935,8 +934,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= -honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= From 9f152d107594570b936810dfe437cdd9b3ae6715 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Thu, 6 May 2021 15:13:40 +0200 Subject: [PATCH 21/40] *: fix error reporting for RENAME USER when new user exists --- executor/simple.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/executor/simple.go b/executor/simple.go index be85baa219b95..92e143e7a574c 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1095,7 +1095,8 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { return err } if exists { - failedUsers = append(failedUsers, newUser.String()) + // MySQL reports the old user, even when the issue is the new user. + failedUsers = append(failedUsers, oldUser.String()) break } From 771e3e0d60b943fe4903dcaad87094950dc6bb00 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 7 May 2021 15:33:09 +0200 Subject: [PATCH 22/40] RENAME USER - wip issue with panic during atomic rename/swap --- executor/simple.go | 70 +- privilege/privileges/privileges_test.go | 1303 +---------------------- session/tidb.go | 2 +- 3 files changed, 72 insertions(+), 1303 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 92e143e7a574c..848208cbf32c2 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1052,20 +1052,6 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { // Should cover same internal mysql.* tables as DROP USER, so this function is very similar func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { - // Check privileges. - // Check `CREATE USER` privilege. - // TODO: Also allow UPDATE privilege for the mysql system schema? - if !config.GetGlobalConfig().Security.SkipGrantTable { - checker := privilege.GetPrivilegeManager(e.ctx) - if checker == nil { - return errors.New("miss privilege checker") - } - activeRoles := e.ctx.GetSessionVars().ActiveRoles - if !checker.RequestVerification(activeRoles, mysql.SystemDB, "", "", mysql.UpdatePriv) && - !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) { - return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER or UPDATE on mysql.*") - } - } failedUsers := make([]string, 0, len(s.UserToUsers)) sysSession, err := e.getSysSession() @@ -1081,22 +1067,22 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { for _, userToUser := range s.UserToUsers { oldUser, newUser := userToUser.OldUser, userToUser.NewUser - exists, err := userExists(e.ctx, oldUser.Username, oldUser.Hostname) + exists, err := userExistsInternal(sqlExecutor, oldUser.Username, oldUser.Hostname) if err != nil { return err } if !exists { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" old did not exist") break } - exists, err = userExists(e.ctx, newUser.Username, newUser.Hostname) + exists, err = userExistsInternal(sqlExecutor, newUser.Username, newUser.Hostname) if err != nil { return err } if exists { // MySQL reports the old user, even when the issue is the new user. - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" new did exist") break } @@ -1104,59 +1090,59 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } - // delete privileges from mysql.global_priv + // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } continue } - // delete privileges from mysql.db + // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") break } - // delete privileges from mysql.tables_priv + // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") break } - // delete relationship from mysql.role_edges + // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? // Since the FROM_USER/HOST is already done break } - // delete relationship from mysql.default_roles + // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") break } - // delete relationship from mysql.global_grants + // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()) + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") break } @@ -1343,6 +1329,24 @@ func userExists(ctx sessionctx.Context, name string, host string) (bool, error) return len(rows) > 0, nil } +// use the same internal executor to read within the same transaction, otherwise same as userExists +func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host string) (bool, error) { + sql := new(strings.Builder) + sqlexec.MustFormatSQL(sql, `SELECT * FROM %n.%n WHERE User=%? AND Host=%?;`, mysql.SystemDB, mysql.UserTable, name, host) + recordSet, err := sqlExecutor.ExecuteInternal(context.TODO(), sql.String()) + if err != nil { + return false, err + } + req := recordSet.NewChunk() + err = recordSet.Next(context.TODO(), req) + if err != nil { + return false, err + } + rows := req.NumRows() + recordSet.Close() + return rows > 0, nil +} + func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { var u, h string if s.User == nil { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 2efb565b3ed2b..cf8b1f02a3932 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,34 +14,18 @@ package privileges_test import ( - "bytes" "context" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "fmt" - "net/url" - "os" - "strings" "testing" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" - "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/planner/core" - "github.com/pingcap/tidb/privilege" - "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" - "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" - "github.com/pingcap/tidb/util" - "github.com/pingcap/tidb/util/sem" - "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" - "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -112,1112 +96,6 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } -func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) - mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) - se2 := newSession(c, s.store, s.dbName) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc = privilege.GetPrivilegeManager(se2) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) - mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - mustExec(c, rootSe, `create database test2`) - mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) - mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `use test;`) - _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestIssue22946(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "create database db1;") - mustExec(c, rootSe, "create database db2;") - mustExec(c, rootSe, "use test;") - mustExec(c, rootSe, "create table a(id int);") - mustExec(c, rootSe, "use db1;") - mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") - mustExec(c, rootSe, "use db2;") - mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") - mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") - mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") - mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") - mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") - mustExec(c, rootSe, "flush privileges;") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) - c.Assert(err, IsNil) - mustExec(c, rootSe, "use db1;") - _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) - se2 := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc2 := privilege.GetPrivilegeManager(se2) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) - mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) - mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE r_1, r_2;`) - mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) - - mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) - pc := privilege.GetPrivilegeManager(se) - activeRoles := se.GetSessionVars().ActiveRoles - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - mustExec(c, se, `SET ROLE NONE;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) - mustExec(c, se, `SET ROLE DEFAULT;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) - mustExec(c, se, `SET ROLE ALL;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) - mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) -} - -func (s *testPrivilegeSuite) TestShowGrants(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) - mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) - pc := privilege.GetPrivilegeManager(se) - - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) - - mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) - - // The order of privs is the same with AllGlobalPrivs - mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) - - // All privileges - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // All privileges with grant option - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) - - // Revoke grant option - mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // Add db scope privileges - mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT Index ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Add table scope privileges - mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 4) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, - `GRANT Update ON test.test TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Expected behavior: Usage still exists after revoking all privileges - mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) - - // Usage should not exist after dropping the user - // Which we need privileges to do so! - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `DROP USER 'show'@'localhost'`) - - // This should now return an error - _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, NotNil) - // cant show grants for non-existent - c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) - - // Test SHOW GRANTS with USING roles. - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) - mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles := make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) - mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) - mustExec(c, se, `create table test.b (id int)`) - mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 5) - mustExec(c, se, `DROP ROLE 'r1', 'r2'`) - mustExec(c, se, `DROP USER 'testrole'@'localhost'`) - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) - mustExec(c, se, `GRANT 'r2' TO 'r1'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles = make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) -} - -func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `USE test`) - mustExec(c, se, `CREATE USER 'column'@'%'`) - mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) - mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) - - pc := privilege.GetPrivilegeManager(se) - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) - c.Assert(err, IsNil) - c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") -} - -func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE todrop(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'drop'@'localhost';`) - mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) - - // ctx.GetSessionVars().User = "drop@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM todrop;`) - _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") - c.Assert(err, NotNil) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} - mustExec(c, se, `DROP TABLE todrop;`) -} - -func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { - - se := newSession(c, s.store, s.dbName) - - // high privileged user setting password for other user (passes) - mustExec(c, se, "CREATE USER 'superuser'") - mustExec(c, se, "CREATE USER 'nobodyuser'") - mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") - - // low privileged user trying to set password for other user (fails) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) - - // ctx.GetSessionVars().User = "selectusr@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM test.viewsecurity;`) - mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, "SELECT * FROM test.selectviewsecurity") - mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) - _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") - c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) -} - -func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) - mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) - mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'ar1'@'localhost'") - mustExec(c, se, "drop user 'ar2'@'localhost'") - }() - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `create role r_test1@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'r1'@'localhost';`) - mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) - mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) - mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) - mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) - mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) - mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) - mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) - mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) - mustExec(c, se, "flush privileges") - - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'r1'@'localhost'") - mustExec(c, se, "drop user 'r2'@'localhost'") - mustExec(c, se, "drop user 'r3'@'localhost'") - mustExec(c, se, "drop user 'r4'@'localhost'") - mustExec(c, se, "drop user 'r5'@'localhost'") - mustExec(c, se, "drop user 'r6'@'localhost'") - mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") - mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") - mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") - mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") - mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") - mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") - mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") - mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") - mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") - }() - - // test without ssl or ca - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl without ca - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl with signed but info wrong ca. - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test a all pass case - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { - var url url.URL - err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) - c.Assert(err, IsNil) - cert.URIs = append(cert.URIs, &url) - }) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) - - // test require but give nothing - se.GetSessionVars().TLSConnectionState = nil - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch cipher - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_256_GCM_SHA384) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher - c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test only subject or only issuer - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AZ"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Shijingshang"), - util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester2"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AU"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin2"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test disorder issuer or subject - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch san - c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) - - // test old data and broken data - c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) - -} - -func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { - cert := &x509.Certificate{Issuer: issuer, Subject: subject} - for _, o := range opt { - o(cert) - } - return &tls.ConnectionState{ - VerifiedChains: [][]*x509.Certificate{{cert}}, - CipherSuite: cipher, - } -} - -func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { - - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) - mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) - mustExec(c, se, `CREATE USER u4@localhost;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} - authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) - - se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "drop user 'u1'@'localhost'") - mustExec(c, se1, "drop user 'u2'@'localhost'") - mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") - mustExec(c, se1, "drop user u4@localhost") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "create role 'r1'@'localhost'") - mustExec(c, se2, "create role 'r2'@'localhost'") - mustExec(c, se2, "create role 'r3@example.com'@'localhost'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - - mustExec(c, se1, "drop user 'r1'@'localhost'") - mustExec(c, se1, "drop user 'r2'@'localhost'") - mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") -} - -func (s *testPrivilegeSuite) TestUseDB(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'usesuper'") - mustExec(c, se, "CREATE USER 'usenobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") - // without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(e, NotNil) - // with grant option - se = newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "use mysql") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) - - // try again after privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, IsNil) - - // test `use db` for role. - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE DATABASE app_db`) - mustExec(c, se, `CREATE ROLE 'app_developer'`) - mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) - mustExec(c, se, `CREATE USER 'dev'@'localhost'`) - mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) - mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use app_db") - c.Assert(err, IsNil) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, "CREATE USER 'hasgrant'") - mustExec(c, se, "CREATE USER 'withoutgrant'") - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") - mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") - // Without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - c.Assert(e, NotNil) - // With grant option - se = newSession(c, s.store, s.dbName) - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") -} - -func (s *testPrivilegeSuite) TestSetGlobal(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER setglobal_a@localhost`) - mustExec(c, se, `CREATE USER setglobal_b@localhost`) - mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `set global innodb_commit_concurrency=16`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tcd1, tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `DROP USER tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - - // should pass - mustExec(c, se, `GRANT tcd2 TO tcd1`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE tcd2;`) - mustExec(c, se, `CREATE USER tcd3`) - mustExec(c, se, `DROP USER tcd3`) -} - -func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `DROP USER IF EXISTS tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - mustExec(c, se, `GRANT ALL ON *.* to tcd1`) - mustExec(c, se, `DROP USER IF EXISTS tcd2`) - mustExec(c, se, `CREATE USER tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2`) - mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) - c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") - mustExec(c, se, `DROP USER tcd1, tcd2`) -} - -func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tsct1, tsct2`) - mustExec(c, se, `GRANT select ON mysql.* to tsct2`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SHOW CREATE TABLE mysql.user`) -} - -func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tr_insert`) - mustExec(c, se, `CREATE USER tr_update`) - mustExec(c, se, `CREATE USER tr_delete`) - mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) - mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) - mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) - mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) - - // Restrict the permission to INSERT only. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) - - // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. - _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") - - // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") - - // Plain INSERT should work. - mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) - - // Also check that having DELETE alone is insufficient for REPLACE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") - - // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") -} - -func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'asuper'") - mustExec(c, se, "CREATE USER 'anobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") - mustExec(c, se, "CREATE DATABASE atest") - mustExec(c, se, "use atest") - mustExec(c, se, "CREATE TABLE t1 (a int)") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "analyze table mysql.user") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - - _, err = se.ExecuteInternal(context.Background(), "select * from t1") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") - - // try again after SELECT privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - // Add INSERT privilege and it should work. - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(err, IsNil) - -} - -func (s *testPrivilegeSuite) TestSystemSchema(c *C) { - // This test tests no privilege check for INFORMATION_SCHEMA database. - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `select * from information_schema.tables`) - mustExec(c, se, `select * from information_schema.key_column_usage`) - _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - // Test performance_schema. - mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) - _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) - - // Test metric_schema. - mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) - _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) -} - -func (s *testPrivilegeSuite) TestAdminCommand(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) - mustExec(c, se, `CREATE TABLE t(a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) - mustExec(c, se, `CREATE DATABASE dbexists`) - mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) - - tests := []struct { - stmt string - stmtType string - }{ - { - "SELECT * FROM %s.%s", - "SELECT", - }, - { - "SHOW CREATE TABLE %s.%s", - "SHOW", - }, - { - "DELETE FROM %s.%s WHERE a=0", - "DELETE", - }, - { - "DELETE FROM %s.%s", - "DELETE", - }, - } - - for _, t := range tests { - - _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) - _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) - - // Check the error is the same whether table exists or not. - c.Assert(terror.ErrorEqual(err1, err2), IsTrue) - - // Check it is permission denied, not not found. - c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) - - } - -} - -func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { - // Create file. - path := "/tmp/load_data_priv.csv" - fp, err := os.Create(path) - c.Assert(err, IsNil) - c.Assert(fp, NotNil) - defer func() { - err = fp.Close() - c.Assert(err, IsNil) - err = os.Remove(path) - c.Assert(err, IsNil) - }() - _, err = fp.WriteString("1\n") - c.Assert(err, IsNil) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) - mustExec(c, se, `CREATE TABLE t_load(a int)`) - mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) - message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - -func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") -} - -func (s *testPrivilegeSuite) TestAuthHost(c *C) { - rootSe := newSession(c, s.store, s.dbName) - se := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) - mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") - mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") - c.Assert(err, NotNil) - - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") -} - -func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) - mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - pc := privilege.GetPrivilegeManager(se) - - ret := pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) - - mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 2) - - mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) -} - -func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("create user superadmin") - tk.MustExec("grant all privileges on *.* to 'superadmin'") - - // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 - c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) - - var buf bytes.Buffer - var res bytes.Buffer - buf.WriteString("select ") - i := 0 - for _, priv := range mysql.AllGlobalPrivs { - if i != 0 { - buf.WriteString(", ") - res.WriteString(" ") - } - buf.WriteString(mysql.Priv2UserCol[priv]) - res.WriteString("Y") - i++ - } - buf.WriteString(" from mysql.user where user = 'superadmin'") - tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) -} - -func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) - mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.FieldList("fieldlistt1") - message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -1241,157 +119,44 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } -func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { +func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER notsuper") - mustExec(c, rootSe, "CREATE USER otheruser") - mustExec(c, rootSe, "CREATE ROLE anyrolename") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") - - // test SYSTEM_VARIABLES_ADMIN - _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // test ROLE_ADMIN - _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") - mustExec(c, se, "GRANT anyrolename TO otheruser") - - // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped - mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - - // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN - mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. - // confirm that a dynamic privilege can be inherited from a role. - mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") - mustExec(c, rootSe, "GRANT anyrolename TO notsuper") - - // It's not a default role, this should initially fail: - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, se, "SET ROLE anyrolename") - mustExec(c, se, "SET GLOBAL wait_timeout = 87000") -} - -func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER varuser1") - mustExec(c, rootSe, "CREATE USER varuser2") - mustExec(c, rootSe, "CREATE USER varuser3") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") + mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") + mustExec(c, rootSe, "CREATE USER ru3") + mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") + mustExec(c, rootSe, "CREATE USER ru6@localhost") + mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) - _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { - // This provides an integration test of the tests in util/security/security_test.go - cloudAdminSe := newSession(c, s.store, s.dbName) - mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") - mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") - mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") - mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") - mustExec(c, cloudAdminSe, "CREATE USER uroot") - mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. - c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) - urootSe := newSession(c, s.store, s.dbName) - mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") - c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) - - sem.Enable() - defer sem.Disable() - - _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") - c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") - - _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") - - _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") - c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") - - mustExec(c, cloudAdminSe, "USE metrics_schema") - mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") - mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER uroot1, uroot2, uroot3") - tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot1", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - sem.Enable() - defer sem.Disable() - - // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) - // 36 = a UUID. Normally it is an IP address. - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) - - // That is unless we have the RESTRICTED_TABLES_ADMIN privilege - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot2", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - // flip from is NOT NULL etc - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { - // Without TiKV the status var list does not include tidb_gc_leader_desc - // So we can only test that the dynamic privilege is grantable. - // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' - // and verify if it appears. - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER unostatus, ustatus") - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") - tk.Se.Auth(&auth.UserIdentity{ - Username: "unostatus", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) + c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + // Workaround for *errors.withStack type + errString := err.Error() + c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") + c.Assert(err, IsNil) + _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + c.Assert(err, IsNil) + mustExec(c, rootSe, "DROP USER ru6@localhost") + mustExec(c, rootSe, "DROP USER ru3") + mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") } // TestViewDefiner tests that default roles are correctly applied in the algorithm definer diff --git a/session/tidb.go b/session/tidb.go index 85732b457f7a6..583c5074e6805 100644 --- a/session/tidb.go +++ b/session/tidb.go @@ -239,7 +239,7 @@ func autoCommitAfterStmt(ctx context.Context, se *session, meetsErr error, sql s sessVars := se.sessionVars if meetsErr != nil { if !sessVars.InTxn() { - logutil.BgLogger().Info("rollbackTxn for ddl/autocommit failed") + logutil.BgLogger().Info("rollbackTxn called due to ddl/autocommit failure") se.RollbackTxn(ctx) recordAbortTxnDuration(sessVars) } else if se.txn.Valid() && se.txn.IsPessimistic() && executor.ErrDeadlock.Equal(meetsErr) { From b172de5704f4919e955cbe4d89eec302af0525b4 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 15:59:26 +0200 Subject: [PATCH 23/40] Fixed panic due to missing TimeZone in ExecuteInternal. --- executor/mem_reader.go | 9 ++++++++- privilege/privileges/privileges_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/executor/mem_reader.go b/executor/mem_reader.go index f6023c93c5b1a..27ecd6445a3d6 100644 --- a/executor/mem_reader.go +++ b/executor/mem_reader.go @@ -14,6 +14,8 @@ package executor import ( + "time" + "github.com/pingcap/errors" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" @@ -241,7 +243,12 @@ func (m *memTableReader) decodeRowData(handle kv.Handle, value []byte) ([]types. ds := make([]types.Datum, 0, len(m.columns)) for _, col := range m.columns { offset := m.colIDs[col.ID] - d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, m.ctx.GetSessionVars().TimeZone) + loc := m.ctx.GetSessionVars().TimeZone + if loc == nil { + // TODO: Warn and fix in upper layer, due to ctx not set correctly? + loc = time.UTC + } + d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, loc) if err != nil { return nil, err } diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index cf8b1f02a3932..7a8bfd075492b 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "testing" + "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" @@ -149,10 +150,11 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone + se1.GetSessionVars().TimeZone = time.UTC + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") From 6f52291cb425bd8b879d7a315176fe2525497731 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 19:26:47 +0200 Subject: [PATCH 24/40] RENAME USER fixed unused variable And restored the temporarily removed test case (to speed up development of my patch) --- executor/simple.go | 13 +- privilege/privileges/privileges_test.go | 1277 ++++++++++++++++++++++- 2 files changed, 1283 insertions(+), 7 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 848208cbf32c2..ae1e2b0e93fe6 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1339,12 +1339,15 @@ func userExistsInternal(sqlExecutor sqlexec.SQLExecutor, name string, host strin } req := recordSet.NewChunk() err = recordSet.Next(context.TODO(), req) - if err != nil { - return false, err + var rows int = 0 + if err == nil { + rows = req.NumRows() + } + errClose := recordSet.Close() + if errClose != nil { + return false, errClose } - rows := req.NumRows() - recordSet.Close() - return rows > 0, nil + return rows > 0, err } func (e *SimpleExec) executeSetPwd(s *ast.SetPwdStmt) error { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 7a8bfd075492b..a7e34e6754468 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,19 +14,35 @@ package privileges_test import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "fmt" + "net/url" + "os" + "strings" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/privilege" + "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/sem" + "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" + "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -97,6 +113,1112 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } +func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) + mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) + se2 := newSession(c, s.store, s.dbName) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc = privilege.GetPrivilegeManager(se2) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) + mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + mustExec(c, rootSe, `create database test2`) + mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) + mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `use test;`) + _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestIssue22946(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "create database db1;") + mustExec(c, rootSe, "create database db2;") + mustExec(c, rootSe, "use test;") + mustExec(c, rootSe, "create table a(id int);") + mustExec(c, rootSe, "use db1;") + mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") + mustExec(c, rootSe, "use db2;") + mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") + mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") + mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") + mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") + mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") + mustExec(c, rootSe, "flush privileges;") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) + c.Assert(err, IsNil) + mustExec(c, rootSe, "use db1;") + _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) + se2 := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc2 := privilege.GetPrivilegeManager(se2) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) + mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) + mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE r_1, r_2;`) + mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) + + mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) + pc := privilege.GetPrivilegeManager(se) + activeRoles := se.GetSessionVars().ActiveRoles + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + mustExec(c, se, `SET ROLE NONE;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) + mustExec(c, se, `SET ROLE DEFAULT;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) + mustExec(c, se, `SET ROLE ALL;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) + mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) +} + +func (s *testPrivilegeSuite) TestShowGrants(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) + mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) + pc := privilege.GetPrivilegeManager(se) + + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) + + mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) + + // The order of privs is the same with AllGlobalPrivs + mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) + + // All privileges + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // All privileges with grant option + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) + + // Revoke grant option + mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // Add db scope privileges + mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT Index ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Add table scope privileges + mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 4) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, + `GRANT Update ON test.test TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Expected behavior: Usage still exists after revoking all privileges + mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) + + // Usage should not exist after dropping the user + // Which we need privileges to do so! + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `DROP USER 'show'@'localhost'`) + + // This should now return an error + _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, NotNil) + // cant show grants for non-existent + c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) + + // Test SHOW GRANTS with USING roles. + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) + mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles := make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) + mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) + mustExec(c, se, `create table test.b (id int)`) + mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 5) + mustExec(c, se, `DROP ROLE 'r1', 'r2'`) + mustExec(c, se, `DROP USER 'testrole'@'localhost'`) + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) + mustExec(c, se, `GRANT 'r2' TO 'r1'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles = make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) +} + +func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `USE test`) + mustExec(c, se, `CREATE USER 'column'@'%'`) + mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) + mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) + + pc := privilege.GetPrivilegeManager(se) + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) + c.Assert(err, IsNil) + c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") +} + +func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE todrop(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'drop'@'localhost';`) + mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) + + // ctx.GetSessionVars().User = "drop@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM todrop;`) + _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") + c.Assert(err, NotNil) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} + mustExec(c, se, `DROP TABLE todrop;`) +} + +func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { + + se := newSession(c, s.store, s.dbName) + + // high privileged user setting password for other user (passes) + mustExec(c, se, "CREATE USER 'superuser'") + mustExec(c, se, "CREATE USER 'nobodyuser'") + mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") + + // low privileged user trying to set password for other user (fails) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) + + // ctx.GetSessionVars().User = "selectusr@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM test.viewsecurity;`) + mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, "SELECT * FROM test.selectviewsecurity") + mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) + _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") + c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) +} + +func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) + mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) + mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'ar1'@'localhost'") + mustExec(c, se, "drop user 'ar2'@'localhost'") + }() + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `create role r_test1@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'r1'@'localhost';`) + mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) + mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) + mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) + mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) + mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) + mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) + mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) + mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) + mustExec(c, se, "flush privileges") + + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'r1'@'localhost'") + mustExec(c, se, "drop user 'r2'@'localhost'") + mustExec(c, se, "drop user 'r3'@'localhost'") + mustExec(c, se, "drop user 'r4'@'localhost'") + mustExec(c, se, "drop user 'r5'@'localhost'") + mustExec(c, se, "drop user 'r6'@'localhost'") + mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") + mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") + mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") + mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") + mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") + mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") + mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") + mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") + mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") + }() + + // test without ssl or ca + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl without ca + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl with signed but info wrong ca. + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test a all pass case + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { + var url url.URL + err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) + c.Assert(err, IsNil) + cert.URIs = append(cert.URIs, &url) + }) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) + + // test require but give nothing + se.GetSessionVars().TLSConnectionState = nil + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch cipher + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_256_GCM_SHA384) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher + c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test only subject or only issuer + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AZ"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Shijingshang"), + util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester2"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AU"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin2"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test disorder issuer or subject + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch san + c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) + + // test old data and broken data + c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) + +} + +func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { + cert := &x509.Certificate{Issuer: issuer, Subject: subject} + for _, o := range opt { + o(cert) + } + return &tls.ConnectionState{ + VerifiedChains: [][]*x509.Certificate{{cert}}, + CipherSuite: cipher, + } +} + +func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { + + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) + mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) + mustExec(c, se, `CREATE USER u4@localhost;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} + authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "drop user 'u1'@'localhost'") + mustExec(c, se1, "drop user 'u2'@'localhost'") + mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") + mustExec(c, se1, "drop user u4@localhost") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "create role 'r1'@'localhost'") + mustExec(c, se2, "create role 'r2'@'localhost'") + mustExec(c, se2, "create role 'r3@example.com'@'localhost'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + + mustExec(c, se1, "drop user 'r1'@'localhost'") + mustExec(c, se1, "drop user 'r2'@'localhost'") + mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") +} + +func (s *testPrivilegeSuite) TestUseDB(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'usesuper'") + mustExec(c, se, "CREATE USER 'usenobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") + // without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(e, NotNil) + // with grant option + se = newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "use mysql") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) + + // try again after privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, IsNil) + + // test `use db` for role. + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE DATABASE app_db`) + mustExec(c, se, `CREATE ROLE 'app_developer'`) + mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) + mustExec(c, se, `CREATE USER 'dev'@'localhost'`) + mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) + mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use app_db") + c.Assert(err, IsNil) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, "CREATE USER 'hasgrant'") + mustExec(c, se, "CREATE USER 'withoutgrant'") + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") + mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") + // Without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + c.Assert(e, NotNil) + // With grant option + se = newSession(c, s.store, s.dbName) + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") +} + +func (s *testPrivilegeSuite) TestSetGlobal(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER setglobal_a@localhost`) + mustExec(c, se, `CREATE USER setglobal_b@localhost`) + mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `set global innodb_commit_concurrency=16`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tcd1, tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `DROP USER tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + + // should pass + mustExec(c, se, `GRANT tcd2 TO tcd1`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE tcd2;`) + mustExec(c, se, `CREATE USER tcd3`) + mustExec(c, se, `DROP USER tcd3`) +} + +func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `DROP USER IF EXISTS tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + mustExec(c, se, `GRANT ALL ON *.* to tcd1`) + mustExec(c, se, `DROP USER IF EXISTS tcd2`) + mustExec(c, se, `CREATE USER tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2`) + mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) + c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") + mustExec(c, se, `DROP USER tcd1, tcd2`) +} + +func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tsct1, tsct2`) + mustExec(c, se, `GRANT select ON mysql.* to tsct2`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SHOW CREATE TABLE mysql.user`) +} + +func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tr_insert`) + mustExec(c, se, `CREATE USER tr_update`) + mustExec(c, se, `CREATE USER tr_delete`) + mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) + mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) + mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) + mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) + + // Restrict the permission to INSERT only. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) + + // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. + _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") + + // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") + + // Plain INSERT should work. + mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) + + // Also check that having DELETE alone is insufficient for REPLACE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") + + // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") +} + +func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'asuper'") + mustExec(c, se, "CREATE USER 'anobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") + mustExec(c, se, "CREATE DATABASE atest") + mustExec(c, se, "use atest") + mustExec(c, se, "CREATE TABLE t1 (a int)") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "analyze table mysql.user") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + + _, err = se.ExecuteInternal(context.Background(), "select * from t1") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") + + // try again after SELECT privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + // Add INSERT privilege and it should work. + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(err, IsNil) + +} + +func (s *testPrivilegeSuite) TestSystemSchema(c *C) { + // This test tests no privilege check for INFORMATION_SCHEMA database. + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `select * from information_schema.tables`) + mustExec(c, se, `select * from information_schema.key_column_usage`) + _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + // Test performance_schema. + mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) + _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) + + // Test metric_schema. + mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) + _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) +} + +func (s *testPrivilegeSuite) TestAdminCommand(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) + mustExec(c, se, `CREATE TABLE t(a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) + mustExec(c, se, `CREATE DATABASE dbexists`) + mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) + + tests := []struct { + stmt string + stmtType string + }{ + { + "SELECT * FROM %s.%s", + "SELECT", + }, + { + "SHOW CREATE TABLE %s.%s", + "SHOW", + }, + { + "DELETE FROM %s.%s WHERE a=0", + "DELETE", + }, + { + "DELETE FROM %s.%s", + "DELETE", + }, + } + + for _, t := range tests { + + _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) + _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) + + // Check the error is the same whether table exists or not. + c.Assert(terror.ErrorEqual(err1, err2), IsTrue) + + // Check it is permission denied, not not found. + c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) + + } + +} + +func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { + // Create file. + path := "/tmp/load_data_priv.csv" + fp, err := os.Create(path) + c.Assert(err, IsNil) + c.Assert(fp, NotNil) + defer func() { + err = fp.Close() + c.Assert(err, IsNil) + err = os.Remove(path) + c.Assert(err, IsNil) + }() + _, err = fp.WriteString("1\n") + c.Assert(err, IsNil) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) + mustExec(c, se, `CREATE TABLE t_load(a int)`) + mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) + message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + +func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") +} + +func (s *testPrivilegeSuite) TestAuthHost(c *C) { + rootSe := newSession(c, s.store, s.dbName) + se := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) + mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") + mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") + c.Assert(err, NotNil) + + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") +} + +func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) + mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + pc := privilege.GetPrivilegeManager(se) + + ret := pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) + + mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 2) + + mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) +} + +func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create user superadmin") + tk.MustExec("grant all privileges on *.* to 'superadmin'") + + // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 + c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) + + var buf bytes.Buffer + var res bytes.Buffer + buf.WriteString("select ") + i := 0 + for _, priv := range mysql.AllGlobalPrivs { + if i != 0 { + buf.WriteString(", ") + res.WriteString(" ") + } + buf.WriteString(mysql.Priv2UserCol[priv]) + res.WriteString("Y") + i++ + } + buf.WriteString(" from mysql.user where user = 'superadmin'") + tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) +} + +func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) + mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.FieldList("fieldlistt1") + message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -120,6 +1242,159 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } +func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER notsuper") + mustExec(c, rootSe, "CREATE USER otheruser") + mustExec(c, rootSe, "CREATE ROLE anyrolename") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") + + // test SYSTEM_VARIABLES_ADMIN + _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // test ROLE_ADMIN + _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") + mustExec(c, se, "GRANT anyrolename TO otheruser") + + // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped + mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + + // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN + mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. + // confirm that a dynamic privilege can be inherited from a role. + mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") + mustExec(c, rootSe, "GRANT anyrolename TO notsuper") + + // It's not a default role, this should initially fail: + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, se, "SET ROLE anyrolename") + mustExec(c, se, "SET GLOBAL wait_timeout = 87000") +} + +func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER varuser1") + mustExec(c, rootSe, "CREATE USER varuser2") + mustExec(c, rootSe, "CREATE USER varuser3") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { + // This provides an integration test of the tests in util/security/security_test.go + cloudAdminSe := newSession(c, s.store, s.dbName) + mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") + mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") + mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") + mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") + mustExec(c, cloudAdminSe, "CREATE USER uroot") + mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. + c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) + urootSe := newSession(c, s.store, s.dbName) + mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") + c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) + + sem.Enable() + defer sem.Disable() + + _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") + c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") + + _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") + + _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") + c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") + + mustExec(c, cloudAdminSe, "USE metrics_schema") + mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") + mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER uroot1, uroot2, uroot3") + tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot1", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + sem.Enable() + defer sem.Disable() + + // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) + // 36 = a UUID. Normally it is an IP address. + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) + + // That is unless we have the RESTRICTED_TABLES_ADMIN privilege + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot2", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + // flip from is NOT NULL etc + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { + // Without TiKV the status var list does not include tidb_gc_leader_desc + // So we can only test that the dynamic privilege is grantable. + // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' + // and verify if it appears. + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER unostatus, ustatus") + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") + tk.Se.Auth(&auth.UserIdentity{ + Username: "unostatus", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) +} + func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") @@ -128,9 +1403,7 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") - se1 := newSession(c, s.store, s.dbName) - c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") From 0d01430fda6cb27fcde67935c943487ea29c1fcc Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 08:31:47 +0200 Subject: [PATCH 25/40] RENAME USER code cleanup --- executor/simple.go | 23 +++++++++++------------ planner/core/planbuilder.go | 2 +- privilege/privileges/privileges_test.go | 12 +++++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index ae1e2b0e93fe6..18803643c92fd 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1090,13 +1090,13 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.UserTable+" error") break } // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.globalprivtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.GlobalPrivTable+" error") if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } @@ -1105,49 +1105,48 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.dbtable error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DBTable+" error") break } // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.table-priv-table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.TablePrivTable+" error") break } // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (to) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (to) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.role_edge_table (from) error") - // Should we really break here as in DROP USER? it should all be rolled back right for this Old to New rename? - // Since the FROM_USER/HOST is already done + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (from) error") break } // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table (default role user) error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" (default role user) error") break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.default_role_table error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" error") break } // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.user error") + failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.global_grants error") break } - // WASHERE: //TODO: need update columns_priv once we implement columns_priv functionality. + // When that is added, please refactor both executeRenameUser and executeDropUser to use an array of tables + // to loop over, so it is easier to maintain. } if len(failedUsers) == 0 { diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 833baca70db6f..f6572176d1292 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2263,7 +2263,7 @@ func (b *PlanBuilder) buildSimple(node ast.StmtNode) (Plan, error) { case *ast.AlterInstanceStmt: err := ErrSpecificAccessDenied.GenWithStack("SUPER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err) - case *ast.AlterUserStmt, *ast.RenameUserStmt: // TODO: Is RenameUserStmt needed here? + case *ast.AlterUserStmt, *ast.RenameUserStmt: err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER") b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err) case *ast.GrantStmt: diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index a7e34e6754468..0c682ec5192cf 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1398,13 +1398,12 @@ func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") - mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") mustExec(c, rootSe, "CREATE USER ru3") - mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") - mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + + // Check privileges (need CREATE USER) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") @@ -1415,20 +1414,27 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err, IsNil) + + // Test a few single rename (both Username and Hostname) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") c.Assert(err, IsNil) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") c.Assert(err, IsNil) + // Including negative tests, i.e. non existing from user and existing to user _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + + // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone se1.GetSessionVars().TimeZone = time.UTC _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) + + // Cleanup mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") From 6e7056d2c5ebf2912d3f1cde72f5ac8229bde8d3 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 10:45:45 +0200 Subject: [PATCH 26/40] RENAME USER added VisitInfo unit test --- planner/core/logical_plan_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 11a116bb4fac8..b7ba34118869f 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -1296,6 +1296,12 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { {mysql.ExtendedPriv, "", "", "", ErrSpecificAccessDenied, false, "BACKUP_ADMIN", true}, }, }, + { + sql: "RENAME USER user1 to user1_tmp", + ans: []visitInfo{ + {mysql.CreateUserPriv, "", "", "", ErrSpecificAccessDenied, false, "", false}, + }, + }, } for _, tt := range tests { From 03c31b12f91fc93c9cac59bb8aecae04480ab4eb Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 14:09:14 +0200 Subject: [PATCH 27/40] RENAME USER, minor test case change Just added multi user rename, with failure on the second TO user. --- privilege/privileges/privileges_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 0c682ec5192cf..6f64421984512 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1427,6 +1427,10 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone From 4f3beed8e9ef93035efd3482e6d4b59e7736472a Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 11:38:45 +0200 Subject: [PATCH 28/40] *: RENAME USER TODO: Add tests, check coverage and privileges including roles --- executor/simple.go | 5 +---- go.mod | 2 +- go.sum | 3 +++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index 18803643c92fd..b3b3a39d80eff 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1097,10 +1097,7 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.GlobalPrivTable+" error") - if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { - return err - } - continue + break } // rename privileges from mysql.db diff --git a/go.mod b/go.mod index bf927f9cc55ce..e4080f4eb61d4 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.3 // indirect + honnef.co/go/tools v0.1.4 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 74b4f623789b8..6efd1e46d73bc 100644 --- a/go.sum +++ b/go.sum @@ -500,6 +500,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -934,6 +935,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= +honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= From b1f5e51f023b8971677aee2fc8cbef28312d24e0 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Wed, 5 May 2021 14:25:51 +0200 Subject: [PATCH 29/40] Reverted go.mod and go.sum --- go.mod | 2 +- go.sum | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e4080f4eb61d4..bf927f9cc55ce 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.1.3 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 6efd1e46d73bc..74b4f623789b8 100644 --- a/go.sum +++ b/go.sum @@ -500,7 +500,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= @@ -935,8 +934,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= -honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= From 8fcd83783b7cb4917149bf21221f466a96c2ec0c Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 7 May 2021 15:33:09 +0200 Subject: [PATCH 30/40] RENAME USER - wip issue with panic during atomic rename/swap --- privilege/privileges/privileges_test.go | 1303 +---------------------- 1 file changed, 34 insertions(+), 1269 deletions(-) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 6f64421984512..b41ec6bfc6f9e 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,35 +14,19 @@ package privileges_test import ( - "bytes" "context" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" "fmt" - "net/url" - "os" - "strings" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" - "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" - "github.com/pingcap/tidb/planner/core" - "github.com/pingcap/tidb/privilege" - "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" - "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" - "github.com/pingcap/tidb/util" - "github.com/pingcap/tidb/util/sem" - "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" - "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -113,1112 +97,6 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } -func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) - mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) - se2 := newSession(c, s.store, s.dbName) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc = privilege.GetPrivilegeManager(se2) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) - mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - mustExec(c, rootSe, `create database test2`) - mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) - mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `use test;`) - _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestIssue22946(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "create database db1;") - mustExec(c, rootSe, "create database db2;") - mustExec(c, rootSe, "use test;") - mustExec(c, rootSe, "create table a(id int);") - mustExec(c, rootSe, "use db1;") - mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") - mustExec(c, rootSe, "use db2;") - mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") - mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") - mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") - mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") - mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") - mustExec(c, rootSe, "flush privileges;") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) - c.Assert(err, IsNil) - mustExec(c, rootSe, "use db1;") - _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) - mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) - - mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) - se2 := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) - c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) - pc2 := privilege.GetPrivilegeManager(se2) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) - - mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) - c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) - mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) - - se := newSession(c, s.store, s.dbName) - activeRoles := make([]*auth.RoleIdentity, 0) - c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) - - mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) - mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE r_1, r_2;`) - mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) - - mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) - pc := privilege.GetPrivilegeManager(se) - activeRoles := se.GetSessionVars().ActiveRoles - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) - mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) - c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) - - mustExec(c, se, `SET ROLE NONE;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) - mustExec(c, se, `SET ROLE DEFAULT;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) - mustExec(c, se, `SET ROLE ALL;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) - mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) - c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) -} - -func (s *testPrivilegeSuite) TestShowGrants(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) - mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) - pc := privilege.GetPrivilegeManager(se) - - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) - - mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) - - // The order of privs is the same with AllGlobalPrivs - mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) - - // All privileges - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // All privileges with grant option - mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) - - // Revoke grant option - mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) - - // Add db scope privileges - mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT Index ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Add table scope privileges - mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 4) - expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, - `GRANT Select ON test.* TO 'show'@'localhost'`, - `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, - `GRANT Update ON test.test TO 'show'@'localhost'`} - c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) - - // Expected behavior: Usage still exists after revoking all privileges - mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) - mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 1) - c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) - - // Usage should not exist after dropping the user - // Which we need privileges to do so! - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `DROP USER 'show'@'localhost'`) - - // This should now return an error - _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) - c.Assert(err, NotNil) - // cant show grants for non-existent - c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) - - // Test SHOW GRANTS with USING roles. - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) - mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles := make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) - mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) - mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) - mustExec(c, se, `create table test.b (id int)`) - mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 5) - mustExec(c, se, `DROP ROLE 'r1', 'r2'`) - mustExec(c, se, `DROP USER 'testrole'@'localhost'`) - mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) - mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) - mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) - mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) - mustExec(c, se, `GRANT 'r2' TO 'r1'`) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 2) - roles = make([]*auth.RoleIdentity, 0) - roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) - gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) - c.Assert(err, IsNil) - c.Assert(gs, HasLen, 3) -} - -func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `USE test`) - mustExec(c, se, `CREATE USER 'column'@'%'`) - mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) - mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) - - pc := privilege.GetPrivilegeManager(se) - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) - c.Assert(err, IsNil) - c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") -} - -func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE todrop(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'drop'@'localhost';`) - mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) - - // ctx.GetSessionVars().User = "drop@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM todrop;`) - _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") - c.Assert(err, NotNil) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} - mustExec(c, se, `DROP TABLE todrop;`) -} - -func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { - - se := newSession(c, s.store, s.dbName) - - // high privileged user setting password for other user (passes) - mustExec(c, se, "CREATE USER 'superuser'") - mustExec(c, se, "CREATE USER 'nobodyuser'") - mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") - mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") - - // low privileged user trying to set password for other user (fails) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - ctx, _ := se.(sessionctx.Context) - mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) - // ctx.GetSessionVars().User = "root@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) - mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) - - // ctx.GetSessionVars().User = "selectusr@localhost" - c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `SELECT * FROM test.viewsecurity;`) - mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) - - se = newSession(c, s.store, s.dbName) - ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} - mustExec(c, se, "SELECT * FROM test.selectviewsecurity") - mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) - _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") - c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) -} - -func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) - mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) - mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'ar1'@'localhost'") - mustExec(c, se, "drop user 'ar2'@'localhost'") - }() - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `create role r_test1@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'r1'@'localhost';`) - mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) - mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) - mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) - mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) - mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) - mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) - mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) - mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' - subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) - mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") - mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) - mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) - mustExec(c, se, "flush privileges") - - defer func() { - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "drop user 'r1'@'localhost'") - mustExec(c, se, "drop user 'r2'@'localhost'") - mustExec(c, se, "drop user 'r3'@'localhost'") - mustExec(c, se, "drop user 'r4'@'localhost'") - mustExec(c, se, "drop user 'r5'@'localhost'") - mustExec(c, se, "drop user 'r6'@'localhost'") - mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") - mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") - mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") - mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") - mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") - mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") - mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") - mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") - mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") - }() - - // test without ssl or ca - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl without ca - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test use ssl with signed but info wrong ca. - se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test a all pass case - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { - var url url.URL - err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) - c.Assert(err, IsNil) - cert.URIs = append(cert.URIs, &url) - }) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) - - // test require but give nothing - se.GetSessionVars().TLSConnectionState = nil - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch cipher - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_256_GCM_SHA384) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher - c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test only subject or only issuer - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AZ"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Shijingshang"), - util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester2"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "AU"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin2"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) - - // test disorder issuer or subject - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "ZH"), - util.MockPkixAttribute(util.Province, "Beijing"), - util.MockPkixAttribute(util.Locality, "Haidian"), - util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "tester1"), - }, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - se.GetSessionVars().TLSConnectionState = connectionState( - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{ - util.MockPkixAttribute(util.Country, "US"), - util.MockPkixAttribute(util.Province, "California"), - util.MockPkixAttribute(util.Locality, "San Francisco"), - util.MockPkixAttribute(util.Organization, "PingCAP"), - util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), - util.MockPkixAttribute(util.CommonName, "TiDB admin"), - }, - }, - pkix.Name{ - Names: []pkix.AttributeTypeAndValue{}, - }, - tls.TLS_AES_128_GCM_SHA256) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) - - // test mismatch san - c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) - - // test old data and broken data - c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) - -} - -func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { - cert := &x509.Certificate{Issuer: issuer, Subject: subject} - for _, o := range opt { - o(cert) - } - return &tls.ConnectionState{ - VerifiedChains: [][]*x509.Certificate{{cert}}, - CipherSuite: cipher, - } -} - -func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { - - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) - mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) - mustExec(c, se, `CREATE USER u4@localhost;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} - authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) - - se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "drop user 'u1'@'localhost'") - mustExec(c, se1, "drop user 'u2'@'localhost'") - mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") - mustExec(c, se1, "drop user u4@localhost") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "create role 'r1'@'localhost'") - mustExec(c, se2, "create role 'r2'@'localhost'") - mustExec(c, se2, "create role 'r3@example.com'@'localhost'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) - c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) - - mustExec(c, se1, "drop user 'r1'@'localhost'") - mustExec(c, se1, "drop user 'r2'@'localhost'") - mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") -} - -func (s *testPrivilegeSuite) TestUseDB(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'usesuper'") - mustExec(c, se, "CREATE USER 'usenobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") - // without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(e, NotNil) - // with grant option - se = newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "use mysql") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) - - // try again after privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, IsNil) - - // test `use db` for role. - c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE DATABASE app_db`) - mustExec(c, se, `CREATE ROLE 'app_developer'`) - mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) - mustExec(c, se, `CREATE USER 'dev'@'localhost'`) - mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) - mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "use app_db") - c.Assert(err, IsNil) - _, err = se.ExecuteInternal(context.Background(), "use mysql") - c.Assert(err, NotNil) -} - -func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, "CREATE USER 'hasgrant'") - mustExec(c, se, "CREATE USER 'withoutgrant'") - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") - mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") - // Without grant option - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - c.Assert(e, NotNil) - // With grant option - se = newSession(c, s.store, s.dbName) - mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") - c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") - mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") -} - -func (s *testPrivilegeSuite) TestSetGlobal(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER setglobal_a@localhost`) - mustExec(c, se, `CREATE USER setglobal_b@localhost`) - mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `set global innodb_commit_concurrency=16`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) -} - -func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tcd1, tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) - c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `DROP USER tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - - // should pass - mustExec(c, se, `GRANT tcd2 TO tcd1`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET ROLE tcd2;`) - mustExec(c, se, `CREATE USER tcd3`) - mustExec(c, se, `DROP USER tcd3`) -} - -func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `DROP USER IF EXISTS tcd1`) - mustExec(c, se, `CREATE USER tcd1`) - mustExec(c, se, `GRANT ALL ON *.* to tcd1`) - mustExec(c, se, `DROP USER IF EXISTS tcd2`) - mustExec(c, se, `CREATE USER tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2`) - mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) - c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") - mustExec(c, se, `DROP USER tcd1, tcd2`) -} - -func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tsct1, tsct2`) - mustExec(c, se, `GRANT select ON mysql.* to tsct2`) - - // should fail - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - - // should pass - c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, `SHOW CREATE TABLE mysql.user`) -} - -func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER tr_insert`) - mustExec(c, se, `CREATE USER tr_update`) - mustExec(c, se, `CREATE USER tr_delete`) - mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) - mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) - mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) - mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) - - // Restrict the permission to INSERT only. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) - - // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. - _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") - - // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") - - // Plain INSERT should work. - mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) - - // Also check that having DELETE alone is insufficient for REPLACE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") - - // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. - c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") -} - -func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { - - se := newSession(c, s.store, s.dbName) - // high privileged user - mustExec(c, se, "CREATE USER 'asuper'") - mustExec(c, se, "CREATE USER 'anobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") - mustExec(c, se, "CREATE DATABASE atest") - mustExec(c, se, "use atest") - mustExec(c, se, "CREATE TABLE t1 (a int)") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "analyze table mysql.user") - // low privileged user - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - - _, err = se.ExecuteInternal(context.Background(), "select * from t1") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") - - // try again after SELECT privilege granted - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) - c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") - // Add INSERT privilege and it should work. - c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") - c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "analyze table t1") - c.Assert(err, IsNil) - -} - -func (s *testPrivilegeSuite) TestSystemSchema(c *C) { - // This test tests no privilege check for INFORMATION_SCHEMA database. - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'u1'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `select * from information_schema.tables`) - mustExec(c, se, `select * from information_schema.key_column_usage`) - _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - // Test performance_schema. - mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) - _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) - - // Test metric_schema. - mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) - _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") - c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") - c.Assert(err, NotNil) - c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) -} - -func (s *testPrivilegeSuite) TestAdminCommand(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) - mustExec(c, se, `CREATE TABLE t(a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") - c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) - mustExec(c, se, `CREATE DATABASE dbexists`) - mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) - - tests := []struct { - stmt string - stmtType string - }{ - { - "SELECT * FROM %s.%s", - "SELECT", - }, - { - "SHOW CREATE TABLE %s.%s", - "SHOW", - }, - { - "DELETE FROM %s.%s WHERE a=0", - "DELETE", - }, - { - "DELETE FROM %s.%s", - "DELETE", - }, - } - - for _, t := range tests { - - _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) - _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) - - // Check the error is the same whether table exists or not. - c.Assert(terror.ErrorEqual(err1, err2), IsTrue) - - // Check it is permission denied, not not found. - c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) - - } - -} - -func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { - // Create file. - path := "/tmp/load_data_priv.csv" - fp, err := os.Create(path) - c.Assert(err, IsNil) - c.Assert(fp, NotNil) - defer func() { - err = fp.Close() - c.Assert(err, IsNil) - err = os.Remove(path) - c.Assert(err, IsNil) - }() - _, err = fp.WriteString("1\n") - c.Assert(err, IsNil) - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) - mustExec(c, se, `CREATE TABLE t_load(a int)`) - mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) - c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) - mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) - _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") - c.Assert(err, IsNil) -} - -func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) - message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - -func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) - pc := privilege.GetPrivilegeManager(se) - c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") -} - -func (s *testPrivilegeSuite) TestAuthHost(c *C) { - rootSe := newSession(c, s.store, s.dbName) - se := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) - mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") - mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") - - c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) - _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") - c.Assert(err, NotNil) - - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") - mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") -} - -func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) - mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) - mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) - - se := newSession(c, s.store, s.dbName) - pc := privilege.GetPrivilegeManager(se) - - ret := pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) - - mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 2) - - mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) - mustExec(c, rootSe, `flush privileges;`) - ret = pc.GetDefaultRoles("testdefault", "localhost") - c.Assert(len(ret), Equals, 0) -} - -func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("create user superadmin") - tk.MustExec("grant all privileges on *.* to 'superadmin'") - - // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 - c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) - - var buf bytes.Buffer - var res bytes.Buffer - buf.WriteString("select ") - i := 0 - for _, priv := range mysql.AllGlobalPrivs { - if i != 0 { - buf.WriteString(", ") - res.WriteString(" ") - } - buf.WriteString(mysql.Priv2UserCol[priv]) - res.WriteString("Y") - i++ - } - buf.WriteString(" from mysql.user where user = 'superadmin'") - tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) -} - -func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC - se := newSession(c, s.store, s.dbName) - mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) - mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) - c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) - _, err := se.FieldList("fieldlistt1") - message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" - c.Assert(strings.Contains(err.Error(), message), IsTrue) -} - func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -1242,157 +120,44 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } -func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER notsuper") - mustExec(c, rootSe, "CREATE USER otheruser") - mustExec(c, rootSe, "CREATE ROLE anyrolename") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - se := newSession(c, s.store, s.dbName) - c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") - - // test SYSTEM_VARIABLES_ADMIN - _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // test ROLE_ADMIN - _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") - mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") - mustExec(c, se, "GRANT anyrolename TO otheruser") - - // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped - mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - - // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN - mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") - mustExec(c, se, "SET GLOBAL wait_timeout = 86400") - - // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. - // confirm that a dynamic privilege can be inherited from a role. - mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") - mustExec(c, rootSe, "GRANT anyrolename TO notsuper") - - // It's not a default role, this should initially fail: - _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") - mustExec(c, se, "SET ROLE anyrolename") - mustExec(c, se, "SET GLOBAL wait_timeout = 87000") -} - -func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { +func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER varuser1") - mustExec(c, rootSe, "CREATE USER varuser2") - mustExec(c, rootSe, "CREATE USER varuser3") - mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") - - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") - mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") + mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") + mustExec(c, rootSe, "CREATE USER ru3") + mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") + mustExec(c, rootSe, "CREATE USER ru6@localhost") + mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) - mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) - _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") - - se2 := newSession(c, s.store, s.dbName) - mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") - - c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) - mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { - // This provides an integration test of the tests in util/security/security_test.go - cloudAdminSe := newSession(c, s.store, s.dbName) - mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") - mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") - mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") - mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") - mustExec(c, cloudAdminSe, "CREATE USER uroot") - mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. - c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) - urootSe := newSession(c, s.store, s.dbName) - mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") - c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) - - sem.Enable() - defer sem.Disable() - - _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") - c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") - _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") - c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") - - _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") - c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") - - mustExec(c, cloudAdminSe, "USE metrics_schema") - mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") - mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER uroot1, uroot2, uroot3") - tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot1", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - sem.Enable() - defer sem.Disable() - - // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) - // 36 = a UUID. Normally it is an IP address. - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) - - // That is unless we have the RESTRICTED_TABLES_ADMIN privilege - tk.Se.Auth(&auth.UserIdentity{ - Username: "uroot2", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) - - // flip from is NOT NULL etc - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) - tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) -} - -func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { - // Without TiKV the status var list does not include tidb_gc_leader_desc - // So we can only test that the dynamic privilege is grantable. - // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' - // and verify if it appears. - tk := testkit.NewTestKit(c, s.store) - tk.MustExec("CREATE USER unostatus, ustatus") - tk.MustExec("SET tidb_enable_dynamic_privileges=1") - tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") - tk.Se.Auth(&auth.UserIdentity{ - Username: "unostatus", - Hostname: "localhost", - AuthUsername: "uroot", - AuthHostname: "%", - }, nil, nil) + c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + // Workaround for *errors.withStack type + errString := err.Error() + c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") + c.Assert(err, IsNil) + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") + c.Assert(err, IsNil) + _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + c.Assert(err, IsNil) + mustExec(c, rootSe, "DROP USER ru6@localhost") + mustExec(c, rootSe, "DROP USER ru3") + mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") } func (s *testPrivilegeSuite) TestRenameUser(c *C) { From 5d671e70ad5e5632967c9d526f802f727c3bf61a Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 15:59:26 +0200 Subject: [PATCH 31/40] Fixed panic due to missing TimeZone in ExecuteInternal. --- privilege/privileges/privileges_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index b41ec6bfc6f9e..0c784bfb3683d 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -150,10 +150,11 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - // TODO: Why does this panic (and now 2021-05-06 23:51 CEST ?!? it did work before... _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru4, 'ru3_tmp' to ru6@localhost") + // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone + se1.GetSessionVars().TimeZone = time.UTC + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") From 2367828120de6721459ba1a08f64f700c2997957 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Sat, 8 May 2021 19:26:47 +0200 Subject: [PATCH 32/40] RENAME USER fixed unused variable And restored the temporarily removed test case (to speed up development of my patch) --- privilege/privileges/privileges_test.go | 1277 ++++++++++++++++++++++- 1 file changed, 1275 insertions(+), 2 deletions(-) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 0c784bfb3683d..58811fb0610d9 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -14,19 +14,35 @@ package privileges_test import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "fmt" + "net/url" + "os" + "strings" "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/privilege" + "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/sem" + "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" + "github.com/pingcap/tidb/util/testutil" ) func TestT(t *testing.T) { @@ -97,6 +113,1112 @@ func (s *testPrivilegeSuite) TearDownTest(c *C) { mustExec(c, se, s.dropDBSQL) } +func (s *testPrivilegeSuite) TestCheckDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testcheck'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'testcheck_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "testcheck", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'testcheck'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "testcheck", Hostname: "localhost"}) + mustExec(c, rootSe, `GRANT 'testcheck'@'localhost' TO 'testcheck_tmp'@'localhost';`) + se2 := newSession(c, s.store, s.dbName) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "testcheck_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc = privilege.GetPrivilegeManager(se2) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPointGetDBPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'tester'@'localhost';`) + mustExec(c, rootSe, `GRANT SELECT,UPDATE ON test.* TO 'tester'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + mustExec(c, rootSe, `create database test2`) + mustExec(c, rootSe, `create table test2.t(id int, v int, primary key(id))`) + mustExec(c, rootSe, `insert into test2.t(id, v) values(1, 1)`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tester", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `use test;`) + _, err := se.ExecuteInternal(context.Background(), `select * from test2.t where id = 1`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update test2.t set v = 2 where id = 1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestIssue22946(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "create database db1;") + mustExec(c, rootSe, "create database db2;") + mustExec(c, rootSe, "use test;") + mustExec(c, rootSe, "create table a(id int);") + mustExec(c, rootSe, "use db1;") + mustExec(c, rootSe, "create table a(id int primary key,name varchar(20));") + mustExec(c, rootSe, "use db2;") + mustExec(c, rootSe, "create table b(id int primary key,address varchar(50));") + mustExec(c, rootSe, "CREATE USER 'delTest'@'localhost';") + mustExec(c, rootSe, "grant all on db1.* to delTest@'localhost';") + mustExec(c, rootSe, "grant all on db2.* to delTest@'localhost';") + mustExec(c, rootSe, "grant select on test.* to delTest@'localhost';") + mustExec(c, rootSe, "flush privileges;") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "delTest", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `delete from db1.a as A where exists(select 1 from db2.b as B where A.id = B.id);`) + c.Assert(err, IsNil) + mustExec(c, rootSe, "use db1;") + _, err = se.ExecuteInternal(context.Background(), "delete from test.a as A;") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckTablePrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test1'@'localhost';`) + mustExec(c, rootSe, `CREATE USER 'test1_tmp'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test1", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON *.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsFalse) + + mustExec(c, rootSe, `GRANT Update ON test.* TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + activeRoles = append(activeRoles, &auth.RoleIdentity{Username: "test1", Hostname: "localhost"}) + se2 := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `GRANT 'test1'@'localhost' TO 'test1_tmp'@'localhost';`) + c.Assert(se2.Auth(&auth.UserIdentity{Username: "test1_tmp", Hostname: "localhost"}, nil, nil), IsTrue) + pc2 := privilege.GetPrivilegeManager(se2) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.SelectPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.UpdatePriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsFalse) + + mustExec(c, rootSe, `GRANT Index ON test.test TO 'test1'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) + c.Assert(pc2.RequestVerification(activeRoles, "test", "test", "", mysql.IndexPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckViewPrivilege(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'vuser'@'localhost';`) + mustExec(c, rootSe, `CREATE VIEW v AS SELECT * FROM test;`) + + se := newSession(c, s.store, s.dbName) + activeRoles := make([]*auth.RoleIdentity, 0) + c.Assert(se.Auth(&auth.UserIdentity{Username: "vuser", Hostname: "localhost"}, nil, nil), IsTrue) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SELECT ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsFalse) + + mustExec(c, rootSe, `GRANT SHOW VIEW ON test.v TO 'vuser'@'localhost';`) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "v", "", mysql.ShowViewPriv), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckPrivilegeWithRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_role'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE r_1, r_2, r_3;`) + mustExec(c, rootSe, `GRANT r_1, r_2, r_3 TO 'test_role'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_role", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE r_1, r_2;`) + mustExec(c, rootSe, `SET DEFAULT ROLE r_1 TO 'test_role'@'localhost';`) + + mustExec(c, rootSe, `GRANT SELECT ON test.* TO r_1;`) + pc := privilege.GetPrivilegeManager(se) + activeRoles := se.GetSessionVars().ActiveRoles + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.SelectPriv), IsTrue) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsFalse) + mustExec(c, rootSe, `GRANT UPDATE ON test.* TO r_2;`) + c.Assert(pc.RequestVerification(activeRoles, "test", "", "", mysql.UpdatePriv), IsTrue) + + mustExec(c, se, `SET ROLE NONE;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 0) + mustExec(c, se, `SET ROLE DEFAULT;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) + mustExec(c, se, `SET ROLE ALL;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 3) + mustExec(c, se, `SET ROLE ALL EXCEPT r_1, r_2;`) + c.Assert(len(se.GetSessionVars().ActiveRoles), Equals, 1) +} + +func (s *testPrivilegeSuite) TestShowGrants(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE USER 'show'@'localhost' identified by '123';`) + mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) + pc := privilege.GetPrivilegeManager(se) + + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Index ON *.* TO 'show'@'localhost'`) + + mustExec(c, se, `GRANT Select ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Index ON *.* TO 'show'@'localhost'`) + + // The order of privs is the same with AllGlobalPrivs + mustExec(c, se, `GRANT Update ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT Select,Update,Index ON *.* TO 'show'@'localhost'`) + + // All privileges + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // All privileges with grant option + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost' WITH GRANT OPTION`) + + // Revoke grant option + mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + + // Add db scope privileges + mustExec(c, se, `GRANT Select ON test.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT Index ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + mustExec(c, se, `GRANT ALL ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Add table scope privileges + mustExec(c, se, `GRANT Update ON test.test TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 4) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + `GRANT Select ON test.* TO 'show'@'localhost'`, + `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, + `GRANT Update ON test.test TO 'show'@'localhost'`} + c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) + + // Expected behavior: Usage still exists after revoking all privileges + mustExec(c, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE Select on test.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE ALL ON test1.* FROM 'show'@'localhost'`) + mustExec(c, se, `REVOKE UPDATE on test.test FROM 'show'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 1) + c.Assert(gs[0], Equals, `GRANT USAGE ON *.* TO 'show'@'localhost'`) + + // Usage should not exist after dropping the user + // Which we need privileges to do so! + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `DROP USER 'show'@'localhost'`) + + // This should now return an error + _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + c.Assert(err, NotNil) + // cant show grants for non-existent + c.Assert(terror.ErrorEqual(err, privileges.ErrNonexistingGrant), IsTrue) + + // Test SHOW GRANTS with USING roles. + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r1'`) + mustExec(c, se, `GRANT INSERT, UPDATE ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1', 'r2' TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles := make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r2", Hostname: "%"}) + mustExec(c, se, `GRANT DELETE ON test.* TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) + mustExec(c, se, `GRANT INSERT, DELETE ON test.test TO 'r2'`) + mustExec(c, se, `create table test.b (id int)`) + mustExec(c, se, `GRANT UPDATE ON test.b TO 'testrole'@'localhost'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 5) + mustExec(c, se, `DROP ROLE 'r1', 'r2'`) + mustExec(c, se, `DROP USER 'testrole'@'localhost'`) + mustExec(c, se, `CREATE ROLE 'r1', 'r2'`) + mustExec(c, se, `GRANT SELECT ON test.* TO 'r2'`) + mustExec(c, se, `CREATE USER 'testrole'@'localhost' IDENTIFIED BY 'u1pass'`) + mustExec(c, se, `GRANT 'r1' TO 'testrole'@'localhost'`) + mustExec(c, se, `GRANT 'r2' TO 'r1'`) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, nil) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 2) + roles = make([]*auth.RoleIdentity, 0) + roles = append(roles, &auth.RoleIdentity{Username: "r1", Hostname: "%"}) + gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "testrole", Hostname: "localhost"}, roles) + c.Assert(err, IsNil) + c.Assert(gs, HasLen, 3) +} + +func (s *testPrivilegeSuite) TestShowColumnGrants(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `USE test`) + mustExec(c, se, `CREATE USER 'column'@'%'`) + mustExec(c, se, `CREATE TABLE column_table (a int, b int, c int)`) + mustExec(c, se, `GRANT Select(a),Update(a,b),Insert(c) ON test.column_table TO 'column'@'%'`) + + pc := privilege.GetPrivilegeManager(se) + gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) + c.Assert(err, IsNil) + c.Assert(strings.Join(gs, " "), Equals, "GRANT USAGE ON *.* TO 'column'@'%' GRANT Select(a), Insert(c), Update(a, b) ON test.column_table TO 'column'@'%'") +} + +func (s *testPrivilegeSuite) TestDropTablePriv(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE todrop(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'drop'@'localhost';`) + mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) + + // ctx.GetSessionVars().User = "drop@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "drop", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM todrop;`) + _, err := se.ExecuteInternal(context.Background(), "DROP TABLE todrop;") + c.Assert(err, NotNil) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "drop", Hostname: "localhost"} + mustExec(c, se, `DROP TABLE todrop;`) +} + +func (s *testPrivilegeSuite) TestSetPasswdStmt(c *C) { + + se := newSession(c, s.store, s.dbName) + + // high privileged user setting password for other user (passes) + mustExec(c, se, "CREATE USER 'superuser'") + mustExec(c, se, "CREATE USER 'nobodyuser'") + mustExec(c, se, "GRANT ALL ON *.* TO 'superuser'") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser", Hostname: "localhost", AuthUsername: "superuser", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = 'newpassword'") + mustExec(c, se, "SET PASSWORD for 'nobodyuser' = ''") + + // low privileged user trying to set password for other user (fails) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser", Hostname: "localhost", AuthUsername: "nobodyuser", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "SET PASSWORD for 'superuser' = 'newpassword'") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + ctx, _ := se.(sessionctx.Context) + mustExec(c, se, `CREATE TABLE viewsecurity(c int);`) + // ctx.GetSessionVars().User = "root@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT CREATE VIEW ON test.* TO 'selectusr'@'localhost';`) + mustExec(c, se, `GRANT SELECT ON test.viewsecurity TO 'selectusr'@'localhost';`) + + // ctx.GetSessionVars().User = "selectusr@localhost" + c.Assert(se.Auth(&auth.UserIdentity{Username: "selectusr", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `SELECT * FROM test.viewsecurity;`) + mustExec(c, se, `CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW test.selectviewsecurity as select * FROM test.viewsecurity;`) + + se = newSession(c, s.store, s.dbName) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "root", Hostname: "localhost"} + mustExec(c, se, "SELECT * FROM test.selectviewsecurity") + mustExec(c, se, `REVOKE Select ON test.viewsecurity FROM 'selectusr'@'localhost';`) + _, err := se.ExecuteInternal(context.Background(), "select * from test.selectviewsecurity") + c.Assert(err.Error(), Equals, core.ErrViewInvalid.GenWithStackByArgs("test", "selectviewsecurity").Error()) +} + +func (s *testPrivilegeSuite) TestRoleAdminSecurity(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'ar1'@'localhost';`) + mustExec(c, se, `CREATE USER 'ar2'@'localhost';`) + mustExec(c, se, `GRANT ALL ON *.* to ar1@localhost`) + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'ar1'@'localhost'") + mustExec(c, se, "drop user 'ar2'@'localhost'") + }() + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `create role r_test1@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "ar2", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `create role r_test2@localhost`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCheckCertBasedAuth(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'r1'@'localhost';`) + mustExec(c, se, `CREATE USER 'r2'@'localhost' require none;`) + mustExec(c, se, `CREATE USER 'r3'@'localhost' require ssl;`) + mustExec(c, se, `CREATE USER 'r4'@'localhost' require x509;`) + mustExec(c, se, `CREATE USER 'r5'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1' cipher 'TLS_AES_128_GCM_SHA256'`) + mustExec(c, se, `CREATE USER 'r6'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r7_issuer_only'@'localhost' require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r8_subject_only'@'localhost' require subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r9_subject_disorder'@'localhost' require subject '/ST=Beijing/C=ZH/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, `CREATE USER 'r10_issuer_disorder'@'localhost' require issuer '/ST=California/C=US/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin'`) + mustExec(c, se, `CREATE USER 'r11_cipher_only'@'localhost' require cipher 'TLS_AES_256_GCM_SHA384'`) + mustExec(c, se, `CREATE USER 'r12_old_tidb_user'@'localhost'`) + mustExec(c, se, "DELETE FROM mysql.global_priv WHERE `user` = 'r12_old_tidb_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r13_broken_user'@'localhost'require issuer '/C=US/ST=California/L=San Francisco/O=PingCAP/OU=TiDB/CN=TiDB admin' + subject '/C=ZH/ST=Beijing/L=Haidian/O=PingCAP.Inc/OU=TiDB/CN=tester1'`) + mustExec(c, se, "UPDATE mysql.global_priv set priv = 'abc' where `user` = 'r13_broken_user' and `host` = 'localhost'") + mustExec(c, se, `CREATE USER 'r14_san_only_pass'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me1'`) + mustExec(c, se, `CREATE USER 'r15_san_only_fail'@'localhost' require san 'URI:spiffe://mesh.pingcap.com/ns/timesh/sa/me2'`) + mustExec(c, se, "flush privileges") + + defer func() { + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "drop user 'r1'@'localhost'") + mustExec(c, se, "drop user 'r2'@'localhost'") + mustExec(c, se, "drop user 'r3'@'localhost'") + mustExec(c, se, "drop user 'r4'@'localhost'") + mustExec(c, se, "drop user 'r5'@'localhost'") + mustExec(c, se, "drop user 'r6'@'localhost'") + mustExec(c, se, "drop user 'r7_issuer_only'@'localhost'") + mustExec(c, se, "drop user 'r8_subject_only'@'localhost'") + mustExec(c, se, "drop user 'r9_subject_disorder'@'localhost'") + mustExec(c, se, "drop user 'r10_issuer_disorder'@'localhost'") + mustExec(c, se, "drop user 'r11_cipher_only'@'localhost'") + mustExec(c, se, "drop user 'r12_old_tidb_user'@'localhost'") + mustExec(c, se, "drop user 'r13_broken_user'@'localhost'") + mustExec(c, se, "drop user 'r14_san_only_pass'@'localhost'") + mustExec(c, se, "drop user 'r15_san_only_fail'@'localhost'") + }() + + // test without ssl or ca + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl without ca + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: nil} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test use ssl with signed but info wrong ca. + se.GetSessionVars().TLSConnectionState = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{{{}}}} + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test a all pass case + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256, func(cert *x509.Certificate) { + var url url.URL + err := url.UnmarshalBinary([]byte("spiffe://mesh.pingcap.com/ns/timesh/sa/me1")) + c.Assert(err, IsNil) + cert.URIs = append(cert.URIs, &url) + }) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r4", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r14_san_only_pass", Hostname: "localhost"}, nil, nil), IsTrue) + + // test require but give nothing + se.GetSessionVars().TLSConnectionState = nil + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch cipher + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_256_GCM_SHA384) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r5", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r6", Hostname: "localhost"}, nil, nil), IsTrue) // not require cipher + c.Assert(se.Auth(&auth.UserIdentity{Username: "r11_cipher_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test only subject or only issuer + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AZ"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Shijingshang"), + util.MockPkixAttribute(util.Organization, "CAPPing.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester2"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r7_issuer_only", Hostname: "localhost"}, nil, nil), IsTrue) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "AU"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin2"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r8_subject_only", Hostname: "localhost"}, nil, nil), IsTrue) + + // test disorder issuer or subject + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "ZH"), + util.MockPkixAttribute(util.Province, "Beijing"), + util.MockPkixAttribute(util.Locality, "Haidian"), + util.MockPkixAttribute(util.Organization, "PingCAP.Inc"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "tester1"), + }, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r9_subject_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + se.GetSessionVars().TLSConnectionState = connectionState( + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + util.MockPkixAttribute(util.Country, "US"), + util.MockPkixAttribute(util.Province, "California"), + util.MockPkixAttribute(util.Locality, "San Francisco"), + util.MockPkixAttribute(util.Organization, "PingCAP"), + util.MockPkixAttribute(util.OrganizationalUnit, "TiDB"), + util.MockPkixAttribute(util.CommonName, "TiDB admin"), + }, + }, + pkix.Name{ + Names: []pkix.AttributeTypeAndValue{}, + }, + tls.TLS_AES_128_GCM_SHA256) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r10_issuer_disorder", Hostname: "localhost"}, nil, nil), IsFalse) + + // test mismatch san + c.Assert(se.Auth(&auth.UserIdentity{Username: "r15_san_only_fail", Hostname: "localhost"}, nil, nil), IsFalse) + + // test old data and broken data + c.Assert(se.Auth(&auth.UserIdentity{Username: "r12_old_tidb_user", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r13_broken_user", Hostname: "localhost"}, nil, nil), IsFalse) + +} + +func connectionState(issuer, subject pkix.Name, cipher uint16, opt ...func(c *x509.Certificate)) *tls.ConnectionState { + cert := &x509.Certificate{Issuer: issuer, Subject: subject} + for _, o := range opt { + o(cert) + } + return &tls.ConnectionState{ + VerifiedChains: [][]*x509.Certificate{{cert}}, + CipherSuite: cipher, + } +} + +func (s *testPrivilegeSuite) TestCheckAuthenticate(c *C) { + + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + mustExec(c, se, `CREATE USER 'u2'@'localhost' identified by 'abc';`) + mustExec(c, se, `CREATE USER 'u3@example.com'@'localhost';`) + mustExec(c, se, `CREATE USER u4@localhost;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + salt := []byte{85, 92, 45, 22, 58, 79, 107, 6, 122, 125, 58, 80, 12, 90, 103, 32, 90, 10, 74, 82} + authentication := []byte{24, 180, 183, 225, 166, 6, 81, 102, 70, 248, 199, 143, 91, 204, 169, 9, 161, 171, 203, 33} + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, authentication, salt), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsTrue) + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "drop user 'u1'@'localhost'") + mustExec(c, se1, "drop user 'u2'@'localhost'") + mustExec(c, se1, "drop user 'u3@example.com'@'localhost'") + mustExec(c, se1, "drop user u4@localhost") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u4", Hostname: "localhost"}, nil, nil), IsFalse) + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "create role 'r1'@'localhost'") + mustExec(c, se2, "create role 'r2'@'localhost'") + mustExec(c, se2, "create role 'r3@example.com'@'localhost'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "r1", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r2", Hostname: "localhost"}, nil, nil), IsFalse) + c.Assert(se.Auth(&auth.UserIdentity{Username: "r3@example.com", Hostname: "localhost"}, nil, nil), IsFalse) + + mustExec(c, se1, "drop user 'r1'@'localhost'") + mustExec(c, se1, "drop user 'r2'@'localhost'") + mustExec(c, se1, "drop user 'r3@example.com'@'localhost'") +} + +func (s *testPrivilegeSuite) TestUseDB(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'usesuper'") + mustExec(c, se, "CREATE USER 'usenobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") + // without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(e, NotNil) + // with grant option + se = newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "use mysql") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) + + // try again after privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "usenobody", Hostname: "localhost", AuthUsername: "usenobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, IsNil) + + // test `use db` for role. + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE DATABASE app_db`) + mustExec(c, se, `CREATE ROLE 'app_developer'`) + mustExec(c, se, `GRANT ALL ON app_db.* TO 'app_developer'`) + mustExec(c, se, `CREATE USER 'dev'@'localhost'`) + mustExec(c, se, `GRANT 'app_developer' TO 'dev'@'localhost'`) + mustExec(c, se, `SET DEFAULT ROLE 'app_developer' TO 'dev'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "localhost", AuthUsername: "dev", AuthHostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "use app_db") + c.Assert(err, IsNil) + _, err = se.ExecuteInternal(context.Background(), "use mysql") + c.Assert(err, NotNil) +} + +func (s *testPrivilegeSuite) TestRevokePrivileges(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, "CREATE USER 'hasgrant'") + mustExec(c, se, "CREATE USER 'withoutgrant'") + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant'") + mustExec(c, se, "GRANT ALL ON mysql.* TO 'withoutgrant'") + // Without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.ExecuteInternal(context.Background(), "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + c.Assert(e, NotNil) + // With grant option + se = newSession(c, s.store, s.dbName) + mustExec(c, se, "GRANT ALL ON *.* TO 'hasgrant' WITH GRANT OPTION") + c.Assert(se.Auth(&auth.UserIdentity{Username: "hasgrant", Hostname: "localhost", AuthUsername: "hasgrant", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "REVOKE SELECT ON mysql.* FROM 'withoutgrant'") + mustExec(c, se, "REVOKE ALL ON mysql.* FROM withoutgrant") +} + +func (s *testPrivilegeSuite) TestSetGlobal(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER setglobal_a@localhost`) + mustExec(c, se, `CREATE USER setglobal_b@localhost`) + mustExec(c, se, `GRANT SUPER ON *.* to setglobal_a@localhost`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_a", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `set global innodb_commit_concurrency=16`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "setglobal_b", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `set global innodb_commit_concurrency=16`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) +} + +func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tcd1, tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `CREATE USER acdc`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `DROP USER tcd2`) + c.Assert(terror.ErrorEqual(err, core.ErrSpecificAccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthUsername: "tcd2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `DROP USER tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + + // should pass + mustExec(c, se, `GRANT tcd2 TO tcd1`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET ROLE tcd2;`) + mustExec(c, se, `CREATE USER tcd3`) + mustExec(c, se, `DROP USER tcd3`) +} + +func (s *testPrivilegeSuite) TestConfigPrivilege(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `DROP USER IF EXISTS tcd1`) + mustExec(c, se, `CREATE USER tcd1`) + mustExec(c, se, `GRANT ALL ON *.* to tcd1`) + mustExec(c, se, `DROP USER IF EXISTS tcd2`) + mustExec(c, se, `CREATE USER tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2`) + mustExec(c, se, `REVOKE CONFIG ON *.* FROM tcd2`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthHostname: "tcd1", AuthUsername: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SET CONFIG TIKV testkey="testval"`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd2", Hostname: "localhost", AuthHostname: "tcd2", AuthUsername: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SET CONFIG TIKV testkey="testval"`) + c.Assert(err, ErrorMatches, ".*you need \\(at least one of\\) the CONFIG privilege\\(s\\) for this operation") + mustExec(c, se, `DROP USER tcd1, tcd2`) +} + +func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tsct1, tsct2`) + mustExec(c, se, `GRANT select ON mysql.* to tsct2`) + + // should fail + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct1", Hostname: "localhost", AuthUsername: "tsct1", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `SHOW CREATE TABLE mysql.user`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + + // should pass + c.Assert(se.Auth(&auth.UserIdentity{Username: "tsct2", Hostname: "localhost", AuthUsername: "tsct2", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, `SHOW CREATE TABLE mysql.user`) +} + +func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tr_insert`) + mustExec(c, se, `CREATE USER tr_update`) + mustExec(c, se, `CREATE USER tr_delete`) + mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) + mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) + mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) + mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) + + // Restrict the permission to INSERT only. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) + + // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. + _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") + + // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") + + // Plain INSERT should work. + mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) + + // Also check that having DELETE alone is insufficient for REPLACE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") + + // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") +} + +func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { + + se := newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "CREATE USER 'asuper'") + mustExec(c, se, "CREATE USER 'anobody'") + mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") + mustExec(c, se, "CREATE DATABASE atest") + mustExec(c, se, "use atest") + mustExec(c, se, "CREATE TABLE t1 (a int)") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "analyze table mysql.user") + // low privileged user + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + + _, err = se.ExecuteInternal(context.Background(), "select * from t1") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'anobody'@'%' for table 't1'") + + // try again after SELECT privilege granted + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT SELECT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'anobody'@'%' for table 't1'") + // Add INSERT privilege and it should work. + c.Assert(se.Auth(&auth.UserIdentity{Username: "asuper", Hostname: "localhost", AuthUsername: "asuper", AuthHostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "GRANT INSERT ON atest.* TO 'anobody'") + c.Assert(se.Auth(&auth.UserIdentity{Username: "anobody", Hostname: "localhost", AuthUsername: "anobody", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "analyze table t1") + c.Assert(err, IsNil) + +} + +func (s *testPrivilegeSuite) TestSystemSchema(c *C) { + // This test tests no privilege check for INFORMATION_SCHEMA database. + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'u1'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `select * from information_schema.tables`) + mustExec(c, se, `select * from information_schema.key_column_usage`) + _, err := se.ExecuteInternal(context.Background(), "create table information_schema.t(a int)") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "drop table information_schema.tables") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update information_schema.tables set table_name = 'tst' where table_name = 'mysql'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + // Test performance_schema. + mustExec(c, se, `select * from performance_schema.events_statements_summary_by_digest`) + _, err = se.ExecuteInternal(context.Background(), "drop table performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update performance_schema.events_statements_summary_by_digest set schema_name = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from performance_schema.events_statements_summary_by_digest") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table performance_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) + + // Test metric_schema. + mustExec(c, se, `select * from metrics_schema.tidb_query_duration`) + _, err = se.ExecuteInternal(context.Background(), "drop table metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "update metrics_schema.tidb_query_duration set instance = 'tst'") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "delete from metrics_schema.tidb_query_duration") + c.Assert(strings.Contains(err.Error(), "DELETE command denied to user"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "create table metric_schema.t(a int)") + c.Assert(err, NotNil) + c.Assert(strings.Contains(err.Error(), "CREATE command denied"), IsTrue, Commentf(err.Error())) +} + +func (s *testPrivilegeSuite) TestAdminCommand(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_admin'@'localhost';`) + mustExec(c, se, `CREATE TABLE t(a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_admin", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN CHECK TABLE t") + c.Assert(strings.Contains(err.Error(), "privilege check fail"), IsTrue) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "ADMIN SHOW DDL JOBS") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestTableNotExistNoPermissions(c *C) { + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'testnotexist'@'localhost';`) + mustExec(c, se, `CREATE DATABASE dbexists`) + mustExec(c, se, `CREATE TABLE dbexists.t1 (a int)`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "testnotexist", Hostname: "localhost"}, nil, nil), IsTrue) + + tests := []struct { + stmt string + stmtType string + }{ + { + "SELECT * FROM %s.%s", + "SELECT", + }, + { + "SHOW CREATE TABLE %s.%s", + "SHOW", + }, + { + "DELETE FROM %s.%s WHERE a=0", + "DELETE", + }, + { + "DELETE FROM %s.%s", + "DELETE", + }, + } + + for _, t := range tests { + + _, err1 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbexists", "t1")) + _, err2 := se.ExecuteInternal(context.Background(), fmt.Sprintf(t.stmt, "dbnotexists", "t1")) + + // Check the error is the same whether table exists or not. + c.Assert(terror.ErrorEqual(err1, err2), IsTrue) + + // Check it is permission denied, not not found. + c.Assert(err2.Error(), Equals, fmt.Sprintf("[planner:1142]%s command denied to user 'testnotexist'@'localhost' for table 't1'", t.stmtType)) + + } + +} + +func (s *testPrivilegeSuite) TestLoadDataPrivilege(c *C) { + // Create file. + path := "/tmp/load_data_priv.csv" + fp, err := os.Create(path) + c.Assert(err, IsNil) + c.Assert(fp, NotNil) + defer func() { + err = fp.Close() + c.Assert(err, IsNil) + err = os.Remove(path) + c.Assert(err, IsNil) + }() + _, err = fp.WriteString("1\n") + c.Assert(err, IsNil) + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `CREATE USER 'test_load'@'localhost';`) + mustExec(c, se, `CREATE TABLE t_load(a int)`) + mustExec(c, se, `GRANT SELECT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(strings.Contains(err.Error(), "INSERT command denied to user 'test_load'@'localhost' for table 't_load'"), IsTrue) + c.Assert(se.Auth(&auth.UserIdentity{Username: "root", Hostname: "localhost"}, nil, nil), IsTrue) + mustExec(c, se, `GRANT INSERT on *.* to 'test_load'@'localhost'`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_load", Hostname: "localhost"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), "LOAD DATA LOCAL INFILE '/tmp/load_data_priv.csv' INTO TABLE t_load") + c.Assert(err, IsNil) +} + +func (s *testPrivilegeSuite) TestSelectIntoNoPremissions(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'nofile'@'localhost';`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "nofile", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), `select 1 into outfile '/tmp/doesntmatter-no-permissions'`) + message := "Access denied; you need (at least one of) the FILE privilege(s) for this operation" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + +func (s *testPrivilegeSuite) TestGetEncodedPassword(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'test_encode_u'@'localhost' identified by 'root';`) + pc := privilege.GetPrivilegeManager(se) + c.Assert(pc.GetEncodedPassword("test_encode_u", "localhost"), Equals, "*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B") +} + +func (s *testPrivilegeSuite) TestAuthHost(c *C) { + rootSe := newSession(c, s.store, s.dbName) + se := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'test_auth_host'@'%';`) + mustExec(c, rootSe, `GRANT ALL ON *.* TO 'test_auth_host'@'%' WITH GRANT OPTION;`) + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + mustExec(c, se, "CREATE USER 'test_auth_host'@'192.168.%';") + mustExec(c, se, "GRANT SELECT ON *.* TO 'test_auth_host'@'192.168.%';") + + c.Assert(se.Auth(&auth.UserIdentity{Username: "test_auth_host", Hostname: "192.168.0.10"}, nil, nil), IsTrue) + _, err := se.ExecuteInternal(context.Background(), "create user test_auth_host_a") + c.Assert(err, NotNil) + + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'192.168.%';") + mustExec(c, rootSe, "DROP USER 'test_auth_host'@'%';") +} + +func (s *testPrivilegeSuite) TestDefaultRoles(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, `CREATE USER 'testdefault'@'localhost';`) + mustExec(c, rootSe, `CREATE ROLE 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost';`) + mustExec(c, rootSe, `GRANT 'testdefault_r1'@'localhost', 'testdefault_r2'@'localhost' TO 'testdefault'@'localhost';`) + + se := newSession(c, s.store, s.dbName) + pc := privilege.GetPrivilegeManager(se) + + ret := pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) + + mustExec(c, rootSe, `SET DEFAULT ROLE ALL TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 2) + + mustExec(c, rootSe, `SET DEFAULT ROLE NONE TO 'testdefault'@'localhost';`) + mustExec(c, rootSe, `flush privileges;`) + ret = pc.GetDefaultRoles("testdefault", "localhost") + c.Assert(len(ret), Equals, 0) +} + +func (s *testPrivilegeSuite) TestUserTableConsistency(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create user superadmin") + tk.MustExec("grant all privileges on *.* to 'superadmin'") + + // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 + c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) + + var buf bytes.Buffer + var res bytes.Buffer + buf.WriteString("select ") + i := 0 + for _, priv := range mysql.AllGlobalPrivs { + if i != 0 { + buf.WriteString(", ") + res.WriteString(" ") + } + buf.WriteString(mysql.Priv2UserCol[priv]) + res.WriteString("Y") + i++ + } + buf.WriteString(" from mysql.user where user = 'superadmin'") + tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) +} + +func (s *testPrivilegeSuite) TestFieldList(c *C) { // Issue #14237 List fields RPC + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER 'tableaccess'@'localhost'`) + mustExec(c, se, `CREATE TABLE fieldlistt1 (a int)`) + c.Assert(se.Auth(&auth.UserIdentity{Username: "tableaccess", Hostname: "localhost"}, nil, nil), IsTrue) + _, err := se.FieldList("fieldlistt1") + message := "SELECT command denied to user 'tableaccess'@'localhost' for table 'fieldlistt1'" + c.Assert(strings.Contains(err.Error(), message), IsTrue) +} + func mustExec(c *C, se session.Session, sql string) { _, err := se.ExecuteInternal(context.Background(), sql) c.Assert(err, IsNil) @@ -120,6 +1242,159 @@ func newSession(c *C, store kv.Storage, dbName string) session.Session { return se } +func (s *testPrivilegeSuite) TestDynamicPrivs(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER notsuper") + mustExec(c, rootSe, "CREATE USER otheruser") + mustExec(c, rootSe, "CREATE ROLE anyrolename") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + se := newSession(c, s.store, s.dbName) + c.Assert(se.Auth(&auth.UserIdentity{Username: "notsuper", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se, "SET tidb_enable_dynamic_privileges=1") + + // test SYSTEM_VARIABLES_ADMIN + _, err := se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_admin ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // test ROLE_ADMIN + _, err = se.ExecuteInternal(context.Background(), "GRANT anyrolename TO otheruser") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or ROLE_ADMIN privilege(s) for this operation") + mustExec(c, rootSe, "GRANT ROLE_ADMIN ON *.* TO notsuper") + mustExec(c, se, "GRANT anyrolename TO otheruser") + + // revoke SYSTEM_VARIABLES_ADMIN, confirm it is dropped + mustExec(c, rootSe, "REVOKE SYSTEM_VARIABLES_AdmIn ON *.* FROM notsuper") + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86000") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + + // grant super, confirm that it is also a substitute for SYSTEM_VARIABLES_ADMIN + mustExec(c, rootSe, "GRANT SUPER ON *.* TO notsuper") + mustExec(c, se, "SET GLOBAL wait_timeout = 86400") + + // revoke SUPER, assign SYSTEM_VARIABLES_ADMIN to anyrolename. + // confirm that a dynamic privilege can be inherited from a role. + mustExec(c, rootSe, "REVOKE SUPER ON *.* FROM notsuper") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_AdmIn ON *.* TO anyrolename") + mustExec(c, rootSe, "GRANT anyrolename TO notsuper") + + // It's not a default role, this should initially fail: + _, err = se.ExecuteInternal(context.Background(), "SET GLOBAL wait_timeout = 86400") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SUPER or SYSTEM_VARIABLES_ADMIN privilege(s) for this operation") + mustExec(c, se, "SET ROLE anyrolename") + mustExec(c, se, "SET GLOBAL wait_timeout = 87000") +} + +func (s *testPrivilegeSuite) TestDynamicGrantOption(c *C) { + rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "CREATE USER varuser1") + mustExec(c, rootSe, "CREATE USER varuser2") + mustExec(c, rootSe, "CREATE USER varuser3") + mustExec(c, rootSe, "SET tidb_enable_dynamic_privileges=1") + + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser1") + mustExec(c, rootSe, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser2 WITH GRANT OPTION") + + se1 := newSession(c, s.store, s.dbName) + mustExec(c, se1, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se1.Auth(&auth.UserIdentity{Username: "varuser1", Hostname: "%"}, nil, nil), IsTrue) + _, err := se1.ExecuteInternal(context.Background(), "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") + c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the GRANT OPTION privilege(s) for this operation") + + se2 := newSession(c, s.store, s.dbName) + mustExec(c, se2, "SET tidb_enable_dynamic_privileges=1") + + c.Assert(se2.Auth(&auth.UserIdentity{Username: "varuser2", Hostname: "%"}, nil, nil), IsTrue) + mustExec(c, se2, "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO varuser3") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeRestrictedTables(c *C) { + // This provides an integration test of the tests in util/security/security_test.go + cloudAdminSe := newSession(c, s.store, s.dbName) + mustExec(c, cloudAdminSe, "CREATE USER cloudadmin") + mustExec(c, cloudAdminSe, "SET tidb_enable_dynamic_privileges=1") + mustExec(c, cloudAdminSe, "GRANT RESTRICTED_TABLES_ADMIN, SELECT ON *.* to cloudadmin") + mustExec(c, cloudAdminSe, "GRANT CREATE ON mysql.* to cloudadmin") + mustExec(c, cloudAdminSe, "CREATE USER uroot") + mustExec(c, cloudAdminSe, "GRANT ALL ON *.* to uroot WITH GRANT OPTION") // A "MySQL" all powerful user. + c.Assert(cloudAdminSe.Auth(&auth.UserIdentity{Username: "cloudadmin", Hostname: "%"}, nil, nil), IsTrue) + urootSe := newSession(c, s.store, s.dbName) + mustExec(c, urootSe, "SET tidb_enable_dynamic_privileges=1") + c.Assert(urootSe.Auth(&auth.UserIdentity{Username: "uroot", Hostname: "%"}, nil, nil), IsTrue) + + sem.Enable() + defer sem.Disable() + + _, err := urootSe.ExecuteInternal(context.Background(), "use metrics_schema") + c.Assert(err.Error(), Equals, "[executor:1044]Access denied for user 'uroot'@'%' to database 'metrics_schema'") + + _, err = urootSe.ExecuteInternal(context.Background(), "SELECT * FROM metrics_schema.uptime") + c.Assert(err.Error(), Equals, "[planner:1142]SELECT command denied to user 'uroot'@'%' for table 'uptime'") + + _, err = urootSe.ExecuteInternal(context.Background(), "CREATE TABLE mysql.abcd (a int)") + c.Assert(err.Error(), Equals, "[planner:1142]CREATE command denied to user 'uroot'@'%' for table 'abcd'") + + mustExec(c, cloudAdminSe, "USE metrics_schema") + mustExec(c, cloudAdminSe, "SELECT * FROM metrics_schema.uptime") + mustExec(c, cloudAdminSe, "CREATE TABLE mysql.abcd (a int)") +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeInfoschema(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER uroot1, uroot2, uroot3") + tk.MustExec("GRANT SUPER ON *.* to uroot1 WITH GRANT OPTION") // super not process + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT SUPER, PROCESS, RESTRICTED_TABLES_ADMIN ON *.* to uroot2 WITH GRANT OPTION") + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot1", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + sem.Enable() + defer sem.Disable() + + // Even though we have super, we still can't read protected information from tidb_servers_info, cluster_* tables + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NOT NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NOT NULL`).Check(testkit.Rows("0")) + // 36 = a UUID. Normally it is an IP address. + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) != 36`).Check(testkit.Rows("0")) + + // That is unless we have the RESTRICTED_TABLES_ADMIN privilege + tk.Se.Auth(&auth.UserIdentity{ + Username: "uroot2", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) + + // flip from is NOT NULL etc + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.tidb_servers_info WHERE ip IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.cluster_info WHERE status_address IS NULL`).Check(testkit.Rows("0")) + tk.MustQuery(`SELECT COUNT(*) FROM information_schema.CLUSTER_STATEMENTS_SUMMARY WHERE length(instance) = 36`).Check(testkit.Rows("0")) +} + +func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { + // Without TiKV the status var list does not include tidb_gc_leader_desc + // So we can only test that the dynamic privilege is grantable. + // We will have to use an integration test to run SHOW STATUS LIKE 'tidb_gc_leader_desc' + // and verify if it appears. + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER unostatus, ustatus") + tk.MustExec("SET tidb_enable_dynamic_privileges=1") + tk.MustExec("GRANT RESTRICTED_STATUS_ADMIN ON *.* to ustatus") + tk.Se.Auth(&auth.UserIdentity{ + Username: "unostatus", + Hostname: "localhost", + AuthUsername: "uroot", + AuthHostname: "%", + }, nil, nil) +} + func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") @@ -128,9 +1403,7 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") - se1 := newSession(c, s.store, s.dbName) - c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") From 8ec68c2f1e1e0c11739633d8c52379eed70d3670 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 08:31:47 +0200 Subject: [PATCH 33/40] RENAME USER code cleanup --- privilege/privileges/privileges_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 58811fb0610d9..aa0ecf4b6b9ef 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1398,13 +1398,12 @@ func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") - mustExec(c, rootSe, "GRANT SELECT ON mysql.user TO 'ru1'@'localhost'") mustExec(c, rootSe, "CREATE USER ru3") - mustExec(c, rootSe, "GRANT SELECT ON mysql.db TO ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") - mustExec(c, rootSe, "GRANT SELECT ON mysql.global_grants TO 'ru6'@'localhost'") se1 := newSession(c, s.store, s.dbName) c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) + + // Check privileges (need CREATE USER) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") @@ -1415,20 +1414,27 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err, IsNil) + + // Test a few single rename (both Username and Hostname) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") c.Assert(err, IsNil) _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") c.Assert(err, IsNil) + // Including negative tests, i.e. non existing from user and existing to user _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + + // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone se1.GetSessionVars().TimeZone = time.UTC _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) + + // Cleanup mustExec(c, rootSe, "DROP USER ru6@localhost") mustExec(c, rootSe, "DROP USER ru3") mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") From b3a36acda232d6a1f8864506730d082c039a97ed Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 10 May 2021 14:09:14 +0200 Subject: [PATCH 34/40] RENAME USER, minor test case change Just added multi user rename, with failure on the second TO user. --- privilege/privileges/privileges_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index aa0ecf4b6b9ef..68458272a4f03 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1427,6 +1427,10 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") + c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone From f4fe928444914822c8f5db9b60564e0ab33456e5 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 11 May 2021 01:03:05 +0200 Subject: [PATCH 35/40] RENAME USER, test cleanup --- privilege/privileges/privileges_test.go | 52 ------------------------- 1 file changed, 52 deletions(-) diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 68458272a4f03..0fa34284cc3f2 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -24,7 +24,6 @@ import ( "os" "strings" "testing" - "time" . "github.com/pingcap/check" "github.com/pingcap/parser/auth" @@ -1433,57 +1432,6 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. - // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone - se1.GetSessionVars().TimeZone = time.UTC - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") - c.Assert(err, IsNil) - - // Cleanup - mustExec(c, rootSe, "DROP USER ru6@localhost") - mustExec(c, rootSe, "DROP USER ru3") - mustExec(c, rootSe, "DROP USER 'ru1'@'localhost'") -} - -func (s *testPrivilegeSuite) TestRenameUser(c *C) { - rootSe := newSession(c, s.store, s.dbName) - mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") - mustExec(c, rootSe, "CREATE USER ru3") - mustExec(c, rootSe, "CREATE USER ru6@localhost") - se1 := newSession(c, s.store, s.dbName) - c.Assert(se1.Auth(&auth.UserIdentity{Username: "ru1", Hostname: "localhost"}, nil, nil), IsTrue) - - // Check privileges (need CREATE USER) - _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") - mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") - // Workaround for *errors.withStack type - errString := err.Error() - c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") - mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") - c.Assert(err, IsNil) - - // Test a few single rename (both Username and Hostname) - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru4'@'%' TO 'ru3'@'localhost'") - c.Assert(err, IsNil) - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3'@'localhost' TO 'ru3'@'%'") - c.Assert(err, IsNil) - // Including negative tests, i.e. non existing from user and existing to user - _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") - _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") - - // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. - // Needed to avoid panic due to loc == nil in Time.ConvertTimeZone - se1.GetSessionVars().TimeZone = time.UTC _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost") c.Assert(err, IsNil) From 3f4c242499b6cdf49f01528e6dc10582411a954f Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 11 May 2021 01:24:39 +0200 Subject: [PATCH 36/40] RENAME USER, removed non-needed comment --- executor/simple.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index b3b3a39d80eff..fd6dabeaf75d9 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1086,9 +1086,6 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { break } - // begin a transaction to rename a user. - // Could be restructured with an array of table, user/host columns and break/continue for easier maintenance. - if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.UserTable+" error") break From 407310acfb1ecf303223148cb4db25012dc38132 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 11 May 2021 01:47:35 +0200 Subject: [PATCH 37/40] RENAME USER reverted go.sum changes --- go.mod | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index bf927f9cc55ce..e4080f4eb61d4 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.3 // indirect + honnef.co/go/tools v0.1.4 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 diff --git a/go.sum b/go.sum index 74b4f623789b8..a3ebad580db64 100644 --- a/go.sum +++ b/go.sum @@ -500,6 +500,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A= From fabdd2cc047b3066f4d3844d4bdaedc6370fb83a Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 11 May 2021 01:49:21 +0200 Subject: [PATCH 38/40] RENAME USER reverted go.mod changes --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e4080f4eb61d4..bf927f9cc55ce 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.1.3 // indirect modernc.org/mathutil v1.2.2 // indirect sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 From ad610c818b7e611eafb0635894d607bb64ab6f31 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Tue, 11 May 2021 22:49:24 +0200 Subject: [PATCH 39/40] RENAME USER, removed a temporary fix for internal decoding issue No longer needed. --- executor/mem_reader.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/executor/mem_reader.go b/executor/mem_reader.go index 27ecd6445a3d6..f6023c93c5b1a 100644 --- a/executor/mem_reader.go +++ b/executor/mem_reader.go @@ -14,8 +14,6 @@ package executor import ( - "time" - "github.com/pingcap/errors" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" @@ -243,12 +241,7 @@ func (m *memTableReader) decodeRowData(handle kv.Handle, value []byte) ([]types. ds := make([]types.Datum, 0, len(m.columns)) for _, col := range m.columns { offset := m.colIDs[col.ID] - loc := m.ctx.GetSessionVars().TimeZone - if loc == nil { - // TODO: Warn and fix in upper layer, due to ctx not set correctly? - loc = time.UTC - } - d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, loc) + d, err := tablecodec.DecodeColumnValue(values[offset], &col.FieldType, m.ctx.GetSessionVars().TimeZone) if err != nil { return nil, err } From 2b7615657f29ba4cc71bfe10381e46c83a5fd1ce Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 17 May 2021 02:28:51 +0200 Subject: [PATCH 40/40] RENAME USER, non-logic change, adressed review comments Changed array of string to single string. DROP USER IF EXISTS in tests to make it more stable Changed test to use ErrorMatches to avoid code dependent errors --- executor/simple.go | 28 ++++++++++++------------- privilege/privileges/privileges_test.go | 19 +++++++++-------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/executor/simple.go b/executor/simple.go index fd6dabeaf75d9..e5c6718ab55f6 100644 --- a/executor/simple.go +++ b/executor/simple.go @@ -1053,7 +1053,7 @@ func (e *SimpleExec) executeGrantRole(s *ast.GrantRoleStmt) error { // Should cover same internal mysql.* tables as DROP USER, so this function is very similar func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { - failedUsers := make([]string, 0, len(s.UserToUsers)) + var failedUser string sysSession, err := e.getSysSession() defer e.releaseSysSession(sysSession) if err != nil { @@ -1072,7 +1072,7 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { return err } if !exists { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" old did not exist") + failedUser = oldUser.String() + " TO " + newUser.String() + " old did not exist" break } @@ -1082,59 +1082,59 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { } if exists { // MySQL reports the old user, even when the issue is the new user. - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" new did exist") + failedUser = oldUser.String() + " TO " + newUser.String() + " new did exist" break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.UserTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.UserTable+" error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.UserTable + " error" break } // rename privileges from mysql.global_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.GlobalPrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.GlobalPrivTable+" error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.GlobalPrivTable + " error" break } // rename privileges from mysql.db if err = renameUserHostInSystemTable(sqlExecutor, mysql.DBTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DBTable+" error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.DBTable + " error" break } // rename privileges from mysql.tables_priv if err = renameUserHostInSystemTable(sqlExecutor, mysql.TablePrivTable, "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.TablePrivTable+" error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.TablePrivTable + " error" break } // rename relationship from mysql.role_edges if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "TO_USER", "TO_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (to) error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.RoleEdgeTable + " (to) error" break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.RoleEdgeTable, "FROM_USER", "FROM_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.RoleEdgeTable+" (from) error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.RoleEdgeTable + " (from) error" break } // rename relationship from mysql.default_roles if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "DEFAULT_ROLE_USER", "DEFAULT_ROLE_HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" (default role user) error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.DefaultRoleTable + " (default role user) error" break } if err = renameUserHostInSystemTable(sqlExecutor, mysql.DefaultRoleTable, "USER", "HOST", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" "+mysql.DefaultRoleTable+" error") + failedUser = oldUser.String() + " TO " + newUser.String() + " " + mysql.DefaultRoleTable + " error" break } // rename relationship from mysql.global_grants // TODO: add global_grants into the parser if err = renameUserHostInSystemTable(sqlExecutor, "global_grants", "User", "Host", userToUser); err != nil { - failedUsers = append(failedUsers, oldUser.String()+" TO "+newUser.String()+" mysql.global_grants error") + failedUser = oldUser.String() + " TO " + newUser.String() + " mysql.global_grants error" break } @@ -1143,7 +1143,7 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { // to loop over, so it is easier to maintain. } - if len(failedUsers) == 0 { + if failedUser == "" { if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "commit"); err != nil { return err } @@ -1151,7 +1151,7 @@ func (e *SimpleExec) executeRenameUser(s *ast.RenameUserStmt) error { if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil { return err } - return ErrCannotUser.GenWithStackByArgs("RENAME USER", strings.Join(failedUsers, ",")) + return ErrCannotUser.GenWithStackByArgs("RENAME USER", failedUser) } domain.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx) return nil diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 0fa34284cc3f2..7c33f89342d23 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1396,6 +1396,9 @@ func (s *testPrivilegeSuite) TestSecurityEnhancedModeStatusVars(c *C) { func (s *testPrivilegeSuite) TestRenameUser(c *C) { rootSe := newSession(c, s.store, s.dbName) + mustExec(c, rootSe, "DROP USER IF EXISTS 'ru1'@'localhost'") + mustExec(c, rootSe, "DROP USER IF EXISTS ru3") + mustExec(c, rootSe, "DROP USER IF EXISTS ru6@localhost") mustExec(c, rootSe, "CREATE USER 'ru1'@'localhost'") mustExec(c, rootSe, "CREATE USER ru3") mustExec(c, rootSe, "CREATE USER ru6@localhost") @@ -1404,12 +1407,10 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { // Check privileges (need CREATE USER) _, err := se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") - c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + c.Assert(err, ErrorMatches, ".*Access denied; you need .at least one of. the CREATE USER privilege.s. for this operation") mustExec(c, rootSe, "GRANT UPDATE ON mysql.user TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") - // Workaround for *errors.withStack type - errString := err.Error() - c.Assert(errString, Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation") + c.Assert(err, ErrorMatches, ".*Access denied; you need .at least one of. the CREATE USER privilege.s. for this operation") mustExec(c, rootSe, "GRANT CREATE USER ON *.* TO 'ru1'@'localhost'") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru4") c.Assert(err, IsNil) @@ -1421,15 +1422,15 @@ func (s *testPrivilegeSuite) TestRenameUser(c *C) { c.Assert(err, IsNil) // Including negative tests, i.e. non existing from user and existing to user _, err = rootSe.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru1@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + c.Assert(err, ErrorMatches, ".*Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru4 TO ru5@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + c.Assert(err, ErrorMatches, ".*Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru3") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru3@%.*") + c.Assert(err, ErrorMatches, ".*Operation RENAME USER failed for ru3@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru4 TO ru7") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru4@%.*") + c.Assert(err, ErrorMatches, ".*Operation RENAME USER failed for ru4@%.*") _, err = se1.ExecuteInternal(context.Background(), "RENAME USER ru3 TO ru5@localhost, ru6@localhost TO ru1@localhost") - c.Assert(err.Error(), Matches, "\\[executor:1396\\]Operation RENAME USER failed for ru6@localhost.*") + c.Assert(err, ErrorMatches, ".*Operation RENAME USER failed for ru6@localhost.*") // Test multi rename, this is a full swap of ru3 and ru6, i.e. need to read its previous state in the same transaction. _, err = se1.ExecuteInternal(context.Background(), "RENAME USER 'ru3' TO 'ru3_tmp', ru6@localhost TO ru3, 'ru3_tmp' to ru6@localhost")