github.com/dolthub/go-mysql-server@v0.18.0/sql/rowexec/priv.go (about)

     1  // Copyright 2023 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rowexec
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/mysql_db"
    23  	"github.com/dolthub/go-mysql-server/sql/plan"
    24  )
    25  
    26  func (b *BaseBuilder) buildFlushPrivileges(ctx *sql.Context, n *plan.FlushPrivileges, row sql.Row) (sql.RowIter, error) {
    27  	gts, ok := n.MysqlDb.(*mysql_db.MySQLDb)
    28  	if !ok {
    29  		return nil, sql.ErrDatabaseNotFound.New("mysql")
    30  	}
    31  	editor := gts.Editor()
    32  	defer editor.Close()
    33  	err := gts.Persist(ctx, editor)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	return rowIterWithOkResultWithZeroRowsAffected(), nil
    39  }
    40  
    41  func (b *BaseBuilder) buildDropUser(ctx *sql.Context, n *plan.DropUser, row sql.Row) (sql.RowIter, error) {
    42  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
    43  	if !ok {
    44  		return nil, sql.ErrDatabaseNotFound.New("mysql")
    45  	}
    46  
    47  	editor := mysqlDb.Editor()
    48  	defer editor.Close()
    49  
    50  	for _, user := range n.Users {
    51  		existingUser := mysqlDb.GetUser(editor, user.Name, user.Host, false)
    52  		if existingUser == nil {
    53  			if n.IfExists {
    54  				continue
    55  			}
    56  			return nil, sql.ErrUserDeletionFailure.New(user.String("'"))
    57  		}
    58  
    59  		//TODO: if a user is mentioned in the "mandatory_roles" (users and roles are interchangeable) system variable then they cannot be dropped
    60  		editor.RemoveUser(mysql_db.UserPrimaryKey{
    61  			Host: existingUser.Host,
    62  			User: existingUser.User,
    63  		})
    64  		editor.RemoveRoleEdgesFromKey(mysql_db.RoleEdgesFromKey{
    65  			FromHost: existingUser.Host,
    66  			FromUser: existingUser.User,
    67  		})
    68  		editor.RemoveRoleEdgesToKey(mysql_db.RoleEdgesToKey{
    69  			ToHost: existingUser.Host,
    70  			ToUser: existingUser.User,
    71  		})
    72  	}
    73  	if err := mysqlDb.Persist(ctx, editor); err != nil {
    74  		return nil, err
    75  	}
    76  	return rowIterWithOkResultWithZeroRowsAffected(), nil
    77  }
    78  
    79  func (b *BaseBuilder) buildRevokeRole(ctx *sql.Context, n *plan.RevokeRole, row sql.Row) (sql.RowIter, error) {
    80  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
    81  	if !ok {
    82  		return nil, sql.ErrDatabaseNotFound.New("mysql")
    83  	}
    84  
    85  	editor := mysqlDb.Editor()
    86  	defer editor.Close()
    87  
    88  	for _, targetUser := range n.TargetUsers {
    89  		user := mysqlDb.GetUser(editor, targetUser.Name, targetUser.Host, false)
    90  		if user == nil {
    91  			return nil, sql.ErrGrantRevokeRoleDoesNotExist.New(targetUser.String("`"))
    92  		}
    93  		for _, targetRole := range n.Roles {
    94  			role := mysqlDb.GetUser(editor, targetRole.Name, targetRole.Host, true)
    95  			if role == nil {
    96  				return nil, sql.ErrGrantRevokeRoleDoesNotExist.New(targetRole.String("`"))
    97  			}
    98  			//TODO: if a role is mentioned in the "mandatory_roles" system variable then they cannot be revoked
    99  			editor.RemoveRoleEdge(mysql_db.RoleEdgesPrimaryKey{
   100  				FromHost: role.Host,
   101  				FromUser: role.User,
   102  				ToHost:   user.Host,
   103  				ToUser:   user.User,
   104  			})
   105  		}
   106  	}
   107  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   108  		return nil, err
   109  	}
   110  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   111  }
   112  
   113  func (b *BaseBuilder) buildDropRole(ctx *sql.Context, n *plan.DropRole, row sql.Row) (sql.RowIter, error) {
   114  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
   115  	if !ok {
   116  		return nil, sql.ErrDatabaseNotFound.New("mysql")
   117  	}
   118  
   119  	editor := mysqlDb.Editor()
   120  	defer editor.Close()
   121  	for _, role := range n.Roles {
   122  		userPk := mysql_db.UserPrimaryKey{
   123  			Host: role.Host,
   124  			User: role.Name,
   125  		}
   126  		if role.AnyHost {
   127  			userPk.Host = "%"
   128  		}
   129  		existingUser, ok := editor.GetUser(userPk)
   130  		if !ok {
   131  			if n.IfExists {
   132  				continue
   133  			}
   134  			return nil, sql.ErrRoleDeletionFailure.New(role.String("'"))
   135  		}
   136  
   137  		//TODO: if a role is mentioned in the "mandatory_roles" system variable then they cannot be dropped
   138  		editor.RemoveUser(userPk)
   139  		editor.RemoveRoleEdgesFromKey(mysql_db.RoleEdgesFromKey{
   140  			FromHost: existingUser.Host,
   141  			FromUser: existingUser.User,
   142  		})
   143  		editor.RemoveRoleEdgesToKey(mysql_db.RoleEdgesToKey{
   144  			ToHost: existingUser.Host,
   145  			ToUser: existingUser.User,
   146  		})
   147  	}
   148  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   149  		return nil, err
   150  	}
   151  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   152  }
   153  
   154  func (b *BaseBuilder) buildRevokeProxy(ctx *sql.Context, n *plan.RevokeProxy, row sql.Row) (sql.RowIter, error) {
   155  	return nil, fmt.Errorf("%T has no execution iterator", n)
   156  }
   157  
   158  func (b *BaseBuilder) buildGrantRole(ctx *sql.Context, n *plan.GrantRole, row sql.Row) (sql.RowIter, error) {
   159  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
   160  	if !ok {
   161  		return nil, sql.ErrDatabaseNotFound.New("mysql")
   162  	}
   163  
   164  	editor := mysqlDb.Editor()
   165  	defer editor.Close()
   166  
   167  	for _, targetUser := range n.TargetUsers {
   168  		user := mysqlDb.GetUser(editor, targetUser.Name, targetUser.Host, false)
   169  		if user == nil {
   170  			return nil, sql.ErrGrantRevokeRoleDoesNotExist.New(targetUser.String("`"))
   171  		}
   172  		for _, targetRole := range n.Roles {
   173  			role := mysqlDb.GetUser(editor, targetRole.Name, targetRole.Host, true)
   174  			if role == nil {
   175  				return nil, sql.ErrGrantRevokeRoleDoesNotExist.New(targetRole.String("`"))
   176  			}
   177  			editor.PutRoleEdge(&mysql_db.RoleEdge{
   178  				FromHost:        role.Host,
   179  				FromUser:        role.User,
   180  				ToHost:          user.Host,
   181  				ToUser:          user.User,
   182  				WithAdminOption: n.WithAdminOption,
   183  			})
   184  		}
   185  	}
   186  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   187  		return nil, err
   188  	}
   189  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   190  }
   191  
   192  func (b *BaseBuilder) buildGrantProxy(ctx *sql.Context, n *plan.GrantProxy, row sql.Row) (sql.RowIter, error) {
   193  	return nil, fmt.Errorf("%T has no execution iterator", n)
   194  }
   195  
   196  func (b *BaseBuilder) buildRenameUser(ctx *sql.Context, n *plan.RenameUser, row sql.Row) (sql.RowIter, error) {
   197  	return nil, fmt.Errorf("not yet implemented")
   198  }
   199  
   200  func (b *BaseBuilder) buildRevoke(ctx *sql.Context, n *plan.Revoke, row sql.Row) (sql.RowIter, error) {
   201  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
   202  	if !ok {
   203  		return nil, sql.ErrDatabaseNotFound.New("mysql")
   204  	}
   205  
   206  	editor := mysqlDb.Editor()
   207  	defer editor.Close()
   208  
   209  	users := make([]*mysql_db.User, 0, len(n.Users))
   210  	for _, revokeUser := range n.Users {
   211  		user := mysqlDb.GetUser(editor, revokeUser.Name, revokeUser.Host, false)
   212  		if user == nil {
   213  			return nil, sql.ErrGrantUserDoesNotExist.New()
   214  		}
   215  		users = append(users, user)
   216  	}
   217  
   218  	if n.PrivilegeLevel.Database == "*" {
   219  		// Global privileges
   220  		if n.PrivilegeLevel.TableRoutine != "*" {
   221  			return nil, sql.ErrGrantRevokeIllegalPrivilege.New()
   222  		}
   223  		if n.ObjectType != plan.ObjectType_Any {
   224  			return nil, sql.ErrGrantRevokeIllegalPrivilege.New()
   225  		}
   226  		for _, user := range users {
   227  			if err := n.HandleGlobalPrivileges(user); err != nil {
   228  				return nil, err
   229  			}
   230  		}
   231  	} else {
   232  		database := n.PrivilegeLevel.Database
   233  		if database == "" {
   234  			database = ctx.GetCurrentDatabase()
   235  			if database == "" {
   236  				return nil, sql.ErrNoDatabaseSelected.New()
   237  			}
   238  		}
   239  		if n.PrivilegeLevel.TableRoutine == "*" {
   240  			// Database privileges
   241  			if n.ObjectType != plan.ObjectType_Any {
   242  				return nil, sql.ErrGrantRevokeIllegalPrivilege.New()
   243  			}
   244  			for _, user := range users {
   245  				if err := n.HandleDatabasePrivileges(user, database); err != nil {
   246  					return nil, err
   247  				}
   248  			}
   249  		} else {
   250  			if n.ObjectType != plan.ObjectType_Any {
   251  				if n.ObjectType == plan.ObjectType_Procedure || n.ObjectType == plan.ObjectType_Function {
   252  					// Routine Privileges
   253  					isProc := n.ObjectType == plan.ObjectType_Procedure
   254  					for _, user := range users {
   255  						if err := n.HandleRoutinePrivileges(user, database, n.PrivilegeLevel.TableRoutine, isProc); err != nil {
   256  							return nil, err
   257  						}
   258  					}
   259  				} else {
   260  					return nil, fmt.Errorf("runtime error: unexpected object type: %d", n.ObjectType)
   261  				}
   262  			} else {
   263  				// Table Privileges
   264  				for _, user := range users {
   265  					if err := n.HandleTablePrivileges(user, database, n.PrivilegeLevel.TableRoutine); err != nil {
   266  						return nil, err
   267  					}
   268  				}
   269  			}
   270  		}
   271  	}
   272  
   273  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   274  		return nil, err
   275  	}
   276  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   277  }
   278  
   279  func (b *BaseBuilder) buildRevokeAll(ctx *sql.Context, n *plan.RevokeAll, row sql.Row) (sql.RowIter, error) {
   280  	return nil, fmt.Errorf("not yet implemented")
   281  }
   282  
   283  func (b *BaseBuilder) buildGrant(ctx *sql.Context, n *plan.Grant, row sql.Row) (sql.RowIter, error) {
   284  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
   285  	if !ok {
   286  		return nil, sql.ErrDatabaseNotFound.New("mysql")
   287  	}
   288  	editor := mysqlDb.Editor()
   289  	defer editor.Close()
   290  	if n.PrivilegeLevel.Database == "*" && n.PrivilegeLevel.TableRoutine == "*" {
   291  		if n.ObjectType != plan.ObjectType_Any {
   292  			return nil, sql.ErrGrantRevokeIllegalPrivilege.New()
   293  		}
   294  		if n.As != nil {
   295  			return nil, fmt.Errorf("GRANT has not yet implemented user assumption")
   296  		}
   297  		for _, grantUser := range n.Users {
   298  			user := mysqlDb.GetUser(editor, grantUser.Name, grantUser.Host, false)
   299  			if user == nil {
   300  				return nil, sql.ErrGrantUserDoesNotExist.New()
   301  			}
   302  			if err := n.HandleGlobalPrivileges(user); err != nil {
   303  				return nil, err
   304  			}
   305  			if n.WithGrantOption {
   306  				user.PrivilegeSet.AddGlobalStatic(sql.PrivilegeType_GrantOption)
   307  			}
   308  		}
   309  	} else if n.PrivilegeLevel.Database != "*" && n.PrivilegeLevel.TableRoutine == "*" {
   310  		database := n.PrivilegeLevel.Database
   311  		if database == "" {
   312  			database = ctx.GetCurrentDatabase()
   313  			if database == "" {
   314  				return nil, sql.ErrNoDatabaseSelected.New()
   315  			}
   316  		}
   317  		if n.ObjectType != plan.ObjectType_Any {
   318  			return nil, sql.ErrGrantRevokeIllegalPrivilege.New()
   319  		}
   320  		if n.As != nil {
   321  			return nil, fmt.Errorf("GRANT has not yet implemented user assumption")
   322  		}
   323  		for _, grantUser := range n.Users {
   324  			user := mysqlDb.GetUser(editor, grantUser.Name, grantUser.Host, false)
   325  			if user == nil {
   326  				return nil, sql.ErrGrantUserDoesNotExist.New()
   327  			}
   328  			if err := n.HandleDatabasePrivileges(user, database); err != nil {
   329  				return nil, err
   330  			}
   331  			if n.WithGrantOption {
   332  				user.PrivilegeSet.AddDatabase(database, sql.PrivilegeType_GrantOption)
   333  			}
   334  		}
   335  	} else {
   336  		database := n.PrivilegeLevel.Database
   337  		if database == "" {
   338  			database = ctx.GetCurrentDatabase()
   339  			if database == "" {
   340  				return nil, sql.ErrNoDatabaseSelected.New()
   341  			}
   342  		}
   343  		if n.ObjectType != plan.ObjectType_Any {
   344  			if n.ObjectType == plan.ObjectType_Procedure {
   345  				for _, grantUser := range n.Users {
   346  					user := mysqlDb.GetUser(editor, grantUser.Name, grantUser.Host, false)
   347  					if user == nil {
   348  						return nil, sql.ErrGrantUserDoesNotExist.New()
   349  					}
   350  					if err := n.HandleRoutinePrivileges(user, database, n.PrivilegeLevel.TableRoutine, true); err != nil {
   351  						return nil, err
   352  					}
   353  				}
   354  			} else if n.ObjectType == plan.ObjectType_Function {
   355  				// TODO: We currently model function permissions, but don't have a common place to enforce them, so punting
   356  				// on allowing them to be granted.
   357  				return nil, fmt.Errorf("fine grain function permissions currently unsupported")
   358  			} else {
   359  				// This fall through will only happen if we add new object types, which is unlikely.
   360  				return nil, fmt.Errorf("runtime error: unexpected object type: %d", n.ObjectType)
   361  			}
   362  		} else {
   363  			if n.As != nil {
   364  				return nil, fmt.Errorf("GRANT has not yet implemented user assumption")
   365  			}
   366  			for _, grantUser := range n.Users {
   367  				user := mysqlDb.GetUser(editor, grantUser.Name, grantUser.Host, false)
   368  				if user == nil {
   369  					return nil, sql.ErrGrantUserDoesNotExist.New()
   370  				}
   371  				if err := n.HandleTablePrivileges(user, database, n.PrivilegeLevel.TableRoutine); err != nil {
   372  					return nil, err
   373  				}
   374  				if n.WithGrantOption {
   375  					user.PrivilegeSet.AddTable(database, n.PrivilegeLevel.TableRoutine, sql.PrivilegeType_GrantOption)
   376  				}
   377  			}
   378  		}
   379  	}
   380  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   381  		return nil, err
   382  	}
   383  
   384  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   385  }
   386  
   387  func (b *BaseBuilder) buildCreateRole(ctx *sql.Context, n *plan.CreateRole, row sql.Row) (sql.RowIter, error) {
   388  	mysqlDb, ok := n.MySQLDb.(*mysql_db.MySQLDb)
   389  	if !ok {
   390  		return nil, sql.ErrDatabaseNotFound.New("mysql")
   391  	}
   392  
   393  	editor := mysqlDb.Editor()
   394  	defer editor.Close()
   395  
   396  	for _, role := range n.Roles {
   397  		userPk := mysql_db.UserPrimaryKey{
   398  			Host: role.Host,
   399  			User: role.Name,
   400  		}
   401  		if role.AnyHost {
   402  			userPk.Host = "%"
   403  		}
   404  		_, ok := editor.GetUser(userPk)
   405  		if ok {
   406  			if n.IfNotExists {
   407  				continue
   408  			}
   409  			return nil, sql.ErrRoleCreationFailure.New(role.String("'"))
   410  		}
   411  
   412  		//TODO: When password expiration is implemented, make sure that roles have an expired password on creation
   413  		editor.PutUser(&mysql_db.User{
   414  			User:                userPk.User,
   415  			Host:                userPk.Host,
   416  			PrivilegeSet:        mysql_db.NewPrivilegeSet(),
   417  			Plugin:              "mysql_native_password",
   418  			Password:            "",
   419  			PasswordLastChanged: time.Now().UTC(),
   420  			Locked:              true,
   421  			Attributes:          nil,
   422  			IsRole:              true,
   423  		})
   424  	}
   425  	if err := mysqlDb.Persist(ctx, editor); err != nil {
   426  		return nil, err
   427  	}
   428  	return rowIterWithOkResultWithZeroRowsAffected(), nil
   429  }