githaven-fork/vendor/github.com/pingcap/tidb/executor/grant.go

521 lines
17 KiB
Go
Raw Normal View History

// 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/column"
"github.com/pingcap/tidb/context"
"github.com/pingcap/tidb/model"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/db"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table"
"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
ctx context.Context
done bool
}
// Fields implements Executor Fields interface.
func (e *GrantExec) Fields() []*ast.ResultField {
return nil
}
// Next implements Execution Next interface.
func (e *GrantExec) Next() (*Row, error) {
if e.done {
return nil, nil
}
// Grant for each user
for _, user := range e.Users {
// Check if user exists.
userName, host := parseUser(user.User)
exists, err := userExists(e.ctx, userName, host)
if err != nil {
return nil, errors.Trace(err)
}
if !exists {
return nil, errors.Errorf("Unknown user: %s", user.User)
}
// 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 := e.checkAndInitDBPriv(userName, host)
if err != nil {
return nil, errors.Trace(err)
}
case ast.GrantLevelTable:
err := e.checkAndInitTablePriv(userName, host)
if err != nil {
return nil, errors.Trace(err)
}
}
// Grant each priv to the user.
for _, priv := range e.Privs {
if len(priv.Cols) > 0 {
// Check column scope privilege entry.
// TODO: Check validity before insert new entry.
err1 := e.checkAndInitColumnPriv(userName, host, priv.Cols)
if err1 != nil {
return nil, errors.Trace(err1)
}
}
err2 := e.grantPriv(priv, user)
if err2 != nil {
return nil, errors.Trace(err2)
}
}
}
e.done = true
return nil, nil
}
// Close implements Executor Close interface.
func (e *GrantExec) Close() error {
return nil
}
// Check if DB scope privilege entry exists in mysql.DB.
// If unexists, insert a new one.
func (e *GrantExec) checkAndInitDBPriv(user string, host string) error {
db, err := e.getTargetSchema()
if err != nil {
return errors.Trace(err)
}
ok, err := dbUserExists(e.ctx, user, host, db.Name.O)
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(e.ctx, user, host, db.Name.O)
}
// Check if table scope privilege entry exists in mysql.Tables_priv.
// If unexists, insert a new one.
func (e *GrantExec) checkAndInitTablePriv(user string, host string) error {
db, tbl, err := e.getTargetSchemaAndTable()
if err != nil {
return errors.Trace(err)
}
ok, err := tableUserExists(e.ctx, user, host, db.Name.O, tbl.Meta().Name.O)
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(e.ctx, user, host, db.Name.O, tbl.Meta().Name.O)
}
// Check 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 {
db, tbl, err := e.getTargetSchemaAndTable()
if err != nil {
return errors.Trace(err)
}
for _, c := range cols {
col := column.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, db.Name.O, 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, db.Name.O, tbl.Meta().Name.O, col.Name.O)
if err != nil {
return errors.Trace(err)
}
}
return nil
}
// Insert 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)
}
// Insert 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)
}
// Insert 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)
}
// Grant 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)
}
}
// Manipulate mysql.user table.
func (e *GrantExec) grantGlobalPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
asgns, err := composeGlobalPrivUpdate(priv.Priv)
if err != nil {
return errors.Trace(err)
}
userName, host := parseUser(user.User)
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s"`, mysql.SystemDB, mysql.UserTable, asgns, userName, host)
_, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// Manipulate mysql.db table.
func (e *GrantExec) grantDBPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
db, err := e.getTargetSchema()
if err != nil {
return errors.Trace(err)
}
asgns, err := composeDBPrivUpdate(priv.Priv)
if err != nil {
return errors.Trace(err)
}
userName, host := parseUser(user.User)
sql := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE User="%s" AND Host="%s" AND DB="%s";`, mysql.SystemDB, mysql.DBTable, asgns, userName, host, db.Name.O)
_, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// Manipulate mysql.tables_priv table.
func (e *GrantExec) grantTablePriv(priv *ast.PrivElem, user *ast.UserSpec) error {
db, tbl, err := e.getTargetSchemaAndTable()
if err != nil {
return errors.Trace(err)
}
userName, host := parseUser(user.User)
asgns, err := composeTablePrivUpdate(e.ctx, priv.Priv, userName, host, db.Name.O, tbl.Meta().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";`, mysql.SystemDB, mysql.TablePrivTable, asgns, userName, host, db.Name.O, tbl.Meta().Name.O)
_, err = e.ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(e.ctx, sql)
return errors.Trace(err)
}
// Manipulate mysql.tables_priv table.
func (e *GrantExec) grantColumnPriv(priv *ast.PrivElem, user *ast.UserSpec) error {
db, tbl, err := e.getTargetSchemaAndTable()
if err != nil {
return errors.Trace(err)
}
userName, host := parseUser(user.User)
for _, c := range priv.Cols {
col := column.FindCol(tbl.Cols(), c.Name.L)
if col == nil {
return errors.Errorf("Unknown column: %s", c)
}
asgns, err := composeColumnPrivUpdate(e.ctx, priv.Priv, userName, host, db.Name.O, 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, userName, host, db.Name.O, 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
}
// Compose update stmt assignment list string for global scope privilege update.
func composeGlobalPrivUpdate(priv mysql.PrivilegeType) (string, error) {
if priv == mysql.AllPriv {
strs := make([]string, 0, len(mysql.Priv2UserCol))
for _, v := range mysql.Priv2UserCol {
strs = append(strs, fmt.Sprintf(`%s="Y"`, v))
}
return strings.Join(strs, ", "), nil
}
col, ok := mysql.Priv2UserCol[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
return fmt.Sprintf(`%s="Y"`, col), nil
}
// Compose update stmt assignment list for db scope privilege update.
func composeDBPrivUpdate(priv mysql.PrivilegeType) (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="Y"`, v))
}
return strings.Join(strs, ", "), nil
}
col, ok := mysql.Priv2UserCol[priv]
if !ok {
return "", errors.Errorf("Unknown priv: %v", priv)
}
return fmt.Sprintf(`%s="Y"`, col), nil
}
// Compose update stmt assignment list for table scope privilege update.
func composeTablePrivUpdate(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)
}
if len(newTablePriv) == 0 {
newTablePriv = v
} else {
newTablePriv = fmt.Sprintf("%s,%s", newTablePriv, v)
}
}
for _, p := range mysql.AllColumnPrivs {
v, ok := mysql.Priv2SetStr[p]
if !ok {
return "", errors.Errorf("Unknown column privilege %v", p)
}
if len(newColumnPriv) == 0 {
newColumnPriv = v
} else {
newColumnPriv = fmt.Sprintf("%s,%s", 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)
}
if len(currTablePriv) == 0 {
newTablePriv = p
} else {
newTablePriv = fmt.Sprintf("%s,%s", currTablePriv, p)
}
for _, cp := range mysql.AllColumnPrivs {
if priv == cp {
if len(currColumnPriv) == 0 {
newColumnPriv = p
} else {
newColumnPriv = fmt.Sprintf("%s,%s", currColumnPriv, p)
}
break
}
}
}
return fmt.Sprintf(`Table_priv="%s", Column_priv="%s", Grantor="%s"`, newTablePriv, newColumnPriv, variable.GetSessionVars(ctx).User), nil
}
// Compose update stmt assignment list for column scope privilege update.
func composeColumnPrivUpdate(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)
}
if len(newColumnPriv) == 0 {
newColumnPriv = v
} else {
newColumnPriv = fmt.Sprintf("%s,%s", 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)
}
if len(currColumnPriv) == 0 {
newColumnPriv = p
} else {
newColumnPriv = fmt.Sprintf("%s,%s", currColumnPriv, p)
}
}
return fmt.Sprintf(`Column_priv="%s"`, newColumnPriv), nil
}
// Helper function to check if the sql returns any row.
func recordExists(ctx context.Context, sql string) (bool, error) {
rs, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return false, errors.Trace(err)
}
defer rs.Close()
row, err := rs.Next()
if err != nil {
return false, errors.Trace(err)
}
return row != nil, nil
}
// Check 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)
}
// Check 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)
}
// Check 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)
}
// Get 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)
rs, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return "", "", errors.Trace(err)
}
defer rs.Close()
row, err := rs.Next()
if err != nil {
return "", "", errors.Trace(err)
}
var tPriv, cPriv string
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
}
// Get 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)
rs, err := ctx.(sqlexec.RestrictedSQLExecutor).ExecRestrictedSQL(ctx, sql)
if err != nil {
return "", errors.Trace(err)
}
defer rs.Close()
row, err := rs.Next()
if err != nil {
return "", errors.Trace(err)
}
cPriv := ""
if row.Data[0].Kind() == types.KindMysqlSet {
cPriv = row.Data[0].GetMysqlSet().Name
}
return cPriv, nil
}
// Find the schema by dbName.
func (e *GrantExec) getTargetSchema() (*model.DBInfo, error) {
dbName := e.Level.DBName
if len(dbName) == 0 {
// Grant *, use current schema
dbName = db.GetCurrentSchema(e.ctx)
if len(dbName) == 0 {
return nil, errors.New("Miss DB name for grant privilege.")
}
}
//check if db exists
schema := model.NewCIStr(dbName)
is := sessionctx.GetDomain(e.ctx).InfoSchema()
db, ok := is.SchemaByName(schema)
if !ok {
return nil, errors.Errorf("Unknown schema name: %s", dbName)
}
return db, nil
}
// Find the schema and table by dbName and tableName.
func (e *GrantExec) getTargetSchemaAndTable() (*model.DBInfo, table.Table, error) {
db, err := e.getTargetSchema()
if err != nil {
return nil, nil, errors.Trace(err)
}
name := model.NewCIStr(e.Level.TableName)
is := sessionctx.GetDomain(e.ctx).InfoSchema()
tbl, err := is.TableByName(db.Name, name)
if err != nil {
return nil, nil, errors.Trace(err)
}
return db, tbl, nil
}