Skip to content

Commit 8b46d44

Browse files
authored
Merge pull request #1095 from constructive-io/feat/rls-alice-bob-integration-test
feat: three-actor RLS + adversarial attack integration tests (Alice/Bob/Mallory)
2 parents 11b5f54 + 8c0d743 commit 8b46d44

4 files changed

Lines changed: 1120 additions & 141 deletions

File tree

graphql/server-test/__fixtures__/seed/simple-seed-services/setup.sql

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,42 @@ COMMENT ON CONSTRAINT tokens_table_fkey ON metaschema_modules_public.rls_module
289289
COMMENT ON CONSTRAINT users_table_fkey ON metaschema_modules_public.rls_module IS E'@omit';
290290
CREATE INDEX rls_module_database_id_idx ON metaschema_modules_public.rls_module ( database_id );
291291

292+
-- database_settings table (typed feature flags — database-wide defaults)
293+
CREATE TABLE IF NOT EXISTS services_public.database_settings (
294+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
295+
database_id uuid NOT NULL UNIQUE,
296+
enable_aggregates boolean NOT NULL DEFAULT false,
297+
enable_postgis boolean NOT NULL DEFAULT true,
298+
enable_search boolean NOT NULL DEFAULT true,
299+
enable_direct_uploads boolean NOT NULL DEFAULT true,
300+
enable_presigned_uploads boolean NOT NULL DEFAULT true,
301+
enable_many_to_many boolean NOT NULL DEFAULT true,
302+
enable_connection_filter boolean NOT NULL DEFAULT true,
303+
enable_ltree boolean NOT NULL DEFAULT true,
304+
enable_llm boolean NOT NULL DEFAULT false,
305+
options jsonb NOT NULL DEFAULT '{}'::jsonb,
306+
CONSTRAINT ds_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE
307+
);
308+
309+
-- api_settings table (per-API overrides — NULL = inherit from database_settings)
310+
CREATE TABLE IF NOT EXISTS services_public.api_settings (
311+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
312+
database_id uuid NOT NULL,
313+
api_id uuid NOT NULL UNIQUE,
314+
enable_aggregates boolean,
315+
enable_postgis boolean,
316+
enable_search boolean,
317+
enable_direct_uploads boolean,
318+
enable_presigned_uploads boolean,
319+
enable_many_to_many boolean,
320+
enable_connection_filter boolean,
321+
enable_ltree boolean,
322+
enable_llm boolean,
323+
options jsonb NOT NULL DEFAULT '{}'::jsonb,
324+
CONSTRAINT as_db_fkey FOREIGN KEY (database_id) REFERENCES metaschema_public.database (id) ON DELETE CASCADE,
325+
CONSTRAINT as_api_fkey FOREIGN KEY (api_id) REFERENCES services_public.apis (id) ON DELETE CASCADE
326+
);
327+
292328
-- Grant permissions on metaschema tables
293329
GRANT SELECT, INSERT, UPDATE, DELETE ON metaschema_public.database TO administrator, authenticated, anonymous;
294330
GRANT SELECT, INSERT, UPDATE, DELETE ON metaschema_public.schema TO administrator, authenticated, anonymous;
@@ -301,4 +337,6 @@ GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.domains TO administrator
301337
GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.api_schemas TO administrator, authenticated, anonymous;
302338
GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.api_extensions TO administrator, authenticated, anonymous;
303339
GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.api_modules TO administrator, authenticated, anonymous;
340+
GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.database_settings TO administrator, authenticated, anonymous;
341+
GRANT SELECT, INSERT, UPDATE, DELETE ON services_public.api_settings TO administrator, authenticated, anonymous;
304342
GRANT SELECT, INSERT, UPDATE, DELETE ON metaschema_modules_public.rls_module TO administrator, authenticated, anonymous;
Lines changed: 147 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,148 @@
11
-- Schema creation for simple-seed-storage test scenario
2-
-- Creates the app schema with storage tables (buckets, files)
3-
4-
-- Create app schemas
5-
CREATE SCHEMA IF NOT EXISTS "simple-storage-public";
6-
7-
-- Grant schema usage
8-
GRANT USAGE ON SCHEMA "simple-storage-public" TO administrator, authenticated, anonymous;
9-
10-
-- Set default privileges
11-
ALTER DEFAULT PRIVILEGES IN SCHEMA "simple-storage-public"
12-
GRANT ALL ON TABLES TO administrator;
13-
ALTER DEFAULT PRIVILEGES IN SCHEMA "simple-storage-public"
14-
GRANT USAGE ON SEQUENCES TO administrator, authenticated;
15-
ALTER DEFAULT PRIVILEGES IN SCHEMA "simple-storage-public"
16-
GRANT ALL ON FUNCTIONS TO administrator, authenticated, anonymous;
17-
18-
-- =====================================================
19-
-- STORAGE TABLES (mirroring what the storage module generator creates)
20-
-- =====================================================
21-
22-
-- Buckets table
23-
CREATE TABLE IF NOT EXISTS "simple-storage-public".app_buckets (
24-
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
25-
key text NOT NULL,
26-
type text NOT NULL DEFAULT 'private',
27-
is_public boolean NOT NULL DEFAULT false,
28-
allowed_mime_types text[] NULL,
29-
max_file_size bigint NULL,
30-
allow_custom_keys boolean NOT NULL DEFAULT false,
31-
created_at timestamptz DEFAULT now(),
32-
updated_at timestamptz DEFAULT now(),
33-
UNIQUE (key)
34-
);
35-
36-
COMMENT ON TABLE "simple-storage-public".app_buckets IS E'@storageBuckets\nStorage buckets table';
37-
38-
-- Files table
39-
CREATE TABLE IF NOT EXISTS "simple-storage-public".app_files (
40-
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
41-
bucket_id uuid NOT NULL REFERENCES "simple-storage-public".app_buckets(id),
42-
key text NOT NULL,
43-
content_hash text NOT NULL,
44-
mime_type text NOT NULL,
45-
size bigint,
46-
filename text,
47-
owner_id uuid,
48-
is_public boolean NOT NULL DEFAULT false,
49-
previous_version_id uuid REFERENCES "simple-storage-public".app_files(id),
50-
created_at timestamptz DEFAULT now(),
51-
updated_at timestamptz DEFAULT now(),
52-
UNIQUE (bucket_id, key)
53-
);
54-
55-
COMMENT ON TABLE "simple-storage-public".app_files IS E'@storageFiles\nStorage files table';
56-
57-
-- Grant table permissions (allow anonymous to do CRUD for tests — no RLS)
58-
GRANT SELECT, INSERT, UPDATE, DELETE ON "simple-storage-public".app_buckets TO administrator, authenticated, anonymous;
59-
GRANT SELECT, INSERT, UPDATE, DELETE ON "simple-storage-public".app_files TO administrator, authenticated, anonymous;
2+
-- Creates storage schemas (buckets + files) for three tenants:
3+
-- Alice (no RLS), Bob (moderate RLS), Mallory (strictest RLS)
4+
5+
-- =====================================================
6+
-- Helper: create a storage schema with buckets + files tables
7+
-- =====================================================
8+
9+
CREATE FUNCTION _test_create_storage_schema(schema_name text) RETURNS void
10+
LANGUAGE plpgsql AS $$
11+
BEGIN
12+
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', schema_name);
13+
14+
EXECUTE format('GRANT USAGE ON SCHEMA %I TO administrator, authenticated, anonymous', schema_name);
15+
16+
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT ALL ON TABLES TO administrator', schema_name);
17+
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT USAGE ON SEQUENCES TO administrator, authenticated', schema_name);
18+
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT ALL ON FUNCTIONS TO administrator, authenticated, anonymous', schema_name);
19+
20+
-- Buckets table
21+
EXECUTE format(
22+
'CREATE TABLE IF NOT EXISTS %I.app_buckets (
23+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
24+
key text NOT NULL,
25+
type text NOT NULL DEFAULT ''private'',
26+
is_public boolean NOT NULL DEFAULT false,
27+
allowed_mime_types text[] NULL,
28+
max_file_size bigint NULL,
29+
allow_custom_keys boolean NOT NULL DEFAULT false,
30+
created_at timestamptz DEFAULT now(),
31+
updated_at timestamptz DEFAULT now(),
32+
UNIQUE (key)
33+
)', schema_name);
34+
35+
EXECUTE format(
36+
'COMMENT ON TABLE %I.app_buckets IS E''@storageBuckets\nStorage buckets table''',
37+
schema_name);
38+
39+
-- Files table
40+
EXECUTE format(
41+
'CREATE TABLE IF NOT EXISTS %I.app_files (
42+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
43+
bucket_id uuid NOT NULL REFERENCES %I.app_buckets(id),
44+
key text NOT NULL,
45+
content_hash text NOT NULL,
46+
mime_type text NOT NULL,
47+
size bigint,
48+
filename text,
49+
owner_id uuid,
50+
is_public boolean NOT NULL DEFAULT false,
51+
previous_version_id uuid REFERENCES %I.app_files(id),
52+
created_at timestamptz DEFAULT now(),
53+
updated_at timestamptz DEFAULT now(),
54+
UNIQUE (bucket_id, key)
55+
)', schema_name, schema_name, schema_name);
56+
57+
EXECUTE format(
58+
'COMMENT ON TABLE %I.app_files IS E''@storageFiles\nStorage files table''',
59+
schema_name);
60+
61+
-- Grant CRUD to all roles
62+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON %I.app_buckets TO administrator, authenticated, anonymous', schema_name);
63+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON %I.app_files TO administrator, authenticated, anonymous', schema_name);
64+
END;
65+
$$;
66+
67+
-- =====================================================
68+
-- ALICE (no RLS — wide open)
69+
-- =====================================================
70+
71+
SELECT _test_create_storage_schema('simple-storage-public');
72+
73+
-- =====================================================
74+
-- BOB (moderate RLS)
75+
-- Buckets: anonymous sees public only
76+
-- Files: anonymous can SELECT public-bucket files + INSERT; no UPDATE/DELETE
77+
-- =====================================================
78+
79+
SELECT _test_create_storage_schema('bob-storage-public');
80+
81+
ALTER TABLE "bob-storage-public".app_buckets ENABLE ROW LEVEL SECURITY;
82+
83+
CREATE POLICY anon_read_public_buckets ON "bob-storage-public".app_buckets
84+
FOR SELECT TO anonymous
85+
USING (is_public = true);
86+
87+
CREATE POLICY admin_all_buckets ON "bob-storage-public".app_buckets
88+
FOR ALL TO administrator
89+
USING (true)
90+
WITH CHECK (true);
91+
92+
ALTER TABLE "bob-storage-public".app_files ENABLE ROW LEVEL SECURITY;
93+
94+
CREATE POLICY anon_read_public_files ON "bob-storage-public".app_files
95+
FOR SELECT TO anonymous
96+
USING (
97+
bucket_id IN (
98+
SELECT id FROM "bob-storage-public".app_buckets WHERE is_public = true
99+
)
100+
);
101+
102+
-- Anonymous can insert into any bucket (for upload testing)
103+
CREATE POLICY anon_insert_files ON "bob-storage-public".app_files
104+
FOR INSERT TO anonymous
105+
WITH CHECK (true);
106+
107+
-- No UPDATE or DELETE policies for anonymous — absence means denied.
108+
-- This prevents Supabase-style attacks where anonymous could:
109+
-- - change a file's bucket_id from private to public
110+
-- - flip is_public flags
111+
-- - delete other users' files
112+
113+
CREATE POLICY admin_all_files ON "bob-storage-public".app_files
114+
FOR ALL TO administrator
115+
USING (true)
116+
WITH CHECK (true);
117+
118+
-- =====================================================
119+
-- MALLORY (strictest RLS — anonymous can only SELECT)
120+
-- =====================================================
121+
122+
SELECT _test_create_storage_schema('mallory-storage-public');
123+
124+
ALTER TABLE "mallory-storage-public".app_buckets ENABLE ROW LEVEL SECURITY;
125+
126+
CREATE POLICY anon_read_buckets ON "mallory-storage-public".app_buckets
127+
FOR SELECT TO anonymous
128+
USING (true);
129+
130+
CREATE POLICY admin_all_buckets ON "mallory-storage-public".app_buckets
131+
FOR ALL TO administrator
132+
USING (true)
133+
WITH CHECK (true);
134+
135+
ALTER TABLE "mallory-storage-public".app_files ENABLE ROW LEVEL SECURITY;
136+
137+
-- Anonymous can only read (no INSERT/UPDATE/DELETE at all)
138+
CREATE POLICY anon_read_files ON "mallory-storage-public".app_files
139+
FOR SELECT TO anonymous
140+
USING (true);
141+
142+
CREATE POLICY admin_all_files ON "mallory-storage-public".app_files
143+
FOR ALL TO administrator
144+
USING (true)
145+
WITH CHECK (true);
146+
147+
-- Clean up helper
148+
DROP FUNCTION _test_create_storage_schema(text);

0 commit comments

Comments
 (0)