diff --git a/executor/grant.go b/executor/grant.go index 8b99bb8ffdcbb..d4a6fac507771 100644 --- a/executor/grant.go +++ b/executor/grant.go @@ -302,8 +302,8 @@ func (e *GrantExec) grantColumnPriv(priv *ast.PrivElem, user *ast.UserSpec) erro func composeGlobalPrivUpdate(priv mysql.PrivilegeType, value string) (string, error) { if priv == mysql.AllPriv { strs := make([]string, 0, len(mysql.Priv2UserCol)) - for _, v := range mysql.Priv2UserCol { - strs = append(strs, fmt.Sprintf(`%s='%s'`, v, value)) + for _, v := range mysql.AllGlobalPrivs { + strs = append(strs, fmt.Sprintf(`%s='%s'`, mysql.Priv2UserCol[v], value)) } return strings.Join(strs, ", "), nil } diff --git a/executor/grant_test.go b/executor/grant_test.go index 0016e0b96c6f1..b4fb7b856d566 100644 --- a/executor/grant_test.go +++ b/executor/grant_test.go @@ -14,6 +14,7 @@ package executor_test import ( + "bytes" "fmt" "strings" @@ -53,6 +54,12 @@ func (s *testSuite3) TestGrantGlobal(c *C) { sql := fmt.Sprintf("SELECT %s FROM mysql.User WHERE User=\"testGlobal1\" and host=\"localhost\"", mysql.Priv2UserCol[v]) tk.MustQuery(sql).Check(testkit.Rows("Y")) } + //with grant option + tk.MustExec("GRANT ALL ON *.* TO 'testGlobal1'@'localhost' WITH GRANT OPTION;") + for _, v := range mysql.AllGlobalPrivs { + sql := fmt.Sprintf("SELECT %s FROM mysql.User WHERE User=\"testGlobal1\" and host=\"localhost\"", mysql.Priv2UserCol[v]) + tk.MustQuery(sql).Check(testkit.Rows("Y")) + } } func (s *testSuite3) TestGrantDBScope(c *C) { @@ -96,6 +103,13 @@ func (s *testSuite3) TestWithGrantOption(c *C) { // Grant select priv to the user, with grant option. tk.MustExec("GRANT select ON test.* TO 'testWithGrant'@'localhost' WITH GRANT OPTION;") tk.MustQuery("SELECT grant_priv FROM mysql.DB WHERE User=\"testWithGrant\" and host=\"localhost\" and db=\"test\"").Check(testkit.Rows("Y")) + + tk.MustExec("CREATE USER 'testWithGrant1'") + tk.MustQuery("SELECT grant_priv FROM mysql.user WHERE User=\"testWithGrant1\"").Check(testkit.Rows("N")) + tk.MustExec("GRANT ALL ON *.* TO 'testWithGrant1'") + tk.MustQuery("SELECT grant_priv FROM mysql.user WHERE User=\"testWithGrant1\"").Check(testkit.Rows("N")) + tk.MustExec("GRANT ALL ON *.* TO 'testWithGrant1' WITH GRANT OPTION") + tk.MustQuery("SELECT grant_priv FROM mysql.user WHERE User=\"testWithGrant1\"").Check(testkit.Rows("Y")) } func (s *testSuite3) TestTableScope(c *C) { @@ -124,7 +138,7 @@ func (s *testSuite3) TestTableScope(c *C) { tk.MustExec("USE test;") tk.MustExec(`CREATE TABLE test2(c1 int);`) // Grant all table scope privs. - tk.MustExec("GRANT ALL ON test2 TO 'testTbl1'@'localhost';") + tk.MustExec("GRANT ALL ON test2 TO 'testTbl1'@'localhost' WITH GRANT OPTION;") // Make sure all the table privs for granted user are in the Table_priv set. for _, v := range mysql.AllTablePrivs { rows := tk.MustQuery(`SELECT Table_priv FROM mysql.Tables_priv WHERE User="testTbl1" and host="localhost" and db="test" and Table_name="test2";`).Rows() @@ -224,3 +238,28 @@ func (s *testSuite3) TestGrantUnderANSIQuotes(c *C) { tk.MustExec(`REVOKE ALL PRIVILEGES ON video_ulimit.* FROM web@'%';`) tk.MustExec(`DROP USER IF EXISTS 'web'@'%'`) } + +func (s *testSuite3) TestUserTableConsistency(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("create user superadmin") + tk.MustExec("grant all privileges on *.* to 'superadmin'") + + // GrantPriv is not in AllGlobalPrivs any more, see pingcap/parser#581 + c.Assert(len(mysql.Priv2UserCol), Equals, len(mysql.AllGlobalPrivs)+1) + + var buf bytes.Buffer + var res bytes.Buffer + buf.WriteString("select ") + i := 0 + for _, priv := range mysql.AllGlobalPrivs { + if i != 0 { + buf.WriteString(", ") + res.WriteString(" ") + } + buf.WriteString(mysql.Priv2UserCol[priv]) + res.WriteString("Y") + i++ + } + buf.WriteString(" from mysql.user where user = 'superadmin'") + tk.MustQuery(buf.String()).Check(testkit.Rows(res.String())) +} diff --git a/executor/revoke_test.go b/executor/revoke_test.go index f3a00c0a3e947..71cf1d68fd713 100644 --- a/executor/revoke_test.go +++ b/executor/revoke_test.go @@ -82,7 +82,7 @@ func (s *testSuite1) TestRevokeTableScope(c *C) { // Make sure all the table privs for new user is Y. res := tk.MustQuery(`SELECT Table_priv FROM mysql.tables_priv WHERE User="testTblRevoke" and host="localhost" and db="test" and Table_name="test1"`) - res.Check(testkit.Rows("Select,Insert,Update,Delete,Create,Drop,Grant,Index,Alter")) + res.Check(testkit.Rows("Select,Insert,Update,Delete,Create,Drop,Index,Alter")) // Revoke each priv from the user. for _, v := range mysql.AllTablePrivs { diff --git a/executor/show_test.go b/executor/show_test.go index 559a7f1286e17..ef08c6d7c40f7 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -286,10 +286,11 @@ func (s *testSuite2) TestShow2(c *C) { tk.MustQuery("show databases like 'test'").Check(testkit.Rows("test")) tk.MustExec(`grant all on *.* to 'root'@'%'`) - tk.MustQuery("show grants").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'`)) - tk.MustQuery("show grants for current_user()").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'`)) - tk.MustQuery("show grants for current_user").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'`)) + tk.MustQuery("show grants").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) + + tk.MustQuery("show grants for current_user()").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) + tk.MustQuery("show grants for current_user").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) } func (s *testSuite2) TestShowCreateUser(c *C) { diff --git a/go.mod b/go.mod index ede41f8384076..998b3db735c94 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e github.com/pingcap/kvproto v0.0.0-20191106014506-c5d88d699a8d github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd - github.com/pingcap/parser v0.0.0-20191205094455-91f36bd4dbcc + github.com/pingcap/parser v0.0.0-20191209061101-9504797d375e github.com/pingcap/pd v1.1.0-beta.0.20190912093418-dc03c839debd github.com/pingcap/tidb-tools v3.0.6-0.20191119150227-ff0a3c6e5763+incompatible github.com/pingcap/tipb v0.0.0-20191120045257-1b9900292ab6 diff --git a/go.sum b/go.sum index bdb3a430b7d41..0e7d04359be7e 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ github.com/pingcap/kvproto v0.0.0-20191106014506-c5d88d699a8d h1:zTHgLr8+0LTEJmj github.com/pingcap/kvproto v0.0.0-20191106014506-c5d88d699a8d/go.mod h1:QMdbTAXCHzzygQzqcG9uVUgU2fKeSN1GmfMiykdSzzY= github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd h1:hWDol43WY5PGhsh3+8794bFHY1bPrmu6bTalpssCrGg= github.com/pingcap/log v0.0.0-20190715063458-479153f07ebd/go.mod h1:WpHUKhNZ18v116SvGrmjkA9CBhYmuUTKL+p8JC9ANEw= -github.com/pingcap/parser v0.0.0-20191205094455-91f36bd4dbcc h1:xlLnj8xnQPR84gxR9a1G+7E5T9MSXGk62DQasfBQ1wY= -github.com/pingcap/parser v0.0.0-20191205094455-91f36bd4dbcc/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= +github.com/pingcap/parser v0.0.0-20191209061101-9504797d375e h1:o7gGWvwtOmGYEH1vY5WkyXurch3ji9iTiLWtmpTvgrk= +github.com/pingcap/parser v0.0.0-20191209061101-9504797d375e/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= github.com/pingcap/pd v1.1.0-beta.0.20190912093418-dc03c839debd h1:bKj6hodu/ro78B0oN2yicdGn0t4yd9XjnyoW95qmWic= github.com/pingcap/pd v1.1.0-beta.0.20190912093418-dc03c839debd/go.mod h1:I7TEby5BHTYIxgHszfsOJSBsk8b2Qt8QrSIgdv5n5QQ= github.com/pingcap/tidb-tools v3.0.6-0.20191119150227-ff0a3c6e5763+incompatible h1:I8HirWsu1MZp6t9G/g8yKCEjJJxtHooKakEgccvdJ4M= diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index 699bfe616dacc..9c27fbd54b578 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -758,15 +758,26 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit allRoles := p.FindAllRole(roles) // Show global grants. var currentPriv mysql.PrivilegeType + var hasGrantOptionPriv bool = false var g string for _, record := range p.User { if record.User == user && record.Host == host { hasGlobalGrant = true + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + currentPriv |= (record.Privileges & ^mysql.GrantPriv) + continue + } currentPriv |= record.Privileges } else { for _, r := range allRoles { if record.User == r.Username && record.Host == r.Hostname { hasGlobalGrant = true + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + currentPriv |= (record.Privileges & ^mysql.GrantPriv) + continue + } currentPriv |= record.Privileges } } @@ -774,13 +785,25 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit } g = userPrivToString(currentPriv) if len(g) > 0 { - s := fmt.Sprintf(`GRANT %s ON *.* TO '%s'@'%s'`, g, user, host) + var s string + if hasGrantOptionPriv { + s = fmt.Sprintf(`GRANT %s ON *.* TO '%s'@'%s' WITH GRANT OPTION`, g, user, host) + + } else { + 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 { - s := fmt.Sprintf("GRANT USAGE ON *.* TO '%s'@'%s'", user, host) + var s string + if hasGrantOptionPriv { + s = fmt.Sprintf("GRANT USAGE ON *.* TO '%s'@'%s' WITH GRANT OPTION", user, host) + } else { + s = fmt.Sprintf("GRANT USAGE ON *.* TO '%s'@'%s'", user, host) + } gs = append(gs, s) } @@ -789,16 +812,36 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit for _, record := range p.DB { if record.User == user && record.Host == host { if _, ok := dbPrivTable[record.DB]; ok { + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + dbPrivTable[record.DB] |= (record.Privileges & ^mysql.GrantPriv) + continue + } dbPrivTable[record.DB] |= record.Privileges } else { + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + dbPrivTable[record.DB] = (record.Privileges & ^mysql.GrantPriv) + continue + } 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 { + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + dbPrivTable[record.DB] |= (record.Privileges & ^mysql.GrantPriv) + continue + } dbPrivTable[record.DB] |= record.Privileges } else { + if (record.Privileges & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + dbPrivTable[record.DB] = (record.Privileges & ^mysql.GrantPriv) + continue + } dbPrivTable[record.DB] = record.Privileges } } @@ -808,7 +851,14 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit 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) + var s string + if hasGrantOptionPriv { + s = fmt.Sprintf(`GRANT %s ON %s.* TO '%s'@'%s' WITH GRANT OPTION`, g, dbName, user, host) + + } else { + s = fmt.Sprintf(`GRANT %s ON %s.* TO '%s'@'%s'`, g, dbName, user, host) + + } gs = append(gs, s) } } @@ -819,16 +869,36 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit recordKey := record.DB + "." + record.TableName if record.User == user && record.Host == host { if _, ok := dbPrivTable[record.DB]; ok { + if (record.TablePriv & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + tablePrivTable[recordKey] |= (record.TablePriv & ^mysql.GrantPriv) + continue + } tablePrivTable[recordKey] |= record.TablePriv } else { + if (record.TablePriv & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + tablePrivTable[recordKey] = (record.TablePriv & ^mysql.GrantPriv) + continue + } tablePrivTable[recordKey] = record.TablePriv } } else { for _, r := range allRoles { if record.User == r.Username && record.Host == r.Hostname { if _, ok := dbPrivTable[record.DB]; ok { + if (record.TablePriv & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + tablePrivTable[recordKey] |= (record.TablePriv & ^mysql.GrantPriv) + continue + } tablePrivTable[recordKey] |= record.TablePriv } else { + if (record.TablePriv & mysql.GrantPriv) > 0 { + hasGrantOptionPriv = true + tablePrivTable[recordKey] = (record.TablePriv & ^mysql.GrantPriv) + continue + } tablePrivTable[recordKey] = record.TablePriv } } @@ -838,7 +908,12 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit 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) + var s string + if hasGrantOptionPriv { + s = fmt.Sprintf(`GRANT %s ON %s TO '%s'@'%s' WITH GRANT OPTION`, g, k, user, host) + } else { + s = fmt.Sprintf(`GRANT %s ON %s TO '%s'@'%s'`, g, k, user, host) + } gs = append(gs, s) } } @@ -920,9 +995,6 @@ func appendUserPrivilegesTableRow(rows [][]types.Datum, user UserRecord) [][]typ guarantee := fmt.Sprintf("'%s'@'%s'", user.User, user.Host) for _, priv := range mysql.AllGlobalPrivs { - if priv == mysql.GrantPriv { - continue - } if user.Privileges&priv > 0 { privilegeType := mysql.Priv2Str[priv] // +---------------------------+---------------+-------------------------+--------------+ diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 34609149776d9..ac10d3fa96ba4 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -262,6 +262,20 @@ func (s *testPrivilegeSuite) TestShowGrants(c *C) { c.Assert(gs, HasLen, 1) c.Assert(gs[0], Equals, `GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`) + // All privileges with grant option + mustExec(c, se, `GRANT ALL ON *.* TO 'show'@'localhost' WITH GRANT OPTION;`) + 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' WITH GRANT OPTION`) + + // Revoke grant option + mustExec(c, se, `REVOKE GRANT OPTION ON *.* FROM 'show'@'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"}, nil) @@ -490,6 +504,14 @@ func (s *testPrivilegeSuite) TestUseDB(c *C) { mustExec(c, se, "CREATE USER 'usesuper'") mustExec(c, se, "CREATE USER 'usenobody'") mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper'") + //without grant option + c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) + _, e := se.Execute(context.Background(), "GRANT SELECT ON mysql.* TO 'usenobody'") + c.Assert(e, NotNil) + //with grant option + se = newSession(c, s.store, s.dbName) + // high privileged user + mustExec(c, se, "GRANT ALL ON *.* TO 'usesuper' WITH GRANT OPTION") c.Assert(se.Auth(&auth.UserIdentity{Username: "usesuper", Hostname: "localhost", AuthUsername: "usesuper", AuthHostname: "%"}, nil, nil), IsTrue) mustExec(c, se, "use mysql") // low privileged user @@ -536,7 +558,7 @@ func (s *testPrivilegeSuite) TestSetGlobal(c *C) { func (s *testPrivilegeSuite) TestCreateDropUser(c *C) { se := newSession(c, s.store, s.dbName) mustExec(c, se, `CREATE USER tcd1, tcd2`) - mustExec(c, se, `GRANT ALL ON *.* to tcd2`) + mustExec(c, se, `GRANT ALL ON *.* to tcd2 WITH GRANT OPTION`) // should fail c.Assert(se.Auth(&auth.UserIdentity{Username: "tcd1", Hostname: "localhost", AuthUsername: "tcd1", AuthHostname: "%"}, nil, nil), IsTrue) @@ -579,7 +601,7 @@ func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { // high privileged user mustExec(c, se, "CREATE USER 'asuper'") mustExec(c, se, "CREATE USER 'anobody'") - mustExec(c, se, "GRANT ALL ON *.* TO 'asuper'") + mustExec(c, se, "GRANT ALL ON *.* TO 'asuper' WITH GRANT OPTION") mustExec(c, se, "CREATE DATABASE atest") mustExec(c, se, "use atest") mustExec(c, se, "CREATE TABLE t1 (a int)")