Skip to content

fix: Deleting a role does not invalidate the cached role closures - #10620

Open
AdrianCurtin wants to merge 4 commits into
parse-community:alphafrom
AdrianCurtin:fix_role_cache_on_delete
Open

fix: Deleting a role does not invalidate the cached role closures#10620
AdrianCurtin wants to merge 4 commits into
parse-community:alphafrom
AdrianCurtin:fix_role_cache_on_delete

Conversation

@AdrianCurtin

@AdrianCurtin AdrianCurtin commented Aug 1, 2026

Copy link
Copy Markdown

Pull Request

Issue

Closes #10619.

Deleting a _Role did not invalidate the role cache, so every user who held that role kept it in their cached closure until the entry expired. ACLs granting access to role:<name> continued to be honored for a role that no longer existed.

cacheController.role.clear() was called from only two places, RestWrite.js:1566 (create and update) and PurgeRouter.js:22. Deletes do not go through RestWrite; they go through rest.js del(), which had no role cache handling at all.

The asymmetry is what makes it a bug rather than a tradeoff: removing a user from a role is a _Role update and clears the cache correctly, while deleting the whole role revokes the same access from every member and cleared nothing.

Approach

Clear the role cache in rest.js del() when className === '_Role', mirroring RestWrite#runDatabaseOperation, including the accompanying liveQueryController.clearCachedRoles call that the delete path was also missing.

Two details worth reviewer attention:

  • Placed after the delete commits, in the .then() following database.destroy, rather than before the write as RestWrite does. Clearing before the write leaves a window in which a concurrent read repopulates the closure from pre-delete state, which would outlive the invalidation.
  • Deliberately outside the hasTriggers || hasLiveQuery || className == '_Session' branch, so it runs for a _Role with no triggers registered. That branch is why the existing cacheAdapter.user.del at rest.js:199 does not run for most classes.
  • Not awaited, matching the existing call site at RestWrite.js:1566. Awaiting would make a Redis blip fail the delete, since RedisCacheAdapter#clear has no internal error handling, and that seemed the worse trade. Happy to change it if maintainers prefer.

The whole role cache is cleared rather than one user's entry, for the same reason RestWrite does it: the cached value is a flattened transitive closure, so deleting a parent role affects the members of every child role, and the delete does not identify which users are affected.

This is independent of #10618 and applies with or without it. With that change merged, role.clear() becomes a scoped SCAN over <appId>:role:* instead of a FLUSHDB, which makes adding this call inexpensive.

Tests

spec/ParseRole.spec.js gains two specs:

  • A deleted role clears the cached closure. This fails on alpha with Expected [ 'role:Doomed' ] to equal null and passes here.
  • Deleting a non-role object leaves the role cache untouched, so the new branch does not over-clear.

The full spec/ParseRole.spec.js (20 specs) and spec/Auth.spec.js (11 specs), the latter being the main consumer of the role cache, both pass against MongoDB 8.

Tasks

  • Add tests
  • Add changes to documentation (code comments)

Summary by CodeRabbit

  • Bug Fixes
    • Deleting a role now immediately clears its cached data.
    • Cached role information for the acting user is invalidated during role deletion.
    • LiveQuery results now reflect removed roles more reliably after deletion.
    • Deleting non-role objects no longer affects role cache entries.
    • Role deletion continues successfully even if cache cleanup encounters an error, ensuring the deletion is completed without interruption.

Copilot AI review requested due to automatic review settings August 1, 2026 05:58
@parse-github-assistant

Copy link
Copy Markdown

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant Bot changed the title fix: deleting a role does not invalidate the cached role closures fix: Deleting a role does not invalidate the cached role closures Aug 1, 2026
@parse-github-assistant

Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 465994d8-9ad4-441e-9970-ded01f2f7fb4

📥 Commits

Reviewing files that changed from the base of the PR and between 95e737c and 79ef5c6.

📒 Files selected for processing (1)
  • src/rest.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/rest.js

📝 Walkthrough

Walkthrough

The REST delete flow now clears the global role cache and cached LiveQuery roles when deleting _Role objects. Tests cover asynchronous cache removal for roles and cache preservation for non-role objects.

Changes

Role deletion cache invalidation

Layer / File(s) Summary
Delete-path cache invalidation
src/rest.js, spec/ParseRole.spec.js
The delete flow clears role and LiveQuery role caches for _Role deletions. It logs cache-clear failures without failing the deletion. Tests verify role-cache removal and preservation during non-role deletion.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: ⚪ Minimal · up to 79ef5

This localized change invalidates cached role closures when roles are deleted, with focused tests covering the new behavior; no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Security Check ⚠️ Warning New _Role deletes call role.clear(), which delegates to RedisCacheAdapter.clear() and raw FLUSHDB; an authorized non-master delete can evict all apps' sessions and unrelated Redis data. Use scoped role-cache invalidation, such as the #10618 SCAN/UNLINK implementation, before invoking it on deletes. Do not call the unscoped clear that issues FLUSHDB.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the required fix: prefix, starts the summary with a capital letter, and accurately describes the role-cache invalidation change.
Description check ✅ Passed The description includes the required issue, approach, and tasks sections and documents the implementation and tests.
Linked Issues check ✅ Passed The changes satisfy #10619 by clearing role and LiveQuery caches after deletion, covering all roles, and adding regression tests.
Out of Scope Changes check ✅ Passed The changes are limited to role-cache invalidation in the delete flow and related regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Engage In Review Feedback ✅ Passed PR #10620 shows author replies to all actionable threads: commits 89b1b6a and b43e6bb implement fixes, and the broader LiveQuery concern was discussed and resolved as #10628.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a security-relevant correctness gap in Parse Server’s role caching: deleting a _Role now invalidates cached role closures (and clears LiveQuery’s cached roles), matching the existing behavior on role create/update so access via role:<name> ACLs can’t linger until cache TTL expiry.

Changes:

  • Clear cacheController.role (and liveQueryController.clearCachedRoles) after _Role deletion in rest.js.
  • Add regression coverage to ensure role deletes clear the role cache while non-role deletes do not.

Reviewed changes

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

File Description
src/rest.js Clears role-related caches when deleting a _Role, aligning delete behavior with role writes.
spec/ParseRole.spec.js Adds specs validating role-cache invalidation on role deletion and non-invalidation on other deletes.
Suppressed comments (1)

spec/ParseRole.spec.js:704

  • Like the previous spec, this uses a fixed sleep which can be flaky and slows the suite. Since the behavior you care about is that role cache clearing is not triggered on non-role deletes, you can assert that deterministically by spying on cacheController.role.clear() and verifying it was not called (no timeout needed).
    await object.destroy({ useMasterKey: true });
    await new Promise(resolve => setTimeout(resolve, 200));

    expect(await cacheController.role.get('someUser')).toEqual(['role:Admin']);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rest.js
Comment thread spec/ParseRole.spec.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
spec/ParseRole.spec.js (1)

679-705: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add coverage for LiveQuery role-cache invalidation.

These tests inspect only Parse.Server.cacheController.role. They do not exercise config.liveQueryController.clearCachedRoles(auth.user), which is part of this change. Add a LiveQuery-enabled case or a focused controller spy. Include the master-key deletion path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/ParseRole.spec.js` around lines 679 - 705, Extend the role-deletion
cache tests around the existing role destroy case to cover LiveQuery
invalidation through config.liveQueryController.clearCachedRoles(auth.user), not
only cacheController.role. Enable LiveQuery or spy on the controller, verify
clearCachedRoles is invoked for the deleted role’s user, and retain the
useMasterKey deletion path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@spec/ParseRole.spec.js`:
- Around line 689-690: Replace the fixed 200 ms sleeps in the role-deletion test
around the clear operation with a bounded polling or eventual assertion that
waits until the deleted role is no longer returned from the cache. Assert the
non-role cache immediately after deletion, preserving deterministic timeout
behavior without relying on elapsed time.

In `@src/rest.js`:
- Around line 244-255: Update the _Role deletion handling in RestRequest and the
corresponding RestWrite role-write path so LiveQuery invalidation does not
depend on auth.user and reaches every session affected by the deleted role,
including users inheriting it through child roles. Add or reuse a
role-id/role-orphan invalidation mechanism that propagates the deleted role’s
impact and clears all matching cached role sessions, while preserving the
existing global role cache clear.

---

Nitpick comments:
In `@spec/ParseRole.spec.js`:
- Around line 679-705: Extend the role-deletion cache tests around the existing
role destroy case to cover LiveQuery invalidation through
config.liveQueryController.clearCachedRoles(auth.user), not only
cacheController.role. Enable LiveQuery or spy on the controller, verify
clearCachedRoles is invoked for the deleted role’s user, and retain the
useMasterKey deletion path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4301386e-5393-485c-8189-877595d4d466

📥 Commits

Reviewing files that changed from the base of the PR and between 315e157 and 619e7cb.

📒 Files selected for processing (2)
  • spec/ParseRole.spec.js
  • src/rest.js

Comment thread spec/ParseRole.spec.js Outdated
Comment thread src/rest.js
Refactors ParseRole cache invalidation specs to use the app config controllers instead of global server state, and adds explicit spies for both role cache and LiveQuery role cache clearing. The role-deletion test now waits on the actual clear promise rather than a fixed timeout, making it deterministic, while the non-role deletion test verifies no cache clear methods are called.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deleting a _Role does not clear the role cache, so revoked roles stay active for the cache TTL

2 participants