Skip to content

Commit

Permalink
*: check visibility for show databases and tables (#2934)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiancaiamao authored Mar 28, 2017
1 parent 0e172ac commit 60ba388
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 6 deletions.
10 changes: 10 additions & 0 deletions executor/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
45 changes: 45 additions & 0 deletions executor/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
}

Expand Down
6 changes: 3 additions & 3 deletions expression/builtin_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -1421,9 +1421,9 @@ func (b *builtinMakeSetSig) eval(row []types.Datum) (d types.Datum, err error) {
continue
}
if arg0&(1<<uint(i-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)
}
Expand Down
6 changes: 3 additions & 3 deletions expression/builtin_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions mysql/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions privilege/privilege.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions privilege/privileges/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions privilege/privileges/privileges.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 60ba388

Please sign in to comment.