Skip to content

Commit

Permalink
*: support "show create user" (#9240)
Browse files Browse the repository at this point in the history
  • Loading branch information
lnhote authored and jackysp committed Feb 21, 2019
1 parent 7a24081 commit aabd330
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 0 deletions.
25 changes: 25 additions & 0 deletions executor/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions executor/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions planner/core/planbuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (s *testPlanBuilderSuite) TestShow(c *C) {
ast.ShowStatus,
ast.ShowCollation,
ast.ShowCreateTable,
ast.ShowCreateUser,
ast.ShowGrants,
ast.ShowTriggers,
ast.ShowProcedureStatus,
Expand Down
3 changes: 3 additions & 0 deletions privilege/privilege.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions privilege/privileges/privileges.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down
7 changes: 7 additions & 0 deletions privilege/privileges/privileges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions util/testkit/testkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down

0 comments on commit aabd330

Please sign in to comment.