Skip to content

fix(rls): return a descriptive error for duplicate rule names - #42819

Open
sadpandajoe wants to merge 4 commits into
masterfrom
fix-rls-duplicate-name-error
Open

fix(rls): return a descriptive error for duplicate rule names#42819
sadpandajoe wants to merge 4 commits into
masterfrom
fix-rls-duplicate-name-error

Conversation

@sadpandajoe

@sadpandajoe sadpandajoe commented Aug 6, 2026

Copy link
Copy Markdown
Member

SUMMARY

Creating (or renaming) a Row Level Security rule with a name that already exists was rejected only at DB flush time, by the unique constraint on RowLevelSecurityFilter.name. That surfaced as a SQLAlchemy IntegrityError whose string form is empty, so the API returned 422 {"message": "()"} and the UI rendered a toast with no reason in it:

An error occurred while creating rowlevelsecuritys: ()

The user is told the save failed but not why, even though the cause is a simple, actionable name collision.

This adds an explicit name-uniqueness check to CreateRLSRuleCommand.validate() and UpdateRLSRuleCommand.validate() that raises a translatable ValidationError before the write, and surfaces it from post()/put() as a 422 carrying the field message. The update path excludes the rule being edited, so saving a rule without renaming it is not treated as a collision.

New RLSDAO.validate_uniqueness(name, rule_id=None) mirrors the existing pattern in DatasetDAO/SemanticLayerDAO.

After the change the response body is {"message": {"name": ["A rule with this name already exists."]}} and the toast states the reason.

Note the ValidationError arm is deliberately ordered after the existing SubjectsNotFoundValidationError / DatasourceNotFoundValidationError arms, since those are ValidationError subclasses and must keep their own responses. RLSRuleNotFoundError is a CommandException, so the put() 404 path is unaffected.

BEFORE/AFTER

Before:

before-primary-1440x900-default.mov

After:

after-primary-1440x900-default.mov

TESTING INSTRUCTIONS

  1. Go to Settings → Row Level Security.
  2. Create a rule named my-rule (Regular, any dataset, any role, any clause). It saves.
  3. Click + Rule again and create another rule with the same name my-rule.
  4. Save. The toast now names the reason instead of showing empty parentheses.
  5. Edit an existing rule and save it without changing the name — it still saves, and is not rejected as a duplicate.

Automated coverage added:

  • tests/unit_tests/commands/security/rls_test.py — create/update reject a duplicate name, allow a unique name, and allow an unchanged name (asserting the update check excludes the rule's own id).
  • tests/integration_tests/security/row_level_security_tests.py — asserts the actual HTTP body for the create and rename cases, and that an unchanged-name save still returns 200.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Out of scope, pre-existing and unchanged by this PR: the frontend createErrorHandler formatting still pluralizes the resource label as rowlevelsecuritys and prefixes the field name, and the update path renders the serialized field object rather than a flattened sentence. Those live in superset-frontend/src/views/CRUD/hooks.ts and affect every CRUD resource, so they are left for a separate change.

Creating or updating a Row Level Security rule with a name that already
exists was only caught at DB flush as a SQLAlchemy IntegrityError, which
surfaced to the client as an empty/opaque message (e.g. the toast "An
error occurred while creating rowlevelsecuritys: ()").

Add an explicit name-uniqueness check to CreateRLSRuleCommand and
UpdateRLSRuleCommand (excluding the rule being updated so an unchanged
save is not rejected) that raises a translatable ValidationError before
the DB write, and surface that ValidationError from the REST API as a
422 carrying the field message so the reason reaches the user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the api Related to the REST API label Aug 6, 2026
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 3480d95
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a760084b95650000921236d
😎 Deploy Preview https://deploy-preview-42819--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.78%. Comparing base (fe06ebe) to head (3480d95).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42819      +/-   ##
==========================================
- Coverage   66.37%   65.78%   -0.59%     
==========================================
  Files        2857     2842      -15     
  Lines      161048   160694     -354     
  Branches    37046    36788     -258     
==========================================
- Hits       106892   105711    -1181     
- Misses      52141    52969     +828     
+ Partials     2015     2014       -1     
Flag Coverage Δ
hive 38.26% <30.00%> (-0.01%) ⬇️
mysql 57.76% <100.00%> (+0.01%) ⬆️
postgres 57.80% <100.00%> (+<0.01%) ⬆️
presto 40.22% <30.00%> (-0.01%) ⬇️
python 59.20% <100.00%> (+<0.01%) ⬆️
sqlite 57.43% <100.00%> (+0.01%) ⬆️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sadpandajoe
sadpandajoe marked this pull request as ready for review August 6, 2026 00:41
@dosubot dosubot Bot added the authentication:row-level-security Related to Row Level Security label Aug 6, 2026
@sadpandajoe
sadpandajoe requested review from rusackas and a lite review from Copilot August 6, 2026 00:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves the Row Level Security (RLS) create/update API behavior by proactively validating rule-name uniqueness and returning a field-specific, translatable validation response (instead of a late DB IntegrityError with an empty message).

Changes:

  • Add RLSDAO.validate_uniqueness(name, rule_id=None) and call it from CreateRLSRuleCommand.validate() / UpdateRLSRuleCommand.validate().
  • Surface marshmallow.ValidationError.messages from the RLS REST post()/put() handlers as a 422 field error payload.
  • Add unit + integration coverage for duplicate-name create/rename and unchanged-name update.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
superset/daos/security.py Adds DAO-level name uniqueness helper for RLS rules (with optional self-exclusion on update).
superset/commands/security/create.py Adds pre-write duplicate-name validation with translated field error.
superset/commands/security/update.py Adds update-time duplicate-name validation excluding the current rule id.
superset/row_level_security/api.py Returns 422 with structured field errors for command-raised ValidationError.
tests/unit_tests/commands/security/rls_test.py Adds/adjusts unit tests to cover duplicate-name validation paths.
tests/integration_tests/security/row_level_security_tests.py Adds integration assertions for descriptive duplicate-name responses and unchanged-name update success.

@bito-code-review

bito-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #ab6722

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/daos/security.py - 1
    • Missing unit tests for DAO method · Line 18-41
      This new method `RLSDAO.validate_uniqueness` lacks dedicated unit tests. The command layer tests mock this method but don't test its actual behavior. Per BITO.md adaptive rule [11730], new code must have comprehensive unit tests covering success paths, error scenarios, and edge cases. Add tests in `tests/unit_tests/dao/` or `tests/unit_tests/commands/security/` that directly test the DAO method with various name/rule_id combinations.
Review Details
  • Files reviewed - 6 · Commit Range: 6986be6..6986be6
    • superset/commands/security/create.py
    • superset/commands/security/update.py
    • superset/daos/security.py
    • superset/row_level_security/api.py
    • tests/integration_tests/security/row_level_security_tests.py
    • tests/unit_tests/commands/security/rls_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines +50 to +54
name = self._properties.get("name")
if name and not RLSDAO.validate_uniqueness(name):
raise ValidationError(
{"name": [_("A rule with this name already exists.")]}
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The duplicate-name check runs before datasource existence and access validation, so a caller who can invoke RLS creation but lacks access to the submitted datasource receives a duplicate-name response for an existing name and a forbidden response for a nonexistent name. This exposes global rule-name existence and changes the expected authorization response; perform datasource authorization before revealing whether the name is already used. The update command has the same ordering issue. [security]

Severity Level: Minor 🧹
- ⚠️ RLS users can enumerate globally used rule names.
- ⚠️ Unauthorized datasource requests reveal duplicate-name status.
- ⚠️ Update requests expose the same name-existence distinction.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/commands/security/create.py
**Line:** 50:54
**Comment:**
	*Security: The duplicate-name check runs before datasource existence and access validation, so a caller who can invoke RLS creation but lacks access to the submitted datasource receives a duplicate-name response for an existing name and a forbidden response for a nonexistent name. This exposes global rule-name existence and changes the expected authorization response; perform datasource authorization before revealing whether the name is already used. The update command has the same ordering issue.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread superset/daos/security.py
Comment on lines +36 to +41
query = db.session.query(RowLevelSecurityFilter).filter(
RowLevelSecurityFilter.name == name
)
if rule_id is not None:
query = query.filter(RowLevelSecurityFilter.id != rule_id)
return not db.session.query(query.exists()).scalar()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The existence query is only a preflight check and is not atomic with the subsequent insert or update. Two concurrent requests can both observe the name as available, after which one write still raises the database unique-constraint error and the API returns the generic opaque SQLAlchemy message instead of the promised descriptive field error. Catch the unique-constraint violation and translate it to the same validation response, while retaining the database constraint as the authoritative guard. [race condition]

Severity Level: Minor 🧹
- ⚠️ Concurrent RLS creation can return opaque `422` errors.
- ⚠️ Concurrent renames can lose the descriptive validation message.
- ❌ No duplicate database rows are created due to the constraint.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/daos/security.py
**Line:** 36:41
**Comment:**
	*Race Condition: The existence query is only a preflight check and is not atomic with the subsequent insert or update. Two concurrent requests can both observe the name as available, after which one write still raises the database unique-constraint error and the API returns the generic opaque SQLAlchemy message instead of the promised descriptive field error. Catch the unique-constraint violation and translate it to the same validation response, while retaining the database constraint as the authoritative guard.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API authentication:row-level-security Related to Row Level Security size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants