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 }