Skip to content

Commit 9b699b9

Browse files
committed
logictest: add test cases for RLS interaction with triggers
Release note: None
1 parent 631e617 commit 9b699b9

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed

pkg/sql/logictest/testdata/logic_test/row_level_security

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4485,3 +4485,267 @@ statement ok
44854485
DROP ROLE parent_role
44864486

44874487
subtest end
4488+
4489+
subtest triggers_with_rls
4490+
4491+
statement ok
4492+
CREATE TABLE trigger_rls_table (id INT PRIMARY KEY, value INT, owner STRING);
4493+
4494+
statement ok
4495+
INSERT INTO trigger_rls_table VALUES (1, 100, 'alice'), (2, 200, 'bob'), (3, 300, 'alice'), (4, 400, 'bob');
4496+
4497+
statement ok
4498+
CREATE USER alice;
4499+
4500+
statement ok
4501+
CREATE USER bob;
4502+
4503+
statement ok
4504+
GRANT ALL ON trigger_rls_table TO alice, bob;
4505+
4506+
# Enable RLS on the table
4507+
statement ok
4508+
ALTER TABLE trigger_rls_table ENABLE ROW LEVEL SECURITY;
4509+
4510+
# Create a policy that only allows users to see their own data.
4511+
statement ok
4512+
CREATE POLICY owner_policy ON trigger_rls_table
4513+
USING (owner = current_user())
4514+
WITH CHECK (owner = current_user());
4515+
4516+
# Create a trigger function that logs changes to the table.
4517+
statement ok
4518+
CREATE FUNCTION log_value_changes() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4519+
BEGIN
4520+
IF TG_OP = 'UPDATE' THEN
4521+
RAISE NOTICE 'User % updated value for id % from % to %', current_user(), (NEW).id, (OLD).value, (NEW).value;
4522+
ELSIF TG_OP = 'INSERT' THEN
4523+
RAISE NOTICE 'User % inserted new row with id % and value %', current_user(), (NEW).id, (NEW).value;
4524+
ELSIF TG_OP = 'DELETE' THEN
4525+
RAISE NOTICE 'User % deleted row with id % and value %', current_user(), (OLD).id, (OLD).value;
4526+
END IF;
4527+
RETURN NEW;
4528+
END;
4529+
$$;
4530+
4531+
# Create a trigger function that enforces a business rule: value must be positive.
4532+
statement ok
4533+
CREATE FUNCTION enforce_positive_value() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4534+
BEGIN
4535+
IF (NEW).value <= 0 THEN
4536+
RAISE EXCEPTION 'Value must be positive';
4537+
END IF;
4538+
RETURN NEW;
4539+
END;
4540+
$$;
4541+
4542+
# Create a trigger that logs changes.
4543+
statement ok
4544+
CREATE TRIGGER log_changes
4545+
AFTER INSERT OR UPDATE OR DELETE ON trigger_rls_table
4546+
FOR EACH ROW
4547+
EXECUTE FUNCTION log_value_changes();
4548+
4549+
# Create a trigger that enforces the business rule.
4550+
statement ok
4551+
CREATE TRIGGER check_positive_value
4552+
BEFORE INSERT OR UPDATE ON trigger_rls_table
4553+
FOR EACH ROW
4554+
EXECUTE FUNCTION enforce_positive_value();
4555+
4556+
# Test as alice.
4557+
statement ok
4558+
SET ROLE alice;
4559+
4560+
# Alice should only see her own rows.
4561+
query IIT
4562+
SELECT * FROM trigger_rls_table ORDER BY id;
4563+
----
4564+
1 100 alice
4565+
3 300 alice
4566+
4567+
# Update a row that alice owns.
4568+
query T noticetrace
4569+
UPDATE trigger_rls_table SET value = 150 WHERE id = 1;
4570+
----
4571+
NOTICE: User alice updated value for id 1 from 100 to 150
4572+
4573+
# Try to update a row that bob owns (should not error, but should not affect any rows).
4574+
query T noticetrace
4575+
UPDATE trigger_rls_table SET value = 250 WHERE id = 2;
4576+
----
4577+
4578+
# Test negative value should trigger error.
4579+
statement error pq: Value must be positive
4580+
UPDATE trigger_rls_table SET value = -100 WHERE id = 1;
4581+
4582+
# Insert a new row that alice owns.
4583+
query T noticetrace
4584+
INSERT INTO trigger_rls_table VALUES (5, 500, 'alice');
4585+
----
4586+
NOTICE: User alice inserted new row with id 5 and value 500
4587+
4588+
# Try to insert a row that bob owns (should fail due to RLS policy).
4589+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4590+
INSERT INTO trigger_rls_table VALUES (6, 600, 'bob');
4591+
4592+
# Delete a row that alice owns.
4593+
query T noticetrace
4594+
DELETE FROM trigger_rls_table WHERE id = 3;
4595+
----
4596+
NOTICE: User alice deleted row with id 3 and value 300
4597+
4598+
# Test as bob.
4599+
statement ok
4600+
SET ROLE bob;
4601+
4602+
# Bob should only see his own rows.
4603+
query IIT
4604+
SELECT * FROM trigger_rls_table ORDER BY id;
4605+
----
4606+
2 200 bob
4607+
4 400 bob
4608+
4609+
# Update a row that bob owns.
4610+
query T noticetrace
4611+
UPDATE trigger_rls_table SET value = 250 WHERE id = 2;
4612+
----
4613+
NOTICE: User bob updated value for id 2 from 200 to 250
4614+
4615+
# Try to update a row that alice owns (should not error, but should not affect any rows).
4616+
query T noticetrace
4617+
UPDATE trigger_rls_table SET value = 175 WHERE id = 1;
4618+
----
4619+
4620+
# Insert a new row that bob owns.
4621+
query T noticetrace
4622+
INSERT INTO trigger_rls_table VALUES (7, 700, 'bob');
4623+
----
4624+
NOTICE: User bob inserted new row with id 7 and value 700
4625+
4626+
# Test if bob can bypass RLS with a trigger function that modifies other rows.
4627+
statement ok
4628+
CREATE FUNCTION try_bypass_rls() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4629+
BEGIN
4630+
UPDATE trigger_rls_table SET value = 999 WHERE owner = 'alice';
4631+
RETURN NEW;
4632+
END;
4633+
$$;
4634+
4635+
statement ok
4636+
CREATE TRIGGER bypass_attempt
4637+
AFTER INSERT ON trigger_rls_table
4638+
FOR EACH ROW
4639+
EXECUTE FUNCTION try_bypass_rls();
4640+
4641+
# Try to trigger the bypass (this insert will succeed but the trigger's UPDATE should be limited by RLS).
4642+
statement ok
4643+
INSERT INTO trigger_rls_table VALUES (8, 800, 'bob');
4644+
4645+
# Check as root what happened.
4646+
statement ok
4647+
SET ROLE root;
4648+
4649+
# Alice's rows should not have been affected by bob's trigger.
4650+
query IIT
4651+
SELECT * FROM trigger_rls_table WHERE owner = 'alice' ORDER BY id;
4652+
----
4653+
1 150 alice
4654+
5 500 alice
4655+
4656+
# Now test a BEFORE trigger that modifies a column in a way that would violate an RLS policy.
4657+
statement ok
4658+
SET ROLE alice;
4659+
4660+
# Create a trigger function that tries to change the owner column to 'bob'.
4661+
statement ok
4662+
CREATE FUNCTION change_owner_to_bob() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4663+
BEGIN
4664+
NEW.owner = 'bob';
4665+
RETURN NEW;
4666+
END;
4667+
$$;
4668+
4669+
# Create a trigger that applies the function before insert.
4670+
statement ok
4671+
CREATE TRIGGER force_bob_ownership
4672+
BEFORE INSERT ON trigger_rls_table
4673+
FOR EACH ROW
4674+
EXECUTE FUNCTION change_owner_to_bob();
4675+
4676+
# Try to insert a row - this should fail because the trigger modifies
4677+
# the owner to 'bob' which violates alice's RLS policy.
4678+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4679+
INSERT INTO trigger_rls_table VALUES (9, 900, 'alice');
4680+
4681+
# Now do the same with update.
4682+
statement ok
4683+
CREATE FUNCTION change_updated_owner_to_bob() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4684+
BEGIN
4685+
IF TG_OP = 'UPDATE' THEN
4686+
NEW.owner = 'bob';
4687+
END IF;
4688+
RETURN NEW;
4689+
END;
4690+
$$;
4691+
4692+
statement ok
4693+
CREATE TRIGGER force_update_bob_ownership
4694+
BEFORE UPDATE ON trigger_rls_table
4695+
FOR EACH ROW
4696+
EXECUTE FUNCTION change_updated_owner_to_bob();
4697+
4698+
# Try to update a row - this should fail because the trigger modifies
4699+
# the owner to 'bob' which violates alice's RLS policy.
4700+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4701+
UPDATE trigger_rls_table SET value = 600 WHERE id = 5;
4702+
4703+
# Let's try with both bob and root to see how it works for them.
4704+
statement ok
4705+
SET ROLE bob;
4706+
4707+
# For bob, changing owner to bob is allowed by the policy.
4708+
query T noticetrace
4709+
INSERT INTO trigger_rls_table VALUES (10, 1000, 'alice');
4710+
----
4711+
NOTICE: User bob inserted new row with id 10 and value 1000
4712+
4713+
# Verify the owner was changed to bob by the trigger.
4714+
query IIT
4715+
SELECT * FROM trigger_rls_table WHERE id = 10;
4716+
----
4717+
10 1000 bob
4718+
4719+
# For completeness, let's see from the root perspective.
4720+
statement ok
4721+
SET ROLE root;
4722+
4723+
# Check all rows in the table to see what happened.
4724+
query IIT
4725+
SELECT * FROM trigger_rls_table ORDER BY id;
4726+
----
4727+
1 150 alice
4728+
2 250 bob
4729+
4 400 bob
4730+
5 500 alice
4731+
7 700 bob
4732+
8 800 bob
4733+
10 1000 bob
4734+
4735+
# Clean up the additional triggers and functions.
4736+
statement ok
4737+
DROP TRIGGER force_bob_ownership ON trigger_rls_table;
4738+
DROP TRIGGER force_update_bob_ownership ON trigger_rls_table;
4739+
DROP FUNCTION change_owner_to_bob;
4740+
DROP FUNCTION change_updated_owner_to_bob;
4741+
DROP TRIGGER log_changes ON trigger_rls_table;
4742+
DROP TRIGGER check_positive_value ON trigger_rls_table;
4743+
DROP TRIGGER bypass_attempt ON trigger_rls_table;
4744+
DROP FUNCTION log_value_changes;
4745+
DROP FUNCTION enforce_positive_value;
4746+
DROP FUNCTION try_bypass_rls;
4747+
DROP TABLE trigger_rls_table;
4748+
DROP USER alice;
4749+
DROP USER bob;
4750+
4751+
subtest end

0 commit comments

Comments
 (0)