diff --git a/executor/builder.go b/executor/builder.go index 6ccddeb8ca22c..3f500b808eaa5 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -522,6 +522,7 @@ func (b *executorBuilder) buildShow(v *plannercore.Show) Executor { Table: v.Table, Column: v.Column, User: v.User, + Roles: v.Roles, IfNotExists: v.IfNotExists, Flag: v.Flag, Full: v.Full, @@ -1253,7 +1254,7 @@ func (b *executorBuilder) buildUpdate(v *plannercore.Update) Executor { // cols2Handle represents an mapper from column index to handle index. type cols2Handle struct { - // start/end represent the ordinal range [start, end) of the consecutive columns. + // start and end represent the ordinal range [start, end) of the consecutive columns. start, end int32 // handleOrdinal represents the ordinal of the handle column. handleOrdinal int32 diff --git a/executor/show.go b/executor/show.go index 6c5e23e602c88..ed0403c214710 100644 --- a/executor/show.go +++ b/executor/show.go @@ -61,8 +61,9 @@ type ShowExec struct { Column *ast.ColumnName // Used for `desc table column`. Flag int // Some flag parsed from sql, such as FULL. Full bool - User *auth.UserIdentity // Used for show grants. - IfNotExists bool // Used for `show create database if not exists` + User *auth.UserIdentity // Used for show grants. + Roles []*auth.RoleIdentity // Used for show grants. + IfNotExists bool // Used for `show create database if not exists` // GlobalScope is used by show variables GlobalScope bool @@ -939,7 +940,15 @@ func (e *ShowExec) fetchShowGrants() error { if checker == nil { return errors.New("miss privilege checker") } - gs, err := checker.ShowGrants(e.ctx, e.User) + for _, r := range e.Roles { + if r.Hostname == "" { + r.Hostname = "%" + } + if !checker.FindEdge(e.ctx, r, e.User) { + return ErrRoleNotGranted.GenWithStackByArgs(r.String(), e.User.String()) + } + } + gs, err := checker.ShowGrants(e.ctx, e.User, e.Roles) if err != nil { return errors.Trace(err) } diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index 4a77d8114d094..6beeeeba536fe 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -318,13 +318,13 @@ type Show struct { Column *ast.ColumnName // Used for `desc table column`. Flag int // Some flag parsed from sql, such as FULL. Full bool - User *auth.UserIdentity // Used for show grants. - IfNotExists bool // Used for `show create database if not exists` + User *auth.UserIdentity // Used for show grants. + Roles []*auth.RoleIdentity // Used for show grants. + IfNotExists bool // Used for `show create database if not exists` Conditions []expression.Expression - // Used by show variables - GlobalScope bool + GlobalScope bool // Used by show variables } // Set represents a plan for set stmt. diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 4e22b38eccd4f..c8b82bf4b21f0 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -1038,6 +1038,7 @@ func (b *PlanBuilder) buildShow(show *ast.ShowStmt) (Plan, error) { Flag: show.Flag, Full: show.Full, User: show.User, + Roles: show.Roles, IfNotExists: show.IfNotExists, GlobalScope: show.GlobalScope, }.Init(b.ctx) diff --git a/privilege/privilege.go b/privilege/privilege.go index e94ae4e9f8dae..332e8229ea93a 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -29,7 +29,7 @@ func (k keyType) String() string { // Manager is the interface for providing privilege related operations. type Manager interface { // ShowGrants shows granted privileges for user. - ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity) ([]string, error) + ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity, roles []*auth.RoleIdentity) ([]string, error) // GetEncodedPassword shows the encoded password for user. GetEncodedPassword(user, host string) string diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index 07055813ae2e3..85121d30440e4 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -119,7 +119,7 @@ type defaultRoleRecord struct { // roleGraphEdgesTable is used to cache relationship between and role. type roleGraphEdgesTable struct { - roleList map[string]bool + roleList map[string]*auth.RoleIdentity } // Find method is used to find role from table @@ -145,6 +145,33 @@ type MySQLPrivilege struct { RoleGraph map[string]roleGraphEdgesTable } +// FindAllRole is used to find all roles grant to this user. +func (p *MySQLPrivilege) FindAllRole(activeRoles []*auth.RoleIdentity) []*auth.RoleIdentity { + queue, head := make([]*auth.RoleIdentity, 0, len(activeRoles)), 0 + for _, r := range activeRoles { + queue = append(queue, r) + } + // Using breadth first search to find all roles grant to this user. + visited, ret := make(map[string]bool), make([]*auth.RoleIdentity, 0) + for head < len(queue) { + role := queue[head] + if _, ok := visited[role.String()]; !ok { + visited[role.String()] = true + ret = append(ret, role) + key := role.Username + "@" + role.Hostname + if edgeTable, ok := p.RoleGraph[key]; ok { + for _, v := range edgeTable.roleList { + if _, ok := visited[v.String()]; !ok { + queue = append(queue, v) + } + } + } + } + head += 1 + } + return ret +} + // FindRole is used to detect whether there is edges between users and roles. func (p *MySQLPrivilege) FindRole(user string, host string, role *auth.RoleIdentity) bool { rec := p.matchUser(user, host) @@ -474,10 +501,10 @@ func (p *MySQLPrivilege) decodeRoleEdgesTable(row chunk.Row, fs []*ast.ResultFie toKey := toUser + "@" + toHost roleGraph, ok := p.RoleGraph[toKey] if !ok { - roleGraph = roleGraphEdgesTable{roleList: make(map[string]bool)} + roleGraph = roleGraphEdgesTable{roleList: make(map[string]*auth.RoleIdentity)} p.RoleGraph[toKey] = roleGraph } - roleGraph.roleList[fromKey] = true + roleGraph.roleList[fromKey] = &auth.RoleIdentity{Username: fromUser, Hostname: fromHost} return nil } @@ -700,21 +727,33 @@ func (p *MySQLPrivilege) DBIsVisible(user, host, db string) bool { return false } -func (p *MySQLPrivilege) showGrants(user, host string) []string { +func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentity) []string { var gs []string var hasGlobalGrant bool = false - // Show global grants + // Some privileges may granted from role inheritance. + // We should find these inheritance relationship. + allRoles := p.FindAllRole(roles) + // Show global grants. + var currentPriv mysql.PrivilegeType + var g string for _, record := range p.User { if record.User == user && record.Host == host { hasGlobalGrant = true - g := userPrivToString(record.Privileges) - if len(g) > 0 { - s := fmt.Sprintf(`GRANT %s ON *.* TO '%s'@'%s'`, g, record.User, record.Host) - gs = append(gs, s) + currentPriv |= record.Privileges + } else { + for _, r := range allRoles { + if record.User == r.Username && record.Host == r.Hostname { + hasGlobalGrant = true + currentPriv |= record.Privileges + } } - break // it's unique } } + g = userPrivToString(currentPriv) + if len(g) > 0 { + s := fmt.Sprintf(`GRANT %s ON *.* TO '%s'@'%s'`, g, user, host) + gs = append(gs, s) + } // This is a mysql convention. if len(gs) == 0 && hasGlobalGrant { @@ -722,28 +761,81 @@ func (p *MySQLPrivilege) showGrants(user, host string) []string { gs = append(gs, s) } - // Show db scope grants + // Show db scope grants. + dbPrivTable := make(map[string]mysql.PrivilegeType) for _, record := range p.DB { if record.User == user && record.Host == host { - g := dbPrivToString(record.Privileges) - if len(g) > 0 { - s := fmt.Sprintf(`GRANT %s ON %s.* TO '%s'@'%s'`, g, record.DB, record.User, record.Host) - gs = append(gs, s) + if _, ok := dbPrivTable[record.DB]; ok { + dbPrivTable[record.DB] |= record.Privileges + } else { + dbPrivTable[record.DB] = record.Privileges + } + } else { + for _, r := range allRoles { + if record.User == r.Username && record.Host == r.Hostname { + if _, ok := dbPrivTable[record.DB]; ok { + dbPrivTable[record.DB] |= record.Privileges + } else { + dbPrivTable[record.DB] = record.Privileges + } + } } } } + for dbName, priv := range dbPrivTable { + g := dbPrivToString(priv) + if len(g) > 0 { + s := fmt.Sprintf(`GRANT %s ON %s.* TO '%s'@'%s'`, g, dbName, user, host) + gs = append(gs, s) + } + } - // Show table scope grants + // Show table scope grants. + tablePrivTable := make(map[string]mysql.PrivilegeType) for _, record := range p.TablesPriv { + recordKey := record.DB + "." + record.TableName if record.User == user && record.Host == host { - g := tablePrivToString(record.TablePriv) - if len(g) > 0 { - s := fmt.Sprintf(`GRANT %s ON %s.%s TO '%s'@'%s'`, g, record.DB, record.TableName, record.User, record.Host) - gs = append(gs, s) + if _, ok := dbPrivTable[record.DB]; ok { + tablePrivTable[recordKey] |= record.TablePriv + } else { + tablePrivTable[recordKey] = record.TablePriv + } + } else { + for _, r := range allRoles { + if record.User == r.Username && record.Host == r.Hostname { + if _, ok := dbPrivTable[record.DB]; ok { + tablePrivTable[recordKey] |= record.TablePriv + } else { + tablePrivTable[recordKey] = record.TablePriv + } + } } } } + for k, priv := range tablePrivTable { + g := tablePrivToString(priv) + if len(g) > 0 { + s := fmt.Sprintf(`GRANT %s ON %s TO '%s'@'%s'`, g, k, user, host) + gs = append(gs, s) + } + } + // Show role grants. + graphKey := user + "@" + host + edgeTable, ok := p.RoleGraph[graphKey] + g = "" + if ok { + for k := range edgeTable.roleList { + role := strings.Split(k, "@") + roleName, roleHost := role[0], role[1] + if g != "" { + g += ", " + } + g += fmt.Sprintf("'%s'@'%s'", roleName, roleHost) + } + s := fmt.Sprintf(`GRANT %s TO '%s'@'%s'`, g, user, host) + gs = append(gs, s) + } return gs } diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 98683977255cb..25e35b17027e7 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -175,7 +175,7 @@ func (p *UserPrivileges) UserPrivilegesTable() [][]types.Datum { } // ShowGrants implements privilege.Manager ShowGrants interface. -func (p *UserPrivileges) ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity) (grants []string, err error) { +func (p *UserPrivileges) ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity, roles []*auth.RoleIdentity) (grants []string, err error) { mysqlPrivilege := p.Handle.Get() u := user.Username h := user.Hostname @@ -183,7 +183,7 @@ func (p *UserPrivileges) ShowGrants(ctx sessionctx.Context, user *auth.UserIdent u = user.AuthUsername h = user.AuthHostname } - grants = mysqlPrivilege.showGrants(u, h) + grants = mysqlPrivilege.showGrants(u, h, roles) if len(grants) == 0 { err = errNonexistingGrant.GenWithStackByArgs(u, h) } diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 1767d6bdd761e..85dc130203fc6 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -147,34 +147,34 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { mustExec(c, se, `GRANT Index ON *.* TO 'show'@'localhost';`) pc := privilege.GetPrivilegeManager(se) - gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "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 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"}) + 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"}) + 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"}) + 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"}) + 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'`, @@ -182,7 +182,7 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { 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"}) + 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'`, @@ -191,7 +191,7 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { 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"}) + 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'`, @@ -201,7 +201,7 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { // 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"}) + 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'`, @@ -215,7 +215,7 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { 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"}) + 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'`) @@ -226,11 +226,51 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { mustExec(c, se, `DROP USER 'show'@'localhost'`) // This should now return an error - _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}) + _, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) c.Assert(err, NotNil) // cant show grants for non-existent errNonexistingGrant := terror.ClassPrivilege.New(mysql.ErrNonexistingGrant, mysql.MySQLErrName[mysql.ErrNonexistingGrant]) c.Assert(terror.ErrorEqual(err, 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, `GRANT UPDATE ON a.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) TestDropTablePriv(c *C) {