diff --git a/executor/show.go b/executor/show.go index db9105e108c6c..db242e97c4820 100644 --- a/executor/show.go +++ b/executor/show.go @@ -138,9 +138,13 @@ func (e *ShowExec) fetchShowEngines() error { func (e *ShowExec) fetchShowDatabases() error { dbs := e.is.AllSchemaNames() + checker := privilege.GetPrivilegeChecker(e.ctx) // TODO: let information_schema be the first database sort.Strings(dbs) for _, d := range dbs { + if checker != nil && !checker.DBIsVisible(d) { + continue + } e.rows = append(e.rows, &Row{Data: types.MakeDatums(d)}) } return nil @@ -179,9 +183,15 @@ func (e *ShowExec) fetchShowTables() error { if !e.is.SchemaExists(e.DBName) { return errors.Errorf("Can not find DB: %s", e.DBName) } + checker := privilege.GetPrivilegeChecker(e.ctx) // sort for tables var tableNames []string for _, v := range e.is.SchemaTables(e.DBName) { + // Test with mysql.AllPrivMask means any privilege would be OK. + // TODO: Should consider column privileges, which also make a table visible. + if checker != nil && !checker.RequestVerification(e.DBName.O, v.Meta().Name.O, "", mysql.AllPrivMask) { + continue + } tableNames = append(tableNames, v.Meta().Name.O) } sort.Strings(tableNames) diff --git a/executor/show_test.go b/executor/show_test.go index 87acc81e54b52..bb1318b279293 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -17,6 +17,8 @@ import ( "strings" . "github.com/pingcap/check" + "github.com/pingcap/tidb" + "github.com/pingcap/tidb/privilege/privileges" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" @@ -131,6 +133,49 @@ func (s *testSuite) TestShow(c *C) { c.Check(result.Rows(), HasLen, 1) } +func (s *testSuite) TestShowVisibility(c *C) { + save := privileges.Enable + privileges.Enable = true + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create database showdatabase") + tk.MustExec("use showdatabase") + tk.MustExec("create table t1 (id int)") + tk.MustExec("create table t2 (id int)") + tk.MustExec(`create user 'show'@'%'`) + + se, err := tidb.CreateSession(s.store) + c.Assert(err, IsNil) + c.Assert(se.Auth(`show@%`, nil, nil), IsTrue) + + // No ShowDatabases privilege, this user would see nothing. + rs, err := se.Execute("show databases") + c.Assert(err, IsNil) + rows, err := tidb.GetRows(rs[0]) + c.Assert(err, IsNil) + c.Assert(rows, HasLen, 0) + + // After grant, the user can see the database. + tk.MustExec(`grant select on showdatabase.t1 to 'show'@'%'`) + rs, err = se.Execute("show databases") + c.Assert(err, IsNil) + rows, err = tidb.GetRows(rs[0]) + c.Assert(err, IsNil) + c.Assert(rows, HasLen, 1) + + _, err = se.Execute("use showdatabase") + c.Assert(err, IsNil) + rs, err = se.Execute("show tables") + c.Assert(err, IsNil) + rows, err = tidb.GetRows(rs[0]) + c.Assert(err, IsNil) + // The user can see t2 but not t1. + c.Assert(rows, HasLen, 1) + + privileges.Enable = save + tk.MustExec(`drop user 'show'@'%'`) + tk.MustExec("drop database showdatabase") +} + type stats struct { } diff --git a/expression/builtin_string.go b/expression/builtin_string.go index 6ec1aa1129764..64857ecec8088 100644 --- a/expression/builtin_string.go +++ b/expression/builtin_string.go @@ -1421,9 +1421,9 @@ func (b *builtinMakeSetSig) eval(row []types.Datum) (d types.Datum, err error) { continue } if arg0&(1< 0 { - str, err := args[i].ToString() - if err != nil { - return d, errors.Trace(err) + str, err1 := args[i].ToString() + if err1 != nil { + return d, errors.Trace(err1) } sets = append(sets, str) } diff --git a/expression/builtin_time.go b/expression/builtin_time.go index 3c150f1a6caa5..f0fd6f5c53664 100644 --- a/expression/builtin_time.go +++ b/expression/builtin_time.go @@ -1560,9 +1560,9 @@ func (b *builtinTimestampSig) eval(row []types.Datum) (d types.Datum, err error) return d, errors.Trace(err) } case types.KindString, types.KindBytes, types.KindMysqlDecimal, types.KindFloat32, types.KindFloat64: - s, err := args[0].ToString() - if err != nil { - return d, errors.Trace(err) + s, err1 := args[0].ToString() + if err1 != nil { + return d, errors.Trace(err1) } arg0, err = types.ParseTime(s, mysql.TypeDatetime, getFsp(s)) if err != nil { diff --git a/mysql/const.go b/mysql/const.go index ca40d96a2da98..cc4f6d2bf9334 100644 --- a/mysql/const.go +++ b/mysql/const.go @@ -182,6 +182,9 @@ const ( AllPriv ) +// AllPrivMask is the mask for PrivilegeType with all bits set to 1. +const AllPrivMask = AllPriv - 1 + // Priv2UserCol is the privilege to mysql.user table column name. var Priv2UserCol = map[PrivilegeType]string{ CreatePriv: "Create_priv", diff --git a/privilege/privilege.go b/privilege/privilege.go index 30639a5378cf9..bddf1d86a9d6c 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -38,6 +38,9 @@ type Checker interface { RequestVerification(db, table, column string, priv mysql.PrivilegeType) bool // ConnectionVerification verifies user privilege for connection. ConnectionVerification(host, user string, auth, salt []byte) bool + + // DBIsVisible returns true is the database is visible to current user. + DBIsVisible(db string) bool } const key keyType = 0 diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index 53ee595521a7c..e059f735c2c90 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -418,6 +418,43 @@ func (p *MySQLPrivilege) RequestVerification(user, host, db, table, column strin return false } +// DBIsVisible checks whether the user can see the db. +func (p *MySQLPrivilege) DBIsVisible(user, host, db string) bool { + if record := p.matchUser(user, host); record != nil { + if record.Privileges&mysql.ShowDBPriv > 0 { + return true + } + } + + if record := p.matchDB(user, host, db); record != nil { + if record.Privileges > 0 { + return true + } + } + + for _, record := range p.TablesPriv { + if record.User == user && + patternMatch(host, record.patChars, record.patTypes) && + strings.EqualFold(record.DB, db) { + if record.TablePriv != 0 || record.ColumnPriv != 0 { + return true + } + } + } + + for _, record := range p.ColumnsPriv { + if record.User == user && + patternMatch(host, record.patChars, record.patTypes) && + strings.EqualFold(record.DB, db) { + if record.ColumnPriv != 0 { + return true + } + } + } + + return false +} + // Handle wraps MySQLPrivilege providing thread safe access. type Handle struct { ctx context.Context diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 0d060924e0f62..97a3d9fa42393 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -246,6 +246,31 @@ func (p *UserPrivileges) ConnectionVerification(user, host string, auth, salt [] return true } +// DBIsVisible implements the Checker interface. +func (p *UserPrivileges) DBIsVisible(db string) bool { + if !Enable || SkipWithGrant { + return true + } + + if p.User == "" { + return true + } + + mysqlPriv := p.Handle.Get() + + // TODO: Store it to UserPrivileges and avoid do it everytime. + strs := strings.Split(p.User, "@") + if len(strs) != 2 { + log.Warnf("Invalid format for user: %s", p.User) + return false + } + // Get user password. + user := strs[0] + host := strs[1] + + return mysqlPriv.DBIsVisible(user, host, db) +} + // Check implements Checker.Check interface. func (p *UserPrivileges) Check(ctx context.Context, db *model.DBInfo, tbl *model.TableInfo, privilege mysql.PrivilegeType) (bool, error) { if p.privs == nil {