From 2f877e80301ded17ed73e23f93a0eaf8a315899c Mon Sep 17 00:00:00 2001 From: kennytm Date: Sat, 10 Apr 2021 00:18:33 +0800 Subject: [PATCH] planner,privilege: requires extra privileges for REPLACE and INSERT ON DUPLICATE statements (#23911) --- planner/core/planbuilder.go | 22 ++++++++++++-- privilege/privileges/privileges_test.go | 39 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 504cc310a811f..d035835e86989 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2465,15 +2465,31 @@ func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) ( return nil, ErrPartitionClauseOnNonpartitioned } + user := b.ctx.GetSessionVars().User var authErr error - if b.ctx.GetSessionVars().User != nil { - authErr = ErrTableaccessDenied.GenWithStackByArgs("INSERT", b.ctx.GetSessionVars().User.AuthUsername, - b.ctx.GetSessionVars().User.AuthHostname, tableInfo.Name.L) + if user != nil { + authErr = ErrTableaccessDenied.GenWithStackByArgs("INSERT", user.AuthUsername, user.AuthHostname, tableInfo.Name.L) } b.visitInfo = appendVisitInfo(b.visitInfo, mysql.InsertPriv, tn.DBInfo.Name.L, tableInfo.Name.L, "", authErr) + // `REPLACE INTO` requires both INSERT + DELETE privilege + // `ON DUPLICATE KEY UPDATE` requires both INSERT + UPDATE privilege + var extraPriv mysql.PrivilegeType + if insert.IsReplace { + extraPriv = mysql.DeletePriv + } else if insert.OnDuplicate != nil { + extraPriv = mysql.UpdatePriv + } + if extraPriv != 0 { + if user != nil { + cmd := strings.ToUpper(mysql.Priv2Str[extraPriv]) + authErr = ErrTableaccessDenied.GenWithStackByArgs(cmd, user.AuthUsername, user.AuthHostname, tableInfo.Name.L) + } + b.visitInfo = appendVisitInfo(b.visitInfo, extraPriv, tn.DBInfo.Name.L, tableInfo.Name.L, "", authErr) + } + mockTablePlan := LogicalTableDual{}.Init(b.ctx, b.getSelectOffset()) mockTablePlan.SetSchema(insertPlan.tableSchema) mockTablePlan.names = insertPlan.tableColNames diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 8b945b1ee1b63..5d32de0d16d7b 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -916,6 +916,45 @@ func (s *testPrivilegeSuite) TestShowCreateTable(c *C) { mustExec(c, se, `SHOW CREATE TABLE mysql.user`) } +func (s *testPrivilegeSuite) TestReplaceAndInsertOnDuplicate(c *C) { + se := newSession(c, s.store, s.dbName) + mustExec(c, se, `CREATE USER tr_insert`) + mustExec(c, se, `CREATE USER tr_update`) + mustExec(c, se, `CREATE USER tr_delete`) + mustExec(c, se, `CREATE TABLE t1 (a int primary key, b int)`) + mustExec(c, se, `GRANT INSERT ON t1 TO tr_insert`) + mustExec(c, se, `GRANT UPDATE ON t1 TO tr_update`) + mustExec(c, se, `GRANT DELETE ON t1 TO tr_delete`) + + // Restrict the permission to INSERT only. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_insert", Hostname: "localhost", AuthUsername: "tr_insert", AuthHostname: "%"}, nil, nil), IsTrue) + + // REPLACE requires INSERT + DELETE privileges, having INSERT alone is insufficient. + _, err := se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (1, 2)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]DELETE command denied to user 'tr_insert'@'%' for table 't1'") + + // INSERT ON DUPLICATE requires INSERT + UPDATE privileges, having INSERT alone is insufficient. + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (3, 4) ON DUPLICATE KEY UPDATE b = 5`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]UPDATE command denied to user 'tr_insert'@'%' for table 't1'") + + // Plain INSERT should work. + mustExec(c, se, `INSERT INTO t1 VALUES (6, 7)`) + + // Also check that having DELETE alone is insufficient for REPLACE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_delete", Hostname: "localhost", AuthUsername: "tr_delete", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `REPLACE INTO t1 VALUES (8, 9)`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_delete'@'%' for table 't1'") + + // Also check that having UPDATE alone is insufficient for INSERT ON DUPLICATE. + c.Assert(se.Auth(&auth.UserIdentity{Username: "tr_update", Hostname: "localhost", AuthUsername: "tr_update", AuthHostname: "%"}, nil, nil), IsTrue) + _, err = se.ExecuteInternal(context.Background(), `INSERT INTO t1 VALUES (10, 11) ON DUPLICATE KEY UPDATE b = 12`) + c.Assert(terror.ErrorEqual(err, core.ErrTableaccessDenied), IsTrue) + c.Assert(err.Error(), Equals, "[planner:1142]INSERT command denied to user 'tr_update'@'%' for table 't1'") +} + func (s *testPrivilegeSuite) TestAnalyzeTable(c *C) { se := newSession(c, s.store, s.dbName)