diff --git a/data/test/tabletserver/exec_cases.txt b/data/test/tabletserver/exec_cases.txt index eeb0131ff5b..7b19abae91e 100644 --- a/data/test/tabletserver/exec_cases.txt +++ b/data/test/tabletserver/exec_cases.txt @@ -3,6 +3,16 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + }, + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1 union select * from b where 1 != 1", "FullQuery": "select * from a union select * from b limit :#maxLimit" } @@ -12,6 +22,16 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + }, + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1 union select * from b where 1 != 1", "FullQuery": "select * from a union select * from b limit 10" } @@ -21,6 +41,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select distinct * from a limit :#maxLimit" } @@ -30,6 +56,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1 group by b", "FullQuery": "select * from a group by b limit :#maxLimit" } @@ -39,6 +71,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select * from a having b = 1 limit :#maxLimit" } @@ -48,6 +86,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select * from a limit 5" } @@ -57,6 +101,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select * from a limit 10, 5" } @@ -66,6 +116,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select * from a limit 10, 5" } @@ -75,6 +131,12 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a.b where 1 != 1", "FullQuery": "select * from a.b limit :#maxLimit" } @@ -84,6 +146,16 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + }, + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a, b where 1 != 1", "FullQuery": "select * from a, b limit :#maxLimit" } @@ -93,6 +165,16 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + }, + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a join b where 1 != 1", "FullQuery": "select * from a join b limit :#maxLimit" } @@ -102,6 +184,16 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + }, + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from a right join b on c = d where 1 != 1", "FullQuery": "select * from a right join b on c = d limit :#maxLimit" } @@ -111,6 +203,12 @@ { "PlanID": "PASS_SELECT", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 0 + } + ], "FieldQuery": "select * from (b) where 1 != 1", "FullQuery": "select * from (b) limit :#maxLimit" } @@ -120,6 +218,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "select :bv from a limit :#maxLimit" } @@ -128,6 +232,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select eid from a where 1 != 1", "FullQuery": "select eid from a limit :#maxLimit" } @@ -137,6 +247,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select eid as foo from a where 1 != 1", "FullQuery": "select eid as foo from a limit :#maxLimit" } @@ -146,6 +262,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select * from a where 1 != 1", "FullQuery": "select * from a limit :#maxLimit" } @@ -155,6 +277,12 @@ { "PlanID": "PASS_SELECT", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select c.eid from a as c where 1 != 1", "FullQuery": "select c.eid from a as c limit :#maxLimit" } @@ -164,6 +292,12 @@ { "PlanID": "SELECT_LOCK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select eid from a where 1 != 1", "FullQuery": "select eid from a limit :#maxLimit for update" } @@ -173,6 +307,12 @@ { "PlanID": "SELECT_LOCK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 0 + } + ], "FieldQuery": "select eid from a where 1 != 1", "FullQuery": "select eid from a limit :#maxLimit lock in share mode" } @@ -183,6 +323,12 @@ "PlanID": "PASS_DML", "Reason": "TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into b.a(eid, id) values (1, :a)" } @@ -192,6 +338,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into b.a(eid, id) values (1, :a)" } @@ -200,6 +352,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, :a)", "OuterQuery": "insert into a(eid, id) values (1, :a)", "PKValues": [[1], [":a"]] @@ -211,6 +369,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, :a)" } @@ -219,6 +383,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(id) values (1)", "OuterQuery": "insert into a(id) values (1)", "PKValues": [0, [1]] @@ -229,6 +399,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "d", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "insert into d(id) values (1)", "OuterQuery": "insert into d(id) values (1)", "PKValues": ["0"] @@ -239,6 +415,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "d", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "insert into d(id) values (1)", "OuterQuery": "insert into d(id) values (1)", "PKValues": ["0"] @@ -253,6 +435,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (-1, 2)", "OuterQuery": "insert into a(eid, id) values (-1, 2)", "PKValues": [[-1], [2]] @@ -263,6 +451,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2)", "OuterQuery": "insert into a(eid, id) values (1, 2)", "PKValues": [[1], [2]] @@ -274,6 +468,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "COMPLEX_EXPR", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (~1, 2)" } @@ -283,6 +483,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "COMPLEX_EXPR", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1 + 1, 2)" } @@ -292,6 +498,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "COMPLEX_EXPR", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (0x04, 2)" } @@ -301,6 +513,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "TABLE_NOINDEX", "TableName": "c", + "Permissions": [ + { + "TableName": "c", + "Role": 1 + } + ], "FullQuery": "insert into c(eid, id) values (1, 2)" } @@ -309,6 +527,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a values (1, 2, 'name', 'foo', 'camelcase')", "OuterQuery": "insert into a(eid, id, name, foo, CamelCase) values (1, 2, 'name', 'foo', 'camelcase')", "PKValues": [[1], [2]] @@ -319,6 +543,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2) on duplicate key update name = func(a)", "OuterQuery": "insert into a(eid, id) values (1, 2)", "UpsertQuery": "update a set name = func(a) where :#pk", @@ -331,6 +561,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2) on duplicate key update name = func(a)" } @@ -339,6 +575,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "insert into b(eid, id) values (1, 2) on duplicate key update name = func(a)", "OuterQuery": "insert into b(eid, id) values (1, 2) on duplicate key update name = func(a)", "PKValues": [[1], [2]] @@ -349,6 +591,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2) on duplicate key update eid = 2", "OuterQuery": "insert into a(eid, id) values (1, 2)", "UpsertQuery": "update a set eid = 2 where :#pk", @@ -361,6 +609,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, 2, 'foo') on duplicate key update name = values(name)", "OuterQuery": "insert into a(eid, id, name) values (1, 2, 'foo')", "UpsertQuery": "update a set name = ('foo') where :#pk", @@ -372,6 +626,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, 2, 'foo') on duplicate key update name = concat(values(name), 'foo')", "OuterQuery": "insert into a(eid, id, name) values (1, 2, 'foo')", "UpsertQuery": "update a set name = concat(('foo'), 'foo') where :#pk", @@ -383,6 +643,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, 2, 3) on duplicate key update name = values(name) + 5", "OuterQuery": "insert into a(eid, id, name) values (1, 2, 3)", "UpsertQuery": "update a set name = (3) + 5 where :#pk", @@ -394,6 +660,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, :id, :name) on duplicate key update name = values(name), id = values(id)", "OuterQuery": "insert into a(eid, id, name) values (1, :id, :name)", "UpsertQuery": "update a set name = (:name), id = (:id) where :#pk", @@ -407,6 +679,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "COMPLEX_EXPR", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1 + 1, 2) on duplicate key update eid = values(eid) + 1" } @@ -416,6 +694,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "PK_CHANGE", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, 2, 1 + 1) on duplicate key update eid = values(name)", "PKValues": [[1],[2]] } @@ -426,6 +710,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "PK_CHANGE", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2) on duplicate key update eid = values(name)", "PKValues": [[1],[2]] } @@ -439,6 +729,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id) values (1, 2) on duplicate key update eid = values(eid), id = values(id)", "OuterQuery": "insert into a(eid, id) values (1, 2)", "UpsertQuery": "update a set eid = (1), id = (2) where :#pk", @@ -451,6 +747,12 @@ options:PassthroughDMLs { "PlanID": "UPSERT_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(eid, id, name) values (1, 2, 'foo') on duplicate key update eid = 2, id = values(id), name = func()", "OuterQuery": "insert into a(eid, id, name) values (1, 2, 'foo')", "UpsertQuery": "update a set eid = 2, id = (2), name = func() where :#pk", @@ -464,6 +766,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "PK_CHANGE", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(id, eid) values (1, 2) on duplicate key update eid = func(a)", "PKValues": [[2], [1]] } @@ -474,6 +782,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "UPSERT_MULTI_ROW", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "insert into a(id, eid) values (1, 2), (2, 3) on duplicate key update name = func(a)", "PKValues": [[2,3],[1,2]] } @@ -483,6 +797,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "insert into b(id, eid) values (1, 2), (2, 3) on duplicate key update name = func(a)", "OuterQuery": "insert into b(id, eid) values (1, 2), (2, 3) on duplicate key update name = func(a)", "PKValues": [[2,3],[1,2]] @@ -493,6 +813,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "insert into b(id, eid) values (1, 2), (2, 3) on duplicate key update id = 1", "OuterQuery": "insert into b(id, eid) values (1, 2), (2, 3) on duplicate key update id = 1", "PKValues": [[2,3],[1,2]], @@ -504,6 +830,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "insert into b(id, eid) values (1, 2), (3, 4) on duplicate key update id = values(eid)", "OuterQuery": "insert into b(id, eid) values (1, 2), (3, 4) on duplicate key update id = values(eid)", "PKValues": [[2,4],[1,3]], @@ -516,6 +848,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "UPSERT_SUBQUERY", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "insert into b(id, eid) select * from a on duplicate key update name = func(a)" } @@ -524,6 +866,16 @@ options:PassthroughDMLs { "PlanID": "INSERT_SUBQUERY", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "insert into b(eid, id) select * from a", "OuterQuery": "insert into b(eid, id) values :#values", "Subquery": "select * from a limit :#maxLimit", @@ -536,6 +888,16 @@ options:PassthroughDMLs { "PlanID": "INSERT_SUBQUERY", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "insert into b select * from a", "OuterQuery": "insert into b(eid, id) values :#values", "Subquery": "select * from a limit :#maxLimit", @@ -548,6 +910,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "insert into b(eid, id) values (1, 2), (3, 4)", "OuterQuery": "insert into b(eid, id) values (1, 2), (3, 4)", "PKValues": [[1, 3], [2, 4]] @@ -558,6 +926,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_MESSAGE", "TableName": "msg", + "Permissions": [ + { + "TableName": "msg", + "Role": 1 + } + ], "FullQuery": "insert into msg(time_scheduled, id, message) values (1, 2, 'aa')", "OuterQuery": "insert into msg(time_scheduled, id, message, time_next, time_created, epoch) values (1, 2, 'aa', 1, :#time_now, 0)", "PKValues": [[1], [2]] @@ -568,6 +942,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_MESSAGE", "TableName": "msg", + "Permissions": [ + { + "TableName": "msg", + "Role": 1 + } + ], "FullQuery": "insert into msg(id, message) values (2, 'aa')", "OuterQuery": "insert into msg(id, message, time_scheduled, time_next, time_created, epoch) values (2, 'aa', :#time_now, :#time_now, :#time_now, 0)", "PKValues": [[":#time_now"], [2]] @@ -578,6 +958,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_MESSAGE", "TableName": "msg", + "Permissions": [ + { + "TableName": "msg", + "Role": 1 + } + ], "FullQuery": "insert into msg(time_scheduled, id, message) values (1, 2, 'aa'), (3, 4, 'bb')", "OuterQuery": "insert into msg(time_scheduled, id, message, time_next, time_created, epoch) values (1, 2, 'aa', 1, :#time_now, 0), (3, 4, 'bb', 3, :#time_now, 0)", "PKValues": [ @@ -634,6 +1020,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason":"REPLACE", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "replace into b(eid, id) values (1, 2), (3, 4)" } @@ -643,6 +1035,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "replace into b(eid, id) values (1, 2), (3, 4)" } @@ -652,6 +1050,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason":"REPLACE", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "replace into b(eid, id) values (1, 2)" } @@ -661,6 +1065,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason":"REPLACE", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "replace into b(eid, id) select * from a" } @@ -670,6 +1084,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason":"REPLACE", "TableName": "", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "a", + "Role": 0 + } + ], "FullQuery": "replace into b select * from a" } @@ -678,6 +1102,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "d", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "update d set foo = 'foo' where name in ('a', 'b') limit 1", "OuterQuery": "update d set foo = 'foo' where :#pk", "Subquery": "select name from d where name in ('a', 'b') limit 1 for update", @@ -690,6 +1120,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "update d set foo = 'foo' where name in ('a', 'b') limit 1" } @@ -699,6 +1135,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update b.a set name = 'foo' where eid = 1 and id = 1" } @@ -708,6 +1150,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update b.a set name = 'foo' where eid = 1 and id = 1" } @@ -717,6 +1165,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "MULTI_TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + }, + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "update a, b set a.name = 'foo' where a.id = b.id and b.var = 'test'" } @@ -726,6 +1184,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "MULTI_TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + }, + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "update a join b on a.id = b.id set a.name = 'foo' where b.var = 'test'" } @@ -734,6 +1202,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "update b set eid = 1", "OuterQuery": "update b set eid = 1 where :#pk", "Subquery": "select eid, id from b limit :#maxLimit for update", @@ -751,6 +1225,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "PK_CHANGE", "TableName": "b", + "Permissions": [ + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "update b set eid = foo()", "WhereClause": "" } @@ -760,6 +1240,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo'", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a limit :#maxLimit for update", @@ -771,6 +1257,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid + 1 = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a where eid + 1 = 1 limit :#maxLimit for update", @@ -782,6 +1274,12 @@ options:PassthroughDMLs { "PlanID": "DML_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1 and id = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "PKValues": [1, 1], @@ -793,6 +1291,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a where eid = 1 limit :#maxLimit for update", @@ -804,6 +1308,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1.0 and id = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a where eid = 1.0 and id = 1 limit :#maxLimit for update", @@ -815,6 +1325,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1 limit 10", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a where eid = 1 limit 10 for update", @@ -826,6 +1342,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1 and name = 'foo'", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a where eid = 1 and name = 'foo' limit :#maxLimit for update", @@ -838,6 +1360,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "TABLE_NOINDEX", "TableName": "c", + "Permissions": [ + { + "TableName": "c", + "Role": 1 + } + ], "FullQuery": "update c set eid = 1", "WhereClause": "" } @@ -847,6 +1375,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"update a set name = 'foo' where eid + 1 = 1 and id = 1", "OuterQuery":"update a set name = 'foo' where :#pk", "Subquery":"select eid, id from a where eid + 1 = 1 and id = 1 limit :#maxLimit for update", @@ -858,6 +1392,12 @@ options:PassthroughDMLs { "PlanID": "DML_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where (eid = 1) and id = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "PKValues": [1, 1], @@ -869,6 +1409,12 @@ options:PassthroughDMLs { "PlanID":"DML_PK", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"update a set name = 'foo' where eid in (1, 2) and id = 1", "OuterQuery":"update a set name = 'foo' where :#pk", "PKValues":[[1,2],1], @@ -880,6 +1426,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"update a set name = 'foo' where eid in (1, 2) and id in (1, 2)", "OuterQuery":"update a set name = 'foo' where :#pk", "Subquery":"select eid, id from a where eid in (1, 2) and id in (1, 2) limit :#maxLimit for update", @@ -891,6 +1443,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"update a set name = 'foo' where eid = 1 and eid = 2", "OuterQuery":"update a set name = 'foo' where :#pk", "Subquery":"select eid, id from a where eid = 1 and eid = 2 limit :#maxLimit for update", @@ -902,6 +1460,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a set name = 'foo' where eid = 1 order by id desc", "OuterQuery": "update a set name = 'foo' where :#pk order by id desc", "Subquery": "select eid, id from a where eid = 1 order by id desc limit :#maxLimit for update", @@ -914,6 +1478,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "update a use index (b) set name = 'foo' where eid = 1", "OuterQuery": "update a set name = 'foo' where :#pk", "Subquery": "select eid, id from a use index (b) where eid = 1 limit :#maxLimit for update", @@ -925,6 +1495,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "d", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "delete from d where name in ('a', 'b') limit 1", "OuterQuery": "delete from d where :#pk", "Subquery": "select name from d where name in ('a', 'b') limit 1 for update", @@ -937,6 +1513,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "d", + "Role": 1 + } + ], "FullQuery": "delete from d where name in ('a', 'b') limit 1" } @@ -946,6 +1528,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from b.a where eid = 1 and id = 1" } @@ -954,6 +1542,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a", "OuterQuery": "delete from a where :#pk", "Subquery": "select eid, id from a limit :#maxLimit for update", @@ -965,6 +1559,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid + 1 = 1", "OuterQuery": "delete from a where :#pk", "Subquery": "select eid, id from a where eid + 1 = 1 limit :#maxLimit for update", @@ -976,6 +1576,12 @@ options:PassthroughDMLs { "PlanID": "DML_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1 and id = 1", "OuterQuery": "delete from a where :#pk", "PKValues": [1, 1], @@ -988,6 +1594,12 @@ options:PassthroughDMLs { "PlanID": "PASS_DML", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1 and id = 1" } @@ -996,6 +1608,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1", "OuterQuery": "delete from a where :#pk", "Subquery": "select eid, id from a where eid = 1 limit :#maxLimit for update", @@ -1007,6 +1625,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1 order by id desc", "OuterQuery": "delete from a where :#pk order by id desc", "Subquery": "select eid, id from a where eid = 1 order by id desc limit :#maxLimit for update", @@ -1018,6 +1642,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1.0 and id = 1", "OuterQuery": "delete from a where :#pk", "Subquery": "select eid, id from a where eid = 1.0 and id = 1 limit :#maxLimit for update", @@ -1029,6 +1659,12 @@ options:PassthroughDMLs { "PlanID": "DML_SUBQUERY", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where eid = 1 and name = 'foo'", "OuterQuery": "delete from a where :#pk", "Subquery": "select eid, id from a where eid = 1 and name = 'foo' limit :#maxLimit for update", @@ -1041,6 +1677,12 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "TABLE_NOINDEX", "TableName": "c", + "Permissions": [ + { + "TableName": "c", + "Role": 1 + } + ], "FullQuery": "delete from c", "WhereClause": "" } @@ -1050,6 +1692,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"delete from a where eid + 1 = 1 and id = 1", "OuterQuery":"delete from a where :#pk", "Subquery":"select eid, id from a where eid + 1 = 1 and id = 1 limit :#maxLimit for update", @@ -1061,6 +1709,12 @@ options:PassthroughDMLs { "PlanID": "DML_PK", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery": "delete from a where (eid = 1) and id = 1", "OuterQuery": "delete from a where :#pk", "PKValues": [1, 1], @@ -1072,6 +1726,12 @@ options:PassthroughDMLs { "PlanID":"DML_PK", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"delete from a where eid in (1, 2) and id = 1", "OuterQuery":"delete from a where :#pk", "PKValues":[[1,2],1], @@ -1083,6 +1743,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"delete from a where eid in (1, 2) and id in (1, 2)", "OuterQuery":"delete from a where :#pk", "Subquery":"select eid, id from a where eid in (1, 2) and id in (1, 2) limit :#maxLimit for update", @@ -1094,6 +1760,12 @@ options:PassthroughDMLs { "PlanID":"DML_SUBQUERY", "TableName":"a", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + } + ], "FullQuery":"delete from a where eid = 1 and eid = 2", "OuterQuery":"delete from a where :#pk", "Subquery":"select eid, id from a where eid = 1 and eid = 2 limit :#maxLimit for update", @@ -1106,6 +1778,20 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "MULTI_TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + }, + { + "TableName": "b", + "Role": 1 + }, + { + "TableName": "c", + "Role": 1 + } + ], "FullQuery": "delete a, b from a, b, c where a.id = b.id and b.id = c.id and c.name = 'foo'" } @@ -1115,6 +1801,16 @@ options:PassthroughDMLs "PlanID": "PASS_DML", "Reason": "MULTI_TABLE", "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 1 + }, + { + "TableName": "b", + "Role": 1 + } + ], "FullQuery": "delete a from a join b on a.id = b.id where a.name = 'foo'" } @@ -1123,6 +1819,12 @@ options:PassthroughDMLs { "PlanID": "NEXTVAL", "TableName": "seq", + "Permissions": [ + { + "TableName": "seq", + "Role": 0 + } + ], "PKValues":[1] } @@ -1131,6 +1833,12 @@ options:PassthroughDMLs { "PlanID": "NEXTVAL", "TableName": "seq", + "Permissions": [ + { + "TableName": "seq", + "Role": 0 + } + ], "PKValues":[10] } @@ -1139,6 +1847,12 @@ options:PassthroughDMLs { "PlanID": "NEXTVAL", "TableName": "seq", + "Permissions": [ + { + "TableName": "seq", + "Role": 0 + } + ], "PKValues":[":a"] } @@ -1147,6 +1861,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "auto", + "Permissions": [ + { + "TableName": "auto", + "Role": 1 + } + ], "FullQuery": "insert into auto values ()", "OuterQuery": "insert into auto(id) values (null)", "PKValues":[ @@ -1159,6 +1879,12 @@ options:PassthroughDMLs { "PlanID": "INSERT_PK", "TableName": "with_defaults", + "Permissions": [ + { + "TableName": "with_defaults", + "Role": 1 + } + ], "FullQuery": "insert into with_defaults values ()", "OuterQuery": "insert into with_defaults(aid, bid, cid) values (3, -2, null)", "PKValues":[ @@ -1210,42 +1936,94 @@ options:PassthroughDMLs "create table a(a int, b varchar(8))" { "PlanID": "DDL", - "TableName": "" + "TableName": "", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + } + ] } # alter "alter table a add column(a int)" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "a", + "Role": 2 + } + ] } # alter rename "alter table a rename b" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "b", + "Role": 2 + } + ] } # rename "rename table a to b" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "b", + "Role": 2 + } + ] } # drop "drop table a" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + } + ] } # analyze "analyze table a" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "a", + "Role": 2 + } + ] } # reorganize partition with bind @@ -1253,6 +2031,12 @@ options:PassthroughDMLs { "PlanID": "DDL", "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + } + ], "FullQuery": "alter table a reorganize partition b into (partition c values less than (:bv), partition d values less than (maxvalue))" } @@ -1260,7 +2044,17 @@ options:PassthroughDMLs "alter table a partition by range (id) (partition p0 values less than (10), partition p1 values less than (maxvalue))" { "PlanID": "DDL", - "TableName": "a" + "TableName": "a", + "Permissions": [ + { + "TableName": "a", + "Role": 2 + }, + { + "TableName": "a", + "Role": 2 + } + ] } # show diff --git a/data/test/tabletserver/stream_cases.txt b/data/test/tabletserver/stream_cases.txt index e70dd8fef12..a42a8eabede 100644 --- a/data/test/tabletserver/stream_cases.txt +++ b/data/test/tabletserver/stream_cases.txt @@ -3,6 +3,7 @@ { "PlanID": "SELECT_STREAM", "TableName": "a", + "Permissions":[{"TableName":"a","Role":0}], "FullQuery": "select * from a" } @@ -11,6 +12,7 @@ { "PlanID": "SELECT_STREAM", "TableName": "", + "Permissions":[{"TableName":"a","Role":0},{"TableName":"b","Role":0}], "FullQuery": "select * from a join b" } @@ -23,6 +25,7 @@ { "PlanID": "SELECT_STREAM", "TableName": "", + "Permissions":[{"TableName":"a","Role":0},{"TableName":"b","Role":0}], "FullQuery": "select * from a union select * from b" } diff --git a/go/vt/vttablet/endtoend/acl_test.go b/go/vt/vttablet/endtoend/acl_test.go index e988b371c33..a02c43dcbeb 100644 --- a/go/vt/vttablet/endtoend/acl_test.go +++ b/go/vt/vttablet/endtoend/acl_test.go @@ -23,12 +23,13 @@ import ( "testing" "github.com/youtube/vitess/go/sqltypes" - querypb "github.com/youtube/vitess/go/vt/proto/query" "github.com/youtube/vitess/go/vt/vttablet/endtoend/framework" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/rules" + + querypb "github.com/youtube/vitess/go/vt/proto/query" ) -func TestTableACLNoAccess(t *testing.T) { +func TestTableACL(t *testing.T) { client := framework.NewClient() aclErr := "table acl error" @@ -79,6 +80,17 @@ func TestTableACLNoAccess(t *testing.T) { }, { query: "alter table vitess_acl_all_user_read_only comment 'comment'", err: aclErr, + }, { + query: "select * from vitess_acl_read_only, vitess_acl_no_access", + err: aclErr, + }, { + query: "delete from vitess_acl_read_write where key1=(select key1 from vitess_acl_no_access)", + err: aclErr, + }, { + query: "delete from vitess_acl_read_write where key1=(select key1 from vitess_acl_read_only)", + }, { + query: "update vitess_acl_read_write join vitess_acl_read_only on 1!=1 set key1=1", + err: aclErr, }} for _, tcase := range execCases { @@ -90,7 +102,7 @@ func TestTableACLNoAccess(t *testing.T) { continue } if err == nil || !strings.HasPrefix(err.Error(), tcase.err) { - t.Errorf("Error: %v, must start with %s", err, tcase.err) + t.Errorf("Execute(%s): Error: %v, must start with %s", tcase.query, err, tcase.err) } } diff --git a/go/vt/vttablet/tabletserver/planbuilder/permission.go b/go/vt/vttablet/tabletserver/planbuilder/permission.go new file mode 100644 index 00000000000..944c4413ba9 --- /dev/null +++ b/go/vt/vttablet/tabletserver/planbuilder/permission.go @@ -0,0 +1,114 @@ +/* +Copyright 2018 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package planbuilder + +import ( + "fmt" + + "github.com/youtube/vitess/go/vt/sqlparser" + "github.com/youtube/vitess/go/vt/tableacl" +) + +// Permission associates the required access permission +// for each table. +type Permission struct { + TableName string + Role tableacl.Role +} + +// BuildPermissions builds the list of required permissions for all the +// tables referenced in a query. +func BuildPermissions(stmt sqlparser.Statement) []Permission { + var permissions []Permission + // All Statement types myst be covered here. + switch node := stmt.(type) { + case *sqlparser.Union, *sqlparser.Select: + permissions = buildSubqueryPermissions(node, tableacl.READER, permissions) + case *sqlparser.Insert: + permissions = buildTableNamePermissions(node.Table, tableacl.WRITER, permissions) + permissions = buildSubqueryPermissions(node, tableacl.READER, permissions) + case *sqlparser.Update: + permissions = buildTableExprsPermissions(node.TableExprs, tableacl.WRITER, permissions) + permissions = buildSubqueryPermissions(node, tableacl.READER, permissions) + case *sqlparser.Delete: + permissions = buildTableExprsPermissions(node.TableExprs, tableacl.WRITER, permissions) + permissions = buildSubqueryPermissions(node, tableacl.READER, permissions) + case *sqlparser.Set, *sqlparser.Show, *sqlparser.OtherRead: + // no-op + case *sqlparser.DDL: + if !node.Table.IsEmpty() { + permissions = buildTableNamePermissions(node.Table, tableacl.ADMIN, permissions) + } + if !node.NewName.IsEmpty() { + permissions = buildTableNamePermissions(node.NewName, tableacl.ADMIN, permissions) + } + case *sqlparser.OtherAdmin: + // no op + default: + panic(fmt.Errorf("BUG: unexpected statement type: %T", node)) + } + return permissions +} + +func buildSubqueryPermissions(stmt sqlparser.Statement, role tableacl.Role, permissions []Permission) []Permission { + _ = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { + switch node := node.(type) { + case *sqlparser.Select: + permissions = buildTableExprsPermissions(node.From, role, permissions) + case sqlparser.TableExprs: + return false, nil + } + return true, nil + }, stmt) + return permissions +} + +func buildTableExprsPermissions(node sqlparser.TableExprs, role tableacl.Role, permissions []Permission) []Permission { + for _, node := range node { + permissions = buildTableExprPermissions(node, role, permissions) + } + return permissions +} + +func buildTableExprPermissions(node sqlparser.TableExpr, role tableacl.Role, permissions []Permission) []Permission { + switch node := node.(type) { + case *sqlparser.AliasedTableExpr: + // An AliasedTableExpr can also be a subquery, but we should skip them here + // because the buildSubQueryPermissions walker will catch them and extract + // the corresponding table names. + switch node := node.Expr.(type) { + case sqlparser.TableName: + permissions = buildTableNamePermissions(node, role, permissions) + case *sqlparser.Subquery: + permissions = buildSubqueryPermissions(node.Select, role, permissions) + } + case *sqlparser.ParenTableExpr: + permissions = buildTableExprsPermissions(node.Exprs, role, permissions) + case *sqlparser.JoinTableExpr: + permissions = buildTableExprPermissions(node.LeftExpr, role, permissions) + permissions = buildTableExprPermissions(node.RightExpr, role, permissions) + } + return permissions +} + +func buildTableNamePermissions(node sqlparser.TableName, role tableacl.Role, permissions []Permission) []Permission { + permissions = append(permissions, Permission{ + TableName: node.Name.String(), + Role: role, + }) + return permissions +} diff --git a/go/vt/vttablet/tabletserver/planbuilder/permission_test.go b/go/vt/vttablet/tabletserver/planbuilder/permission_test.go new file mode 100644 index 00000000000..a34cec96c21 --- /dev/null +++ b/go/vt/vttablet/tabletserver/planbuilder/permission_test.go @@ -0,0 +1,181 @@ +/* +Copyright 2018 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package planbuilder + +import ( + "reflect" + "testing" + + "github.com/youtube/vitess/go/vt/sqlparser" + "github.com/youtube/vitess/go/vt/tableacl" +) + +func TestBuildPermissions(t *testing.T) { + tcases := []struct { + input string + output []Permission + }{{ + input: "select * from t", + output: []Permission{{ + TableName: "t", + Role: tableacl.READER, + }}, + }, { + input: "select * from t1 union select * from t2", + output: []Permission{{ + TableName: "t1", + Role: tableacl.READER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "insert into t values()", + output: []Permission{{ + TableName: "t", + Role: tableacl.WRITER, + }}, + }, { + input: "update t set a=1", + output: []Permission{{ + TableName: "t", + Role: tableacl.WRITER, + }}, + }, { + input: "delete from t", + output: []Permission{{ + TableName: "t", + Role: tableacl.WRITER, + }}, + }, { + input: "set a=1", + output: nil, + }, { + input: "show variable like 'a%'", + output: nil, + }, { + input: "describe t", + output: nil, + }, { + input: "create table t", + output: []Permission{{ + TableName: "t", + Role: tableacl.ADMIN, + }}, + }, { + input: "rename table t1 to t2", + output: []Permission{{ + TableName: "t1", + Role: tableacl.ADMIN, + }, { + TableName: "t2", + Role: tableacl.ADMIN, + }}, + }, { + input: "drop table t", + output: []Permission{{ + TableName: "t", + Role: tableacl.ADMIN, + }}, + }, { + input: "repair t", + output: nil, + }, { + input: "select (select a from t2) from t1", + output: []Permission{{ + TableName: "t1", + Role: tableacl.READER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "insert into t1 values((select a from t2), 1)", + output: []Permission{{ + TableName: "t1", + Role: tableacl.WRITER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "update t1 set a = (select b from t2)", + output: []Permission{{ + TableName: "t1", + Role: tableacl.WRITER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "delete from t1 where a = (select b from t2)", + output: []Permission{{ + TableName: "t1", + Role: tableacl.WRITER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "select * from t1, t2", + output: []Permission{{ + TableName: "t1", + Role: tableacl.READER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "select * from (t1, t2)", + output: []Permission{{ + TableName: "t1", + Role: tableacl.READER, + }, { + TableName: "t2", + Role: tableacl.READER, + }}, + }, { + input: "update t1 join t2 on a=b set c=d", + output: []Permission{{ + TableName: "t1", + Role: tableacl.WRITER, + }, { + TableName: "t2", + Role: tableacl.WRITER, + }}, + }, { + input: "update (select * from t1) as a join t2 on a=b set c=d", + output: []Permission{{ + TableName: "t1", + Role: tableacl.WRITER, + }, { + TableName: "t2", + Role: tableacl.WRITER, + }}, + }} + + for _, tcase := range tcases { + stmt, err := sqlparser.Parse(tcase.input) + if err != nil { + t.Fatal(err) + } + got := BuildPermissions(stmt) + if !reflect.DeepEqual(got, tcase.output) { + t.Errorf("BuildPermissions(%s): %v, want %v", tcase.input, got, tcase.output) + } + } +} diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan.go b/go/vt/vttablet/tabletserver/planbuilder/plan.go index 8f20563c53f..5416b8db2a5 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/plan.go +++ b/go/vt/vttablet/tabletserver/planbuilder/plan.go @@ -21,11 +21,12 @@ import ( "fmt" "github.com/youtube/vitess/go/sqltypes" - vtrpcpb "github.com/youtube/vitess/go/vt/proto/vtrpc" "github.com/youtube/vitess/go/vt/sqlparser" "github.com/youtube/vitess/go/vt/tableacl" "github.com/youtube/vitess/go/vt/vterrors" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/schema" + + vtrpcpb "github.com/youtube/vitess/go/vt/proto/vtrpc" ) var ( @@ -138,8 +139,6 @@ func (pt PlanType) MinRole() tableacl.Role { return tableACLRoles[pt] } -//_______________________________________________ - var tableACLRoles = map[PlanType]tableacl.Role{ PlanPassSelect: tableacl.READER, PlanSelectLock: tableacl.READER, @@ -211,6 +210,9 @@ type Plan struct { // NewName is the new name of the table. Set for DDLs which create or change the table. NewName sqlparser.TableIdent + // Permissions stores the permissions for the tables accessed in the query. + Permissions []Permission + // FieldQuery is used to fetch field info FieldQuery *sqlparser.ParsedQuery @@ -267,34 +269,40 @@ func Build(sql string, tables map[string]*schema.Table) (*Plan, error) { if err != nil { return nil, err } + var plan *Plan switch stmt := statement.(type) { case *sqlparser.Union: - return &Plan{ + plan, err = &Plan{ PlanID: PlanPassSelect, FieldQuery: GenerateFieldQuery(stmt), FullQuery: GenerateLimitQuery(stmt), }, nil case *sqlparser.Select: - return analyzeSelect(stmt, tables) + plan, err = analyzeSelect(stmt, tables) case *sqlparser.Insert: - return analyzeInsert(stmt, tables) + plan, err = analyzeInsert(stmt, tables) case *sqlparser.Update: - return analyzeUpdate(stmt, tables) + plan, err = analyzeUpdate(stmt, tables) case *sqlparser.Delete: - return analyzeDelete(stmt, tables) + plan, err = analyzeDelete(stmt, tables) case *sqlparser.Set: - return analyzeSet(stmt), nil + plan, err = analyzeSet(stmt), nil case *sqlparser.DDL: - return analyzeDDL(stmt, tables), nil + plan, err = analyzeDDL(stmt, tables), nil case *sqlparser.Show: - return &Plan{PlanID: PlanOtherRead}, nil + plan, err = &Plan{PlanID: PlanOtherRead}, nil case *sqlparser.OtherRead: - return &Plan{PlanID: PlanOtherRead}, nil + plan, err = &Plan{PlanID: PlanOtherRead}, nil case *sqlparser.OtherAdmin: - return &Plan{PlanID: PlanOtherAdmin}, nil + plan, err = &Plan{PlanID: PlanOtherAdmin}, nil + default: + return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "invalid SQL") } - - return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "invalid SQL") + if err != nil { + return nil, err + } + plan.Permissions = BuildPermissions(statement) + return plan, nil } // BuildStreaming builds a streaming plan based on the schema. @@ -305,8 +313,9 @@ func BuildStreaming(sql string, tables map[string]*schema.Table) (*Plan, error) } plan := &Plan{ - PlanID: PlanSelectStream, - FullQuery: GenerateFullQuery(statement), + PlanID: PlanSelectStream, + FullQuery: GenerateFullQuery(statement), + Permissions: BuildPermissions(statement), } switch stmt := statement.(type) { @@ -338,5 +347,9 @@ func BuildMessageStreaming(name string, tables map[string]*schema.Table) (*Plan, if plan.Table.Type != schema.Message { return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "'%s' is not a message table", name) } + plan.Permissions = []Permission{{ + TableName: plan.Table.Name.String(), + Role: tableacl.WRITER, + }} return plan, nil } diff --git a/go/vt/vttablet/tabletserver/planbuilder/plan_test.go b/go/vt/vttablet/tabletserver/planbuilder/plan_test.go index 78577f3137d..71bc2e750f9 100644 --- a/go/vt/vttablet/tabletserver/planbuilder/plan_test.go +++ b/go/vt/vttablet/tabletserver/planbuilder/plan_test.go @@ -33,18 +33,18 @@ import ( "github.com/youtube/vitess/go/sqltypes" "github.com/youtube/vitess/go/testfiles" "github.com/youtube/vitess/go/vt/sqlparser" + "github.com/youtube/vitess/go/vt/tableacl" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/schema" ) -// toJSON returns a JSON of the given Plan. -// Except for "TableName", it's a 1:1 copy of the fields of "Plan". -// (The JSON output is used in the tests to compare it against the data in the -// golden files e.g. data/test/tabletserver/exec_cases.txt.) -func toJSON(p *Plan) ([]byte, error) { +// MarshalJSON returns a JSON of the given Plan. +// This is only for testing. +func (p *Plan) MarshalJSON() ([]byte, error) { mplan := struct { PlanID PlanType Reason ReasonType `json:",omitempty"` TableName sqlparser.TableIdent `json:",omitempty"` + Permissions []Permission `json:",omitempty"` FieldQuery *sqlparser.ParsedQuery `json:",omitempty"` FullQuery *sqlparser.ParsedQuery `json:",omitempty"` OuterQuery *sqlparser.ParsedQuery `json:",omitempty"` @@ -59,6 +59,7 @@ func toJSON(p *Plan) ([]byte, error) { PlanID: p.PlanID, Reason: p.Reason, TableName: p.TableName(), + Permissions: p.Permissions, FieldQuery: p.FieldQuery, FullQuery: p.FullQuery, OuterQuery: p.OuterQuery, @@ -86,7 +87,7 @@ func TestPlan(t *testing.T) { if err != nil { out = err.Error() } else { - bout, err := toJSON(plan) + bout, err := json.Marshal(plan) if err != nil { t.Fatalf("Error marshalling %v: %v", plan, err) } @@ -129,7 +130,7 @@ func TestCustom(t *testing.T) { if err != nil { out = err.Error() } else { - bout, err := toJSON(plan) + bout, err := json.Marshal(plan) if err != nil { t.Fatalf("Error marshalling %v: %v", plan, err) } @@ -151,7 +152,7 @@ func TestStreamPlan(t *testing.T) { if err != nil { out = err.Error() } else { - bout, err := toJSON(plan) + bout, err := json.Marshal(plan) if err != nil { t.Fatalf("Error marshalling %v: %v", plan, err) } @@ -184,14 +185,18 @@ func TestMessageStreamingPlan(t *testing.T) { if err != nil { t.Error(err) } - bout, _ := toJSON(plan) + bout, _ := json.Marshal(plan) planJSON := string(bout) wantPlan := &Plan{ PlanID: PlanMessageStream, Table: testSchema["msg"], + Permissions: []Permission{{ + TableName: "msg", + Role: tableacl.WRITER, + }}, } - bout, _ = toJSON(wantPlan) + bout, _ = json.Marshal(wantPlan) wantJSON := string(bout) if planJSON != wantJSON { diff --git a/go/vt/vttablet/tabletserver/query_engine.go b/go/vt/vttablet/tabletserver/query_engine.go index e5c1461e8ac..1e366110d51 100644 --- a/go/vt/vttablet/tabletserver/query_engine.go +++ b/go/vt/vttablet/tabletserver/query_engine.go @@ -56,9 +56,10 @@ import ( // and track stats. type TabletPlan struct { *planbuilder.Plan - Fields []*querypb.Field - Rules *rules.Rules - Authorized *tableacl.ACLResult + Fields []*querypb.Field + Rules *rules.Rules + LegacyAuthorized *tableacl.ACLResult + Authorized []*tableacl.ACLResult mu sync.Mutex QueryCount int64 @@ -96,6 +97,14 @@ func (ep *TabletPlan) Stats() (queryCount int64, duration, mysqlTime time.Durati return } +// buildAuthorized builds 'Authorized', which is the runtime part for 'Permissions'. +func (ep *TabletPlan) buildAuthorized() { + ep.Authorized = make([]*tableacl.ACLResult, len(ep.Permissions)) + for i, perm := range ep.Permissions { + ep.Authorized[i] = tableacl.Authorized(perm.TableName, perm.Role) + } +} + //_______________________________________________ // QueryEngine implements the core functionality of tabletserver. @@ -314,7 +323,8 @@ func (qe *QueryEngine) GetPlan(ctx context.Context, logStats *tabletenv.LogStats } plan := &TabletPlan{Plan: splan} plan.Rules = qe.queryRuleSources.FilterByPlan(sql, plan.PlanID, plan.TableName().String()) - plan.Authorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.LegacyAuthorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.buildAuthorized() if plan.PlanID.IsSelect() { if plan.FieldQuery != nil { conn, err := qe.conns.Get(ctx) @@ -352,7 +362,8 @@ func (qe *QueryEngine) GetStreamPlan(sql string) (*TabletPlan, error) { } plan := &TabletPlan{Plan: splan} plan.Rules = qe.queryRuleSources.FilterByPlan(sql, plan.PlanID, plan.TableName().String()) - plan.Authorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.LegacyAuthorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.buildAuthorized() return plan, nil } @@ -366,7 +377,8 @@ func (qe *QueryEngine) GetMessageStreamPlan(name string) (*TabletPlan, error) { } plan := &TabletPlan{Plan: splan} plan.Rules = qe.queryRuleSources.FilterByPlan("stream from "+name, plan.PlanID, plan.TableName().String()) - plan.Authorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.LegacyAuthorized = tableacl.Authorized(plan.TableName().String(), plan.PlanID.MinRole()) + plan.buildAuthorized() return plan, nil } diff --git a/go/vt/vttablet/tabletserver/query_engine_test.go b/go/vt/vttablet/tabletserver/query_engine_test.go index 7b52edcdbec..bf6513d066f 100644 --- a/go/vt/vttablet/tabletserver/query_engine_test.go +++ b/go/vt/vttablet/tabletserver/query_engine_test.go @@ -30,6 +30,7 @@ import ( "github.com/youtube/vitess/go/mysql/fakesqldb" "github.com/youtube/vitess/go/sqltypes" "github.com/youtube/vitess/go/vt/dbconfigs" + "github.com/youtube/vitess/go/vt/tableacl" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/planbuilder" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/schema" "github.com/youtube/vitess/go/vt/vttablet/tabletserver/schema/schematest" @@ -127,6 +128,10 @@ func TestGetMessageStreamPlan(t *testing.T) { wantPlan := &planbuilder.Plan{ PlanID: planbuilder.PlanMessageStream, Table: qe.tables["msg"], + Permissions: []planbuilder.Permission{{ + TableName: "msg", + Role: tableacl.WRITER, + }}, } if !reflect.DeepEqual(plan.Plan, wantPlan) { t.Errorf("GetMessageStreamPlan(msg): %v, want %v", plan.Plan, wantPlan) diff --git a/go/vt/vttablet/tabletserver/query_executor.go b/go/vt/vttablet/tabletserver/query_executor.go index 042119b5728..68b5234b361 100644 --- a/go/vt/vttablet/tabletserver/query_executor.go +++ b/go/vt/vttablet/tabletserver/query_executor.go @@ -17,6 +17,7 @@ limitations under the License. package tabletserver import ( + "flag" "fmt" "io" "strings" @@ -45,6 +46,9 @@ import ( vtrpcpb "github.com/youtube/vitess/go/vt/proto/vtrpc" ) +// TODO(sougou): remove after affected parties have transitioned to new behavior. +var legacyTableACL = flag.Bool("legacy-table-acl", false, "deprecated: this flag can be used to revert to the older table ACL behavior, which checked access for at most one table") + // QueryExecutor is used for executing a query request. type QueryExecutor struct { query string @@ -351,34 +355,23 @@ func (qre *QueryExecutor) checkPermissions() error { return nil } - // Skip the ACL check if no table name is available in the query or DDL. - if qre.plan.TableName().IsEmpty() && qre.plan.NewName.IsEmpty() { - return nil - } - - // DDL: Check against the new name of the table as well. - if !qre.plan.NewName.IsEmpty() { - altAuthorized := tableacl.Authorized(qre.plan.NewName.String(), qre.plan.PlanID.MinRole()) - err := qre.checkAccess(altAuthorized, qre.plan.NewName, callerID) - if err != nil { - return err + if *legacyTableACL { + if !qre.plan.TableName().IsEmpty() { + return qre.checkAccess(qre.plan.LegacyAuthorized, qre.plan.TableName().String(), callerID) + } + } else { + for i, auth := range qre.plan.Authorized { + if err := qre.checkAccess(auth, qre.plan.Permissions[i].TableName, callerID); err != nil { + return err + } } } - // Actual ACL check: Check if the user is a member of the ACL. - if qre.plan.Authorized == nil { - // Note: This should never happen because tableacl.Authorized() sets this - // field to an "acl.DenyAllACL" ACL if no ACL was found. - return vterrors.Errorf(vtrpcpb.Code_INTERNAL, "table acl error: nil acl") - } - if !qre.plan.TableName().IsEmpty() { - return qre.checkAccess(qre.plan.Authorized, qre.plan.TableName(), callerID) - } return nil } -func (qre *QueryExecutor) checkAccess(authorized *tableacl.ACLResult, tableName sqlparser.TableIdent, callerID *querypb.VTGateCallerID) error { - statsKey := []string{tableName.String(), authorized.GroupName, qre.plan.PlanID.String(), callerID.Username} +func (qre *QueryExecutor) checkAccess(authorized *tableacl.ACLResult, tableName string, callerID *querypb.VTGateCallerID) error { + statsKey := []string{tableName, authorized.GroupName, qre.plan.PlanID.String(), callerID.Username} if !authorized.IsMember(callerID) { if qre.tsv.qe.enableTableACLDryRun { tabletenv.TableaclPseudoDenied.Add(statsKey, 1)