RLS works

This commit is contained in:
Omar Muñoz
2026-01-21 09:43:52 -06:00
parent 095799f950
commit 289de1c7e6
3 changed files with 68 additions and 38 deletions

View File

@@ -69,17 +69,20 @@ public class RLSTestController {
"SELECT current_setting('app.current_user_id', true)",
String.class
);
result.put("user1Context", ctx);
result.put("user1Context", ctx.isEmpty() ? "EMPTY" : ctx);
return null;
});
// Check if context leaked (should be null or empty)
String leakedContext = null;
String leakedContext;
try {
leakedContext = jdbcTemplate.queryForObject(
"SELECT current_setting('app.current_user_id', true)",
String.class
);
if (leakedContext == null || leakedContext.isEmpty()) {
leakedContext = "NOT_SET";
}
} catch (Exception e) {
// Expected - variable should not be set
leakedContext = "NOT_SET";
@@ -92,7 +95,7 @@ public class RLSTestController {
"SELECT current_setting('app.current_user_id', true)",
String.class
);
result.put("user2Context", ctx);
result.put("user2Context", ctx.isEmpty() ? "EMPTY" : ctx);
return null;
});
@@ -102,6 +105,9 @@ public class RLSTestController {
"SELECT current_setting('app.current_user_id', true)",
String.class
);
if (leakedContext == null || leakedContext.isEmpty()) {
leakedContext = "NOT_SET";
}
} catch (Exception e) {
leakedContext = "NOT_SET";
}
@@ -157,27 +163,43 @@ public class RLSTestController {
// Enable RLS
jdbcTemplate.execute("ALTER TABLE documents ENABLE ROW LEVEL SECURITY");
// CRITICAL: Force RLS even for table owner (postgres superuser)
// Without this, RLS policies are bypassed for the table owner
jdbcTemplate.execute("ALTER TABLE documents FORCE ROW LEVEL SECURITY");
// Create RLS policy
// USING clause: determines which rows are visible (for SELECT)
// WITH CHECK clause: determines which rows can be inserted/updated
// Using NULLIF to handle empty strings from current_setting when variable isn't set
jdbcTemplate.execute("""
CREATE POLICY user_documents_policy ON documents
FOR ALL
USING (user_id = current_setting('app.current_user_id', true)::bigint)
USING (user_id = NULLIF(current_setting('app.current_user_id', true), '')::bigint)
WITH CHECK (user_id = NULLIF(current_setting('app.current_user_id', true), '')::bigint)
""");
// Insert test data
jdbcTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"User 1 Document", "Private content for user 1", 1L
);
jdbcTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"User 2 Document", "Private content for user 2", 2L
);
jdbcTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"Another User 1 Doc", "More private content for user 1", 1L
);
// Insert test data WITH RLS context set
// Now that FORCE RLS is enabled, even our inserts must respect the policy
rlsManager.executeWithRLSContext(1L, scopedTemplate -> {
scopedTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"User 1 Document", "Private content for user 1", 1L
);
scopedTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"Another User 1 Doc", "More private content for user 1", 1L
);
return null;
});
return "Database setup complete with RLS enabled";
rlsManager.executeWithRLSContext(2L, scopedTemplate -> {
scopedTemplate.update(
"INSERT INTO documents (title, content, user_id) VALUES (?, ?, ?)",
"User 2 Document", "Private content for user 2", 2L
);
return null;
});
return "Database setup complete with RLS enabled and FORCED for table owner";
}
}