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 }