github.com/dolthub/go-mysql-server@v0.18.0/sql/planbuilder/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 planbuilder
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	ast "github.com/dolthub/vitess/go/vt/sqlparser"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/plan"
    26  )
    27  
    28  func convertAccountName(names ...ast.AccountName) []plan.UserName {
    29  	userNames := make([]plan.UserName, len(names))
    30  	for i, name := range names {
    31  		userNames[i] = plan.UserName{
    32  			Name:    name.Name,
    33  			Host:    name.Host,
    34  			AnyHost: name.AnyHost,
    35  		}
    36  	}
    37  	return userNames
    38  }
    39  
    40  func convertPrivilege(privileges ...ast.Privilege) []plan.Privilege {
    41  	planPrivs := make([]plan.Privilege, len(privileges))
    42  	for i, privilege := range privileges {
    43  		var privType plan.PrivilegeType
    44  		var dynamicString string
    45  		switch privilege.Type {
    46  		case ast.PrivilegeType_All:
    47  			privType = plan.PrivilegeType_All
    48  		case ast.PrivilegeType_Alter:
    49  			privType = plan.PrivilegeType_Alter
    50  		case ast.PrivilegeType_AlterRoutine:
    51  			privType = plan.PrivilegeType_AlterRoutine
    52  		case ast.PrivilegeType_Create:
    53  			privType = plan.PrivilegeType_Create
    54  		case ast.PrivilegeType_CreateRole:
    55  			privType = plan.PrivilegeType_CreateRole
    56  		case ast.PrivilegeType_CreateRoutine:
    57  			privType = plan.PrivilegeType_CreateRoutine
    58  		case ast.PrivilegeType_CreateTablespace:
    59  			privType = plan.PrivilegeType_CreateTablespace
    60  		case ast.PrivilegeType_CreateTemporaryTables:
    61  			privType = plan.PrivilegeType_CreateTemporaryTables
    62  		case ast.PrivilegeType_CreateUser:
    63  			privType = plan.PrivilegeType_CreateUser
    64  		case ast.PrivilegeType_CreateView:
    65  			privType = plan.PrivilegeType_CreateView
    66  		case ast.PrivilegeType_Delete:
    67  			privType = plan.PrivilegeType_Delete
    68  		case ast.PrivilegeType_Drop:
    69  			privType = plan.PrivilegeType_Drop
    70  		case ast.PrivilegeType_DropRole:
    71  			privType = plan.PrivilegeType_DropRole
    72  		case ast.PrivilegeType_Event:
    73  			privType = plan.PrivilegeType_Event
    74  		case ast.PrivilegeType_Execute:
    75  			privType = plan.PrivilegeType_Execute
    76  		case ast.PrivilegeType_File:
    77  			privType = plan.PrivilegeType_File
    78  		case ast.PrivilegeType_GrantOption:
    79  			privType = plan.PrivilegeType_GrantOption
    80  		case ast.PrivilegeType_Index:
    81  			privType = plan.PrivilegeType_Index
    82  		case ast.PrivilegeType_Insert:
    83  			privType = plan.PrivilegeType_Insert
    84  		case ast.PrivilegeType_LockTables:
    85  			privType = plan.PrivilegeType_LockTables
    86  		case ast.PrivilegeType_Process:
    87  			privType = plan.PrivilegeType_Process
    88  		case ast.PrivilegeType_References:
    89  			privType = plan.PrivilegeType_References
    90  		case ast.PrivilegeType_Reload:
    91  			privType = plan.PrivilegeType_Reload
    92  		case ast.PrivilegeType_ReplicationClient:
    93  			privType = plan.PrivilegeType_ReplicationClient
    94  		case ast.PrivilegeType_ReplicationSlave:
    95  			privType = plan.PrivilegeType_ReplicationSlave
    96  		case ast.PrivilegeType_Select:
    97  			privType = plan.PrivilegeType_Select
    98  		case ast.PrivilegeType_ShowDatabases:
    99  			privType = plan.PrivilegeType_ShowDatabases
   100  		case ast.PrivilegeType_ShowView:
   101  			privType = plan.PrivilegeType_ShowView
   102  		case ast.PrivilegeType_Shutdown:
   103  			privType = plan.PrivilegeType_Shutdown
   104  		case ast.PrivilegeType_Super:
   105  			privType = plan.PrivilegeType_Super
   106  		case ast.PrivilegeType_Trigger:
   107  			privType = plan.PrivilegeType_Trigger
   108  		case ast.PrivilegeType_Update:
   109  			privType = plan.PrivilegeType_Update
   110  		case ast.PrivilegeType_Usage:
   111  			privType = plan.PrivilegeType_Usage
   112  		case ast.PrivilegeType_Dynamic:
   113  			privType = plan.PrivilegeType_Dynamic
   114  			dynamicString = privilege.DynamicName
   115  		default:
   116  			// all privileges have been implemented, so if we hit the default something bad has happened
   117  			panic("given privilege type parses but is not implemented")
   118  		}
   119  		planPrivs[i] = plan.Privilege{
   120  			Type:    privType,
   121  			Columns: privilege.Columns,
   122  			Dynamic: dynamicString,
   123  		}
   124  	}
   125  	return planPrivs
   126  }
   127  
   128  func convertObjectType(objType ast.GrantObjectType) plan.ObjectType {
   129  	switch objType {
   130  	case ast.GrantObjectType_Any:
   131  		return plan.ObjectType_Any
   132  	case ast.GrantObjectType_Table:
   133  		return plan.ObjectType_Table
   134  	case ast.GrantObjectType_Function:
   135  		return plan.ObjectType_Function
   136  	case ast.GrantObjectType_Procedure:
   137  		return plan.ObjectType_Procedure
   138  	default:
   139  		panic("no other grant object types exist")
   140  	}
   141  }
   142  
   143  func convertPrivilegeLevel(privLevel ast.PrivilegeLevel) plan.PrivilegeLevel {
   144  	return plan.PrivilegeLevel{
   145  		Database:     privLevel.Database,
   146  		TableRoutine: privLevel.TableRoutine,
   147  	}
   148  }
   149  
   150  func (b *Builder) buildAuthenticatedUser(user ast.AccountWithAuth) plan.AuthenticatedUser {
   151  	authUser := plan.AuthenticatedUser{
   152  		UserName: convertAccountName(user.AccountName)[0],
   153  	}
   154  	if user.Auth1 != nil {
   155  		authUser.Identity = user.Auth1.Identity
   156  		if user.Auth1.Plugin == "mysql_native_password" && len(user.Auth1.Password) > 0 {
   157  			authUser.Auth1 = plan.AuthenticationMysqlNativePassword(user.Auth1.Password)
   158  		} else if len(user.Auth1.Plugin) > 0 {
   159  			authUser.Auth1 = plan.NewOtherAuthentication(user.Auth1.Password, user.Auth1.Plugin)
   160  		} else {
   161  			// We default to using the password, even if it's empty
   162  			authUser.Auth1 = plan.NewDefaultAuthentication(user.Auth1.Password)
   163  		}
   164  	}
   165  	// We do not support Auth2, Auth3, or AuthInitial, so error out if they are set, since nothing reads them
   166  	if user.Auth2 != nil || user.Auth3 != nil || user.AuthInitial != nil {
   167  		err := fmt.Errorf(`multi-factor authentication is not yet supported`)
   168  		b.handleErr(err)
   169  	}
   170  	//TODO: figure out how to represent the remaining authentication methods and multi-factor auth
   171  
   172  	return authUser
   173  }
   174  
   175  func (b *Builder) buildCreateUser(inScope *scope, n *ast.CreateUser) (outScope *scope) {
   176  	outScope = inScope.push()
   177  	authUsers := make([]plan.AuthenticatedUser, len(n.Users))
   178  	for i, user := range n.Users {
   179  		if user.Auth1 != nil && user.Auth1.RandomPassword {
   180  			b.handleErr(fmt.Errorf("random password generation is not currently supported; " +
   181  				"you can request support at https://github.com/dolthub/dolt/issues/new"))
   182  		}
   183  		authUsers[i] = b.buildAuthenticatedUser(user)
   184  	}
   185  	var tlsOptions *plan.TLSOptions
   186  	if n.TLSOptions != nil {
   187  		tlsOptions = &plan.TLSOptions{
   188  			SSL:     n.TLSOptions.SSL,
   189  			X509:    n.TLSOptions.X509,
   190  			Cipher:  n.TLSOptions.Cipher,
   191  			Issuer:  n.TLSOptions.Issuer,
   192  			Subject: n.TLSOptions.Subject,
   193  		}
   194  	}
   195  	var accountLimits *plan.AccountLimits
   196  	if n.AccountLimits != nil {
   197  		var maxQueries *int64
   198  		if n.AccountLimits.MaxQueriesPerHour != nil {
   199  			if val, err := strconv.ParseInt(string(n.AccountLimits.MaxQueriesPerHour.Val), 10, 64); err != nil {
   200  				b.handleErr(err)
   201  			} else {
   202  				maxQueries = &val
   203  			}
   204  		}
   205  		var maxUpdates *int64
   206  		if n.AccountLimits.MaxUpdatesPerHour != nil {
   207  			if val, err := strconv.ParseInt(string(n.AccountLimits.MaxUpdatesPerHour.Val), 10, 64); err != nil {
   208  				b.handleErr(err)
   209  			} else {
   210  				maxUpdates = &val
   211  			}
   212  		}
   213  		var maxConnections *int64
   214  		if n.AccountLimits.MaxConnectionsPerHour != nil {
   215  			if val, err := strconv.ParseInt(string(n.AccountLimits.MaxConnectionsPerHour.Val), 10, 64); err != nil {
   216  				b.handleErr(err)
   217  			} else {
   218  				maxConnections = &val
   219  			}
   220  		}
   221  		var maxUserConnections *int64
   222  		if n.AccountLimits.MaxUserConnections != nil {
   223  			if val, err := strconv.ParseInt(string(n.AccountLimits.MaxUserConnections.Val), 10, 64); err != nil {
   224  				b.handleErr(err)
   225  			} else {
   226  				maxUserConnections = &val
   227  			}
   228  		}
   229  		accountLimits = &plan.AccountLimits{
   230  			MaxQueriesPerHour:     maxQueries,
   231  			MaxUpdatesPerHour:     maxUpdates,
   232  			MaxConnectionsPerHour: maxConnections,
   233  			MaxUserConnections:    maxUserConnections,
   234  		}
   235  	}
   236  	var passwordOptions *plan.PasswordOptions
   237  	if n.PasswordOptions != nil {
   238  		var expirationTime *int64
   239  		if n.PasswordOptions.ExpirationTime != nil {
   240  			if val, err := strconv.ParseInt(string(n.PasswordOptions.ExpirationTime.Val), 10, 64); err != nil {
   241  				b.handleErr(err)
   242  			} else {
   243  				expirationTime = &val
   244  			}
   245  		}
   246  		var history *int64
   247  		if n.PasswordOptions.History != nil {
   248  			if val, err := strconv.ParseInt(string(n.PasswordOptions.History.Val), 10, 64); err != nil {
   249  				b.handleErr(err)
   250  			} else {
   251  				history = &val
   252  			}
   253  		}
   254  		var reuseInterval *int64
   255  		if n.PasswordOptions.ReuseInterval != nil {
   256  			if val, err := strconv.ParseInt(string(n.PasswordOptions.ReuseInterval.Val), 10, 64); err != nil {
   257  				b.handleErr(err)
   258  			} else {
   259  				reuseInterval = &val
   260  			}
   261  		}
   262  		var failedAttempts *int64
   263  		if n.PasswordOptions.FailedAttempts != nil {
   264  			if val, err := strconv.ParseInt(string(n.PasswordOptions.FailedAttempts.Val), 10, 64); err != nil {
   265  				b.handleErr(err)
   266  			} else {
   267  				failedAttempts = &val
   268  			}
   269  		}
   270  		var lockTime *int64
   271  		if n.PasswordOptions.LockTime != nil {
   272  			if val, err := strconv.ParseInt(string(n.PasswordOptions.LockTime.Val), 10, 64); err != nil {
   273  				b.handleErr(err)
   274  			} else {
   275  				lockTime = &val
   276  			}
   277  		}
   278  		passwordOptions = &plan.PasswordOptions{
   279  			RequireCurrentOptional: n.PasswordOptions.RequireCurrentOptional,
   280  			ExpirationTime:         expirationTime,
   281  			History:                history,
   282  			ReuseInterval:          reuseInterval,
   283  			FailedAttempts:         failedAttempts,
   284  			LockTime:               lockTime,
   285  		}
   286  	}
   287  	database := b.resolveDb("mysql")
   288  
   289  	outScope.node = &plan.CreateUser{
   290  		IfNotExists:     n.IfNotExists,
   291  		Users:           authUsers,
   292  		DefaultRoles:    convertAccountName(n.DefaultRoles...),
   293  		TLSOptions:      tlsOptions,
   294  		AccountLimits:   accountLimits,
   295  		PasswordOptions: passwordOptions,
   296  		Locked:          n.Locked,
   297  		Attribute:       n.Attribute,
   298  		MySQLDb:         database,
   299  	}
   300  	return outScope
   301  }
   302  
   303  func (b *Builder) buildRenameUser(inScope *scope, n *ast.RenameUser) (outScope *scope) {
   304  	oldNames := make([]plan.UserName, len(n.Accounts))
   305  	newNames := make([]plan.UserName, len(n.Accounts))
   306  	for i, account := range n.Accounts {
   307  		oldNames[i] = convertAccountName(account.From)[0]
   308  		newNames[i] = convertAccountName(account.To)[0]
   309  	}
   310  	outScope = inScope.push()
   311  	outScope.node = plan.NewRenameUser(oldNames, newNames)
   312  	return outScope
   313  }
   314  
   315  func (b *Builder) buildGrantPrivilege(inScope *scope, n *ast.GrantPrivilege) (outScope *scope) {
   316  	outScope = inScope.push()
   317  	var gau *plan.GrantUserAssumption
   318  	if n.As != nil {
   319  		gauType := plan.GrantUserAssumptionType_Default
   320  		switch n.As.Type {
   321  		case ast.GrantUserAssumptionType_None:
   322  			gauType = plan.GrantUserAssumptionType_None
   323  		case ast.GrantUserAssumptionType_All:
   324  			gauType = plan.GrantUserAssumptionType_All
   325  		case ast.GrantUserAssumptionType_AllExcept:
   326  			gauType = plan.GrantUserAssumptionType_AllExcept
   327  		case ast.GrantUserAssumptionType_Roles:
   328  			gauType = plan.GrantUserAssumptionType_Roles
   329  		}
   330  		gau = &plan.GrantUserAssumption{
   331  			Type:  gauType,
   332  			User:  convertAccountName(n.As.User)[0],
   333  			Roles: convertAccountName(n.As.Roles...),
   334  		}
   335  	}
   336  	granter := b.ctx.Session.Client().User
   337  	level := convertPrivilegeLevel(n.PrivilegeLevel)
   338  	if strings.ToLower(level.Database) == sql.InformationSchemaDatabaseName {
   339  		err := sql.ErrDatabaseAccessDeniedForUser.New(granter, level.Database)
   340  		b.handleErr(err)
   341  	}
   342  
   343  	outScope.node = &plan.Grant{
   344  		Privileges:      convertPrivilege(n.Privileges...),
   345  		ObjectType:      convertObjectType(n.ObjectType),
   346  		PrivilegeLevel:  level,
   347  		Users:           convertAccountName(n.To...),
   348  		WithGrantOption: n.WithGrantOption,
   349  		As:              gau,
   350  		MySQLDb:         b.resolveDb("mysql"),
   351  		Catalog:         &b.cat,
   352  	}
   353  
   354  	return outScope
   355  }
   356  
   357  func (b *Builder) buildShowGrants(inScope *scope, n *ast.ShowGrants) (outScope *scope) {
   358  	var currentUser bool
   359  	var user *plan.UserName
   360  	if n.For != nil {
   361  		currentUser = false
   362  		user = &convertAccountName(*n.For)[0]
   363  	} else {
   364  		currentUser = true
   365  		client := b.ctx.Session.Client()
   366  		user = &plan.UserName{
   367  			Name:    client.User,
   368  			Host:    client.Address,
   369  			AnyHost: client.Address == "%",
   370  		}
   371  	}
   372  	outScope = inScope.push()
   373  	outScope.node = &plan.ShowGrants{
   374  		CurrentUser: currentUser,
   375  		For:         user,
   376  		Using:       convertAccountName(n.Using...),
   377  		MySQLDb:     b.resolveDb("mysql"),
   378  	}
   379  	return
   380  }
   381  
   382  func (b *Builder) buildFlush(inScope *scope, f *ast.Flush) (outScope *scope) {
   383  	outScope = inScope.push()
   384  	var writesToBinlog = true
   385  	switch strings.ToLower(f.Type) {
   386  	case "no_write_to_binlog", "local":
   387  		//writesToBinlog = false
   388  		err := fmt.Errorf("%s not supported", f.Type)
   389  		b.handleErr(err)
   390  	}
   391  
   392  	// MySQL docs: https://dev.mysql.com/doc/refman/8.0/en/flush.html
   393  	// Some opts should be no-ops, but we should support others, so have those be errors
   394  	opt := strings.ToLower(f.Option.Name)
   395  	if strings.HasPrefix(opt, "relay logs for channel") {
   396  		err := fmt.Errorf("%s not supported", f.Option.Name)
   397  		b.handleErr(err)
   398  	}
   399  	switch opt {
   400  	case "privileges":
   401  		node, _ := plan.NewFlushPrivileges(writesToBinlog).WithDatabase(b.resolveDb("mysql"))
   402  		outScope.node = node
   403  	case "binary logs", "engine logs":
   404  		node := plan.Nothing{}
   405  		outScope.node = node
   406  	case "error logs", "relay logs", "general logs", "slow logs", "status":
   407  		err := fmt.Errorf("%s not supported", f.Option.Name)
   408  		b.handleErr(err)
   409  	default:
   410  		err := fmt.Errorf("%s not supported", f.Option.Name)
   411  		b.handleErr(err)
   412  	}
   413  	return outScope
   414  }
   415  
   416  func (b *Builder) buildCreateRole(inScope *scope, n *ast.CreateRole) (outScope *scope) {
   417  	outScope = inScope.push()
   418  	outScope.node = &plan.CreateRole{
   419  		IfNotExists: n.IfNotExists,
   420  		Roles:       convertAccountName(n.Roles...),
   421  		MySQLDb:     b.resolveDb("mysql"),
   422  	}
   423  	return
   424  }
   425  
   426  func (b *Builder) buildDropRole(inScope *scope, n *ast.DropRole) (outScope *scope) {
   427  	outScope = inScope.push()
   428  	outScope.node = &plan.DropRole{
   429  		IfExists: n.IfExists,
   430  		Roles:    convertAccountName(n.Roles...),
   431  		MySQLDb:  b.resolveDb("mysql"),
   432  	}
   433  	return
   434  }
   435  
   436  func (b *Builder) buildDropUser(inScope *scope, n *ast.DropUser) (outScope *scope) {
   437  	outScope = inScope.push()
   438  	outScope.node = &plan.DropUser{
   439  		IfExists: n.IfExists,
   440  		Users:    convertAccountName(n.AccountNames...),
   441  		MySQLDb:  b.resolveDb("mysql"),
   442  	}
   443  	return
   444  }
   445  
   446  func (b *Builder) buildGrantRole(inScope *scope, n *ast.GrantRole) (outScope *scope) {
   447  	outScope = inScope.push()
   448  	outScope.node = &plan.GrantRole{
   449  		Roles:           convertAccountName(n.Roles...),
   450  		TargetUsers:     convertAccountName(n.To...),
   451  		WithAdminOption: n.WithAdminOption,
   452  		MySQLDb:         b.resolveDb("mysql"),
   453  	}
   454  	return
   455  }
   456  
   457  func (b *Builder) buildGrantProxy(inScope *scope, n *ast.GrantProxy) (outScope *scope) {
   458  	outScope = inScope.push()
   459  
   460  	outScope.node = plan.NewGrantProxy(
   461  		convertAccountName(n.On)[0],
   462  		convertAccountName(n.To...),
   463  		n.WithGrantOption,
   464  	)
   465  	return
   466  }
   467  
   468  func (b *Builder) buildRevokePrivilege(inScope *scope, n *ast.RevokePrivilege) (outScope *scope) {
   469  	privs := convertPrivilege(n.Privileges...)
   470  	objType := convertObjectType(n.ObjectType)
   471  	level := convertPrivilegeLevel(n.PrivilegeLevel)
   472  	users := convertAccountName(n.From...)
   473  	revoker := b.ctx.Session.Client().User
   474  	if strings.ToLower(level.Database) == sql.InformationSchemaDatabaseName {
   475  		b.handleErr(sql.ErrDatabaseAccessDeniedForUser.New(revoker, level.Database))
   476  	}
   477  	outScope = inScope.push()
   478  	outScope.node = &plan.Revoke{
   479  		Privileges:     privs,
   480  		ObjectType:     objType,
   481  		PrivilegeLevel: level,
   482  		Users:          users,
   483  		MySQLDb:        b.resolveDb("mysql"),
   484  	}
   485  	return
   486  }
   487  
   488  func (b *Builder) buildRevokeAllPrivileges(inScope *scope, n *ast.RevokeAllPrivileges) (outScope *scope) {
   489  	outScope = inScope.push()
   490  	outScope.node = plan.NewRevokeAll(convertAccountName(n.From...))
   491  	return
   492  }
   493  
   494  func (b *Builder) buildRevokeRole(inScope *scope, n *ast.RevokeRole) (outScope *scope) {
   495  	outScope = inScope.push()
   496  	outScope.node = &plan.RevokeRole{
   497  		Roles:       convertAccountName(n.Roles...),
   498  		TargetUsers: convertAccountName(n.From...),
   499  		MySQLDb:     b.resolveDb("mysql"),
   500  	}
   501  	return
   502  }
   503  
   504  func (b *Builder) buildRevokeProxy(inScope *scope, n *ast.RevokeProxy) (outScope *scope) {
   505  	outScope = inScope.push()
   506  	outScope.node = plan.NewRevokeProxy(convertAccountName(n.On)[0], convertAccountName(n.From...))
   507  	return
   508  }
   509  
   510  func (b *Builder) buildShowPrivileges(inScope *scope, n *ast.ShowPrivileges) (outScope *scope) {
   511  	outScope = inScope.push()
   512  	outScope.node = plan.NewShowPrivileges()
   513  	return
   514  }