90 Star 490 Fork 149

平凯星辰(北京)科技有限公司/tidb

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
grant.go 19.44 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package executor
import (
"fmt"
"strings"
"github.com/juju/errors"
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/context"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/model"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/util/auth"
"github.com/pingcap/tidb/util/sqlexec"
"github.com/pingcap/tidb/util/types"
)
/***
* Grant Statement
* See https://dev.mysql.com/doc/refman/5.7/en/grant.html
************************************************************************************/
var (
_ Executor = (*GrantExec)(nil)
)
// GrantExec executes GrantStmt.
type GrantExec struct {
Privs []*ast.PrivElem
ObjectType ast.ObjectTypeType
Level *ast.GrantLevel
Users []*ast.UserSpec
WithGrant bool
ctx context.Context
is infoschema.InfoSchema
done bool
}
// Schema implements the Executor Schema interface.
func (e *GrantExec) Schema() *expression.Schema {
return expression.NewSchema()
}
// Next implements Execution Next interface.
func (e *GrantExec) Next() (Row, error) {
if e.done {
return nil, nil
}
dbName := e.Level.DBName
if len(dbName) == 0 {
dbName = e.ctx.GetSessionVars().CurrentDB
}
// Grant for each user
for _, user := range e.Users {
// Check if user exists.
exists, err := userExists(e.ctx, user.User.Username, user.User.Hostname)
if err != nil {
return nil, errors.Trace(err)
}
if !exists {
pwd := ""
if user.AuthOpt != nil {
if user.AuthOpt.ByAuthString {
pwd = auth.EncodePassword(user.AuthOpt.AuthString)
} else {
pwd = auth.EncodePassword(user.AuthOpt.HashString)
}
}
user := fmt.Sprintf(`("%s", "%s", "%s")`, user.User.Hostname, user.User.Username, pwd)
sql := fmt.Sprintf(`INSERT INTO %s.%s (Host, User, Password) VALUES %s;`, mysql.SystemDB, mysql.UserTable, user)
_, err := e.ctx.(sqlexec.SQLExecutor).Execute(sql)
if err != nil {
return nil, errors.Trace(err)
}
}
// If there is no privilege entry in corresponding table, insert a new one.
// DB scope: mysql.DB
// Table scope: mysql.Tables_priv
// Column scope: mysql.Columns_priv
switch e.Level.Level {
case ast.GrantLevelDB:
err := checkAndInitDBPriv(e.ctx, dbName, e.is, user.User.Username, user.User.Hostname)
if err != nil {
return nil, errors.Trace(err)
}
case ast.GrantLevelTable:
err := checkAndInitTablePriv(e.ctx, dbName, e.Level.TableName, e.is, user.User.Username, user.User.Hostname)
if err != nil {
return nil, errors.Trace(err)
}
}
privs := e.Privs
if e.WithGrant {
privs = append(privs, &ast.PrivElem{Priv: mysql.GrantPriv})
}
// Grant each priv to the user.
for _, priv := range privs {
if len(priv.Cols) > 0 {
// Check column scope privilege entry.
// TODO: Check validity before insert new entry.
err := e.checkAndInitColumnPriv(user.User.Username, user.User.Hostname, priv.Cols)
if err != nil {
return nil, errors.Trace(err)
}
}
err := e.grantPriv(priv, user)
if err != nil {
return nil, errors.Trace(err)
}
}
}
e.done = true
sessionctx.GetDomain(e.ctx).NotifyUpdatePrivilege(e.ctx)
return nil, nil
}
// Close implements the Executor Close interface.
func (e *GrantExec) Close() error {
return nil
}
// Open implements the Executor Open interface.
func (e *GrantExec) Open() error {
return nil
}
// checkAndInitDBPriv checks if DB scope privilege entry exists in mysql.DB.
// If unexists, insert a new one.
func checkAndInitDBPriv(ctx context.Context, dbName string, is infoschema.InfoSchema, user string, host string) error {
ok, err := dbUserExists(ctx, user, host, dbName)
if err != nil {
return errors.Trace(err)
}
if ok {
return nil
}
// Entry does not exist for user-host-db. Insert a new entry.
return initDBPrivEntry(ctx, user, host, dbName)
}
// checkAndInitTablePriv checks if table scope privilege entry exists in mysql.Tables_priv.
// If unexists, insert a new one.
func checkAndInitTablePriv(ctx context.Context, dbName, tblName string, is infoschema.InfoSchema, user string, host string) error {
ok, err := tableUserExists(ctx, user, host, dbName, tblName)
if err != nil {
return errors.Trace(err)
}
if ok {
return nil
}
// Entry does not exist for user-host-db-tbl. Insert a new entry.
return initTablePrivEntry(ctx, user, host, dbName, tblName)
}
// checkAndInitColumnPriv checks if column scope privilege entry exists in mysql.Columns_priv.
// If unexists, insert a new one.
func (e *GrantExec) checkAndInitColumnPriv(user string, host string, cols []*ast.ColumnName) error {
dbName, tbl, err := getTargetSchemaAndTable(e.ctx, e.Level.DBName, e.Level.TableName, e.is)
if err != nil {
return errors.Trace(err)
}
for _, c := range cols {
col := table.FindCol(tbl.Cols(), c.Name.L)
if col == nil {
return errors.Errorf("Unknown column: %s", c.Name.O)
}
ok, err := columnPrivEntryExists(e.ctx, user, host, dbName, tbl.Meta().Name.O, col.Name.O)
if err != nil {
return errors.Trace(err)
}
if ok {
continue
}
// Entry does not exist for user-host-db-tbl-col. Insert a new entry.
err = initColumnPrivEntry(e.ctx, user, host, dbName, tbl.Meta().Name.O, col.Name.O)
if err != nil {
return errors.Trace(err)
}
}
return nil
}
// initDBPrivEntry inserts a new row into mysql.DB with empty privilege.
func initDBPrivEntry(ctx context.Context, user string, host string, db string) error {
sql := fmt.Sprintf(`INSERT INTO %s.%s (Host, User, DB) VALUES ("%s", "%s", "%s")`, mysql.SystemDB, mysql.DBTable, host, user, db)
_, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
return errors.Trace(err)
}
// initTablePrivEntry inserts a new row into mysql.Tables_priv with empty privilege.
func initTablePrivEntry(ctx context.Context, user string, host string, db string, tbl string) error {
sql := fmt.Sprintf(`INSERT INTO %s.%s (Host, User, DB, Table_name, Table_priv, Column_priv) VALUES ("%s", "%s", "%s", "%s", "", "")`, mysql.SystemDB, mysql.TablePrivTable, host, user, db, tbl)
_, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
return errors.Trace(err)
}
// initColumnPrivEntry inserts a new row into mysql.Columns_priv with empty privilege.
func initColumnPrivEntry(ctx context.Context, user string, host string, db string, tbl string, col string) error {
sql := fmt.Sprintf(`INSERT INTO %s.%s (Host, User, DB, Table_name, Column_name, Column_priv) VALUES ("%s", "%s", "%s", "%s", "%s", "")`, mysql.SystemDB, mysql.ColumnPrivTable, host, user, db, tbl, col)
_, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
return errors.Trace(err)
}
// grantPriv grants priv to user in s.Level scope.
func (e *GrantExec) grantPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
switch e.Level.Level {
case ast.GrantLevelGlobal:
return e.grantGlobalPriv(priv, user)
case ast.GrantLevelDB:
return e.grantDBPriv(priv, user)
case ast.GrantLevelTable:
if len(priv.Cols) == 0 {
return e.grantTablePriv(priv, user)
}
return e.grantColumnPriv(priv, user)
default:
return errors.Errorf("Unknown grant level: %#v", e.Level)
}
}
// grantGlobalPriv manipulates mysql.user table.
func (e *GrantExec) grantGlobalPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
asgns, err := composeGlobalPrivUpdate(priv.Priv, "Y")
if err != nil {
return errors.Trace(err)
}
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s"`, mysql.SystemDB, mysql.UserTable, asgns, user.User.Username, user.User.Hostname)
_, _, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// grantDBPriv manipulates mysql.db table.
func (e *GrantExec) grantDBPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
dbName := e.Level.DBName
if len(dbName) == 0 {
dbName = e.ctx.GetSessionVars().CurrentDB
}
asgns, err := composeDBPrivUpdate(priv.Priv, "Y")
if err != nil {
return errors.Trace(err)
}
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s" AND DB="%s";`, mysql.SystemDB, mysql.DBTable, asgns, user.User.Username, user.User.Hostname, dbName)
_, _, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// grantTablePriv manipulates mysql.tables_priv table.
func (e *GrantExec) grantTablePriv(priv *ast.PrivElem, user *ast.UserSpec) error {
dbName := e.Level.DBName
if len(dbName) == 0 {
dbName = e.ctx.GetSessionVars().CurrentDB
}
tblName := e.Level.TableName
asgns, err := composeTablePrivUpdateForGrant(e.ctx, priv.Priv, user.User.Username, user.User.Hostname, dbName, tblName)
if err != nil {
return errors.Trace(err)
}
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s";`, mysql.SystemDB, mysql.TablePrivTable, asgns, user.User.Username, user.User.Hostname, dbName, tblName)
_, _, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// grantColumnPriv manipulates mysql.tables_priv table.
func (e *GrantExec) grantColumnPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
dbName, tbl, err := getTargetSchemaAndTable(e.ctx, e.Level.DBName, e.Level.TableName, e.is)
if err != nil {
return errors.Trace(err)
}
for _, c := range priv.Cols {
col := table.FindCol(tbl.Cols(), c.Name.L)
if col == nil {
return errors.Errorf("Unknown column: %s", c)
}
asgns, err := composeColumnPrivUpdateForGrant(e.ctx, priv.Priv, user.User.Username, user.User.Hostname, dbName, tbl.Meta().Name.O, col.Name.O)
if err != nil {
return errors.Trace(err)
}
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s" AND Column_name="%s";`, mysql.SystemDB, mysql.ColumnPrivTable, asgns, user.User.Username, user.User.Hostname, dbName, tbl.Meta().Name.O, col.Name.O)
_, _, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
if err != nil {
return errors.Trace(err)
}
}
return nil
}
// composeGlobalPrivUpdate composes update stmt assignment list string for global scope privilege update.
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))
}
return strings.Join(strs, ", "), nil
}
col, ok := mysql.Priv2UserCol[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
return fmt.Sprintf(`%s="%s"`, col, value), nil
}
// composeDBPrivUpdate composes update stmt assignment list for db scope privilege update.
func composeDBPrivUpdate(priv mysql.PrivilegeType, value string) (string, error) {
if priv == mysql.AllPriv {
strs := make([]string, 0, len(mysql.AllDBPrivs))
for _, p := range mysql.AllDBPrivs {
v, ok := mysql.Priv2UserCol[p]
if !ok {
return "", errors.Errorf("Unknown db privilege %v", priv)
}
strs = append(strs, fmt.Sprintf(`%s="%s"`, v, value))
}
return strings.Join(strs, ", "), nil
}
col, ok := mysql.Priv2UserCol[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
return fmt.Sprintf(`%s="%s"`, col, value), nil
}
// composeTablePrivUpdateForGrant composes update stmt assignment list for table scope privilege update.
func composeTablePrivUpdateForGrant(ctx context.Context, priv mysql.PrivilegeType, name string, host string, db string, tbl string) (string, error) {
var newTablePriv, newColumnPriv string
if priv == mysql.AllPriv {
for _, p := range mysql.AllTablePrivs {
v, ok := mysql.Priv2SetStr[p]
if !ok {
return "", errors.Errorf("Unknown table privilege %v", p)
}
newTablePriv = addToSet(newTablePriv, v)
}
for _, p := range mysql.AllColumnPrivs {
v, ok := mysql.Priv2SetStr[p]
if !ok {
return "", errors.Errorf("Unknown column privilege %v", p)
}
newColumnPriv = addToSet(newColumnPriv, v)
}
} else {
currTablePriv, currColumnPriv, err := getTablePriv(ctx, name, host, db, tbl)
if err != nil {
return "", errors.Trace(err)
}
p, ok := mysql.Priv2SetStr[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
newTablePriv = addToSet(currTablePriv, p)
for _, cp := range mysql.AllColumnPrivs {
if priv == cp {
newColumnPriv = addToSet(currColumnPriv, p)
break
}
}
}
return fmt.Sprintf(`Table_priv="%s", Column_priv="%s", Grantor="%s"`, newTablePriv, newColumnPriv, ctx.GetSessionVars().User), nil
}
func composeTablePrivUpdateForRevoke(ctx context.Context, priv mysql.PrivilegeType, name string, host string, db string, tbl string) (string, error) {
var newTablePriv, newColumnPriv string
if priv == mysql.AllPriv {
newTablePriv = ""
newColumnPriv = ""
} else {
currTablePriv, currColumnPriv, err := getTablePriv(ctx, name, host, db, tbl)
if err != nil {
return "", errors.Trace(err)
}
p, ok := mysql.Priv2SetStr[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
newTablePriv = deleteFromSet(currTablePriv, p)
for _, cp := range mysql.AllColumnPrivs {
if priv == cp {
newColumnPriv = deleteFromSet(currColumnPriv, p)
break
}
}
}
return fmt.Sprintf(`Table_priv="%s", Column_priv="%s", Grantor="%s"`, newTablePriv, newColumnPriv, ctx.GetSessionVars().User), nil
}
// addToSet add a value to the set, e.g:
// addToSet("Select,Insert", "Update") returns "Select,Insert,Update".
func addToSet(set string, value string) string {
if set == "" {
return value
}
return fmt.Sprintf("%s,%s", set, value)
}
// deleteFromSet delete the value from the set, e.g:
// deleteFromSet("Select,Insert,Update", "Update") returns "Select,Insert".
func deleteFromSet(set string, value string) string {
sets := strings.Split(set, ",")
res := make([]string, 0, len(sets))
for _, v := range sets {
if v != value {
res = append(res, v)
}
}
return strings.Join(res, ",")
}
// composeColumnPrivUpdateForGrant composes update stmt assignment list for column scope privilege update.
func composeColumnPrivUpdateForGrant(ctx context.Context, priv mysql.PrivilegeType, name string, host string, db string, tbl string, col string) (string, error) {
newColumnPriv := ""
if priv == mysql.AllPriv {
for _, p := range mysql.AllColumnPrivs {
v, ok := mysql.Priv2SetStr[p]
if !ok {
return "", errors.Errorf("Unknown column privilege %v", p)
}
newColumnPriv = addToSet(newColumnPriv, v)
}
} else {
currColumnPriv, err := getColumnPriv(ctx, name, host, db, tbl, col)
if err != nil {
return "", errors.Trace(err)
}
p, ok := mysql.Priv2SetStr[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
newColumnPriv = addToSet(currColumnPriv, p)
}
return fmt.Sprintf(`Column_priv="%s"`, newColumnPriv), nil
}
func composeColumnPrivUpdateForRevoke(ctx context.Context, priv mysql.PrivilegeType, name string, host string, db string, tbl string, col string) (string, error) {
newColumnPriv := ""
if priv == mysql.AllPriv {
newColumnPriv = ""
} else {
currColumnPriv, err := getColumnPriv(ctx, name, host, db, tbl, col)
if err != nil {
return "", errors.Trace(err)
}
p, ok := mysql.Priv2SetStr[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
newColumnPriv = deleteFromSet(currColumnPriv, p)
}
return fmt.Sprintf(`Column_priv="%s"`, newColumnPriv), nil
}
// recordExists is a helper function to check if the sql returns any row.
func recordExists(ctx context.Context, sql string) (bool, error) {
rows, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return false, errors.Trace(err)
}
return len(rows) > 0, nil
}
// dbUserExists checks if there is an entry with key user-host-db in mysql.DB.
func dbUserExists(ctx context.Context, name string, host string, db string) (bool, error) {
sql := fmt.Sprintf(`SELECT * FROM %s.%s WHERE User="%s" AND Host="%s" AND DB="%s";`, mysql.SystemDB, mysql.DBTable, name, host, db)
return recordExists(ctx, sql)
}
// tableUserExists checks if there is an entry with key user-host-db-tbl in mysql.Tables_priv.
func tableUserExists(ctx context.Context, name string, host string, db string, tbl string) (bool, error) {
sql := fmt.Sprintf(`SELECT * FROM %s.%s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s";`, mysql.SystemDB, mysql.TablePrivTable, name, host, db, tbl)
return recordExists(ctx, sql)
}
// columnPrivEntryExists checks if there is an entry with key user-host-db-tbl-col in mysql.Columns_priv.
func columnPrivEntryExists(ctx context.Context, name string, host string, db string, tbl string, col string) (bool, error) {
sql := fmt.Sprintf(`SELECT * FROM %s.%s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s" AND Column_name="%s";`, mysql.SystemDB, mysql.ColumnPrivTable, name, host, db, tbl, col)
return recordExists(ctx, sql)
}
// getTablePriv gets current table scope privilege set from mysql.Tables_priv.
// Return Table_priv and Column_priv.
func getTablePriv(ctx context.Context, name string, host string, db string, tbl string) (string, string, error) {
sql := fmt.Sprintf(`SELECT Table_priv, Column_priv FROM %s.%s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s";`, mysql.SystemDB, mysql.TablePrivTable, name, host, db, tbl)
rows, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return "", "", errors.Trace(err)
}
if len(rows) < 1 {
return "", "", errors.Errorf("get table privilege fail for %s %s %s %s", name, host, db, tbl)
}
var tPriv, cPriv string
row := rows[0]
if row.Data[0].Kind() == types.KindMysqlSet {
tablePriv := row.Data[0].GetMysqlSet()
tPriv = tablePriv.Name
}
if row.Data[1].Kind() == types.KindMysqlSet {
columnPriv := row.Data[1].GetMysqlSet()
cPriv = columnPriv.Name
}
return tPriv, cPriv, nil
}
// getColumnPriv gets current column scope privilege set from mysql.Columns_priv.
// Return Column_priv.
func getColumnPriv(ctx context.Context, name string, host string, db string, tbl string, col string) (string, error) {
sql := fmt.Sprintf(`SELECT Column_priv FROM %s.%s WHERE User="%s" AND Host="%s" AND DB="%s" AND Table_name="%s" AND Column_name="%s";`, mysql.SystemDB, mysql.ColumnPrivTable, name, host, db, tbl, col)
rows, _, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return "", errors.Trace(err)
}
if len(rows) < 1 {
return "", errors.Errorf("get column privilege fail for %s %s %s %s %s", name, host, db, tbl, col)
}
cPriv := ""
if rows[0].Data[0].Kind() == types.KindMysqlSet {
cPriv = rows[0].Data[0].GetMysqlSet().Name
}
return cPriv, nil
}
// getTargetSchemaAndTable finds the schema and table by dbName and tableName.
func getTargetSchemaAndTable(ctx context.Context, dbName, tableName string, is infoschema.InfoSchema) (string, table.Table, error) {
if len(dbName) == 0 {
dbName = ctx.GetSessionVars().CurrentDB
if len(dbName) == 0 {
return "", nil, errors.New("miss DB name for grant privilege")
}
}
name := model.NewCIStr(tableName)
tbl, err := is.TableByName(model.NewCIStr(dbName), name)
if err != nil {
return "", nil, errors.Trace(err)
}
return dbName, tbl, nil
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/pingcap/tidb.git
git@gitee.com:pingcap/tidb.git
pingcap
tidb
tidb
v1.0.9

搜索帮助