From aabd330d1eeceb7f3de367c82e71dcc71418853b Mon Sep 17 00:00:00 2001 From: Hunter Lin Date: Thu, 21 Feb 2019 13:58:19 +0800 Subject: [PATCH] *: support "show create user" (#9240) --- executor/show.go | 25 +++++++++++++++++++++++++ executor/show_test.go | 21 +++++++++++++++++++++ planner/core/planbuilder.go | 4 ++++ planner/core/planbuilder_test.go | 1 + privilege/privilege.go | 3 +++ privilege/privileges/privileges.go | 16 ++++++++++++++++ privilege/privileges/privileges_test.go | 7 +++++++ util/testkit/testkit.go | 11 +++++++++++ 8 files changed, 88 insertions(+) diff --git a/executor/show.go b/executor/show.go index 9c7c5955bc5bd..d10c707329a62 100644 --- a/executor/show.go +++ b/executor/show.go @@ -107,6 +107,8 @@ func (e *ShowExec) fetchAll() error { return e.fetchShowColumns() case ast.ShowCreateTable: return e.fetchShowCreateTable() + case ast.ShowCreateUser: + return e.fetchShowCreateUser() case ast.ShowCreateView: return e.fetchShowCreateView() case ast.ShowCreateDatabase: @@ -883,6 +885,29 @@ func (e *ShowExec) fetchShowCollation() error { return nil } +// fetchShowCreateUser composes show create create user result. +func (e *ShowExec) fetchShowCreateUser() error { + checker := privilege.GetPrivilegeManager(e.ctx) + if checker == nil { + return errors.New("miss privilege checker") + } + sql := fmt.Sprintf(`SELECT * FROM %s.%s WHERE User='%s' AND Host='%s';`, + mysql.SystemDB, mysql.UserTable, e.User.Username, e.User.Hostname) + rows, _, err := e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql) + if err != nil { + return errors.Trace(err) + } + if len(rows) == 0 { + return ErrCannotUser.GenWithStackByArgs("SHOW CREATE USER", + fmt.Sprintf("'%s'@'%s'", e.User.Username, e.User.Hostname)) + } + showStr := fmt.Sprintf("CREATE USER '%s'@'%s' IDENTIFIED WITH 'mysql_native_password' AS '%s' %s", + e.User.Username, e.User.Hostname, checker.GetEncodedPassword(e.User.Username, e.User.Hostname), + "REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK") + e.appendRow([]interface{}{showStr}) + return nil +} + func (e *ShowExec) fetchShowGrants() error { // Get checker checker := privilege.GetPrivilegeManager(e.ctx) diff --git a/executor/show_test.go b/executor/show_test.go index 1a8bdbe641b7f..c60cdf792f997 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -23,6 +23,7 @@ import ( "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/domain" + "github.com/pingcap/tidb/executor" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/sessionctx" @@ -201,6 +202,26 @@ func (s *testSuite2) TestShow2(c *C) { tk.MustQuery("show grants for current_user").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'`)) } +func (s *testSuite2) TestShow3(c *C) { + tk := testkit.NewTestKit(c, s.store) + // Create a new user. + tk.MustExec(`CREATE USER 'test_show_create_user'@'%' IDENTIFIED BY 'root';`) + tk.MustQuery("show create user 'test_show_create_user'@'%'"). + Check(testkit.Rows(`CREATE USER 'test_show_create_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK`)) + + tk.MustExec(`CREATE USER 'test_show_create_user'@'localhost' IDENTIFIED BY 'test';`) + tk.MustQuery("show create user 'test_show_create_user'@'localhost';"). + Check(testkit.Rows(`CREATE USER 'test_show_create_user'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK`)) + + // Case: the user exists but the host portion doesn't match + err := tk.QueryToErr("show create user 'test_show_create_user'@'asdf';") + c.Assert(err.Error(), Equals, executor.ErrCannotUser.GenWithStackByArgs("SHOW CREATE USER", "'test_show_create_user'@'asdf'").Error()) + + // Case: a user that doesn't exist + err = tk.QueryToErr("show create user 'aaa'@'localhost';") + c.Assert(err.Error(), Equals, executor.ErrCannotUser.GenWithStackByArgs("SHOW CREATE USER", "'aaa'@'localhost'").Error()) +} + func (s *testSuite2) TestUnprivilegedShow(c *C) { tk := testkit.NewTestKit(c, s.store) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 750fe14117160..7683bd33f1701 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -1747,6 +1747,10 @@ func buildShowSchema(s *ast.ShowStmt, isView bool) (schema *expression.Schema) { } else { names = []string{"View", "Create View", "character_set_client", "collation_connection"} } + case ast.ShowCreateUser: + if s.User != nil { + names = []string{fmt.Sprintf("CREATE USER for %s", s.User)} + } case ast.ShowCreateView: names = []string{"View", "Create View", "character_set_client", "collation_connection"} case ast.ShowCreateDatabase: diff --git a/planner/core/planbuilder_test.go b/planner/core/planbuilder_test.go index 8eb2e9623be53..3c1487d1a3637 100644 --- a/planner/core/planbuilder_test.go +++ b/planner/core/planbuilder_test.go @@ -43,6 +43,7 @@ func (s *testPlanBuilderSuite) TestShow(c *C) { ast.ShowStatus, ast.ShowCollation, ast.ShowCreateTable, + ast.ShowCreateUser, ast.ShowGrants, ast.ShowTriggers, ast.ShowProcedureStatus, diff --git a/privilege/privilege.go b/privilege/privilege.go index 8351470f134fa..7091f96effee3 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -31,6 +31,9 @@ type Manager interface { // ShowGrants shows granted privileges for user. ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity) ([]string, error) + // GetEncodedPassword shows the encoded password for user. + GetEncodedPassword(user, host string) string + // RequestVerification verifies user privilege for the request. // If table is "", only check global/db scope privileges. // If table is not "", check global/db/table scope privileges. diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 9da9aa72ba345..6d0b7e71965e3 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -77,6 +77,22 @@ func (p *UserPrivileges) RequestVerificationWithUser(db, table, column string, p return mysqlPriv.RequestVerification(user.Username, user.Hostname, db, table, column, priv) } +// GetEncodedPassword implements the Manager interface. +func (p *UserPrivileges) GetEncodedPassword(user, host string) string { + mysqlPriv := p.Handle.Get() + record := mysqlPriv.connectionVerification(user, host) + if record == nil { + log.Errorf("Get user privilege record fail: user %v, host %v", user, host) + return "" + } + pwd := record.Password + if len(pwd) != 0 && len(pwd) != mysql.PWDHashLen+1 { + log.Errorf("User [%s] password from SystemDB not like a sha1sum", user) + return "" + } + return pwd +} + // ConnectionVerification implements the Manager interface. func (p *UserPrivileges) ConnectionVerification(user, host string, authentication, salt []byte) (u string, h string, success bool) { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 8235a795ce684..cf9e7cb28ac70 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -456,6 +456,13 @@ func (s *testPrivilegeSuite) TestAdminCommand(c *C) { c.Assert(err, IsNil) } +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 mustExec(c *C, se session.Session, sql string) { _, err := se.Execute(context.Background(), sql) c.Assert(err, IsNil) diff --git a/util/testkit/testkit.go b/util/testkit/testkit.go index f8a023e3cf943..2eaf3302cbebb 100644 --- a/util/testkit/testkit.go +++ b/util/testkit/testkit.go @@ -185,6 +185,17 @@ func (tk *TestKit) MustQuery(sql string, args ...interface{}) *Result { return tk.ResultSetToResult(rs, comment) } +// QueryToErr executes a sql statement and discard results. +func (tk *TestKit) QueryToErr(sql string, args ...interface{}) error { + comment := check.Commentf("sql:%s, args:%v", sql, args) + res, err := tk.Exec(sql, args...) + tk.c.Assert(errors.ErrorStack(err), check.Equals, "", comment) + tk.c.Assert(res, check.NotNil, comment) + _, resErr := session.GetRows4Test(context.Background(), tk.Se, res) + tk.c.Assert(res.Close(), check.IsNil) + return resErr +} + // ExecToErr executes a sql statement and discard results. func (tk *TestKit) ExecToErr(sql string, args ...interface{}) error { res, err := tk.Exec(sql, args...)