Restore scheduled deletions if the user loses group/project access
What does this MR do and why?
This change addresses an issue where users can schedule a group or project for deletion and subsequently lose access to it. This can occur in various scenarios, such as user deletion, membership downgrade, blocking, or banning.
Currently, when Sidekiq cron workers attempt to load groups/projects
scheduled for deletion, the Sidekiq jobs fail indefinitely. This
happens because the deleting user no longer has access to the
group/project being deleted, as the DestroyService
s include
permission checks.
I considered several solutions:
-
Skip the authorization check when calling the destroy services:
- Pro: Simple implementation
- Con: Highly destructive; risky if a bad actor schedules a group/project for deletion
-
Delete the project/group deletion schedule when the user loses access:
- Pro: Removes the deletion schedule immediately upon user removal
-
Con: Complex implementation arises due to numerous ways users can
lose access, including deletion, downgrade, blocking, and
banning. Handling all cases, especially membership downgrades
through direct, inherited, or shared membership removal, is
challenging. This complexity makes processing in the
DELETE
request to the members' removal API inefficient. TheMembers::DestroyService
still needs optimization due to timeout issues. While a separate worker could be used, it's not the most efficient solution and may cause race conditions when users are quickly removed and re-added, potentially leaving group/project schedules intact. Moreover, this approach doesn't address existing projects and groups that are failing deletion.
-
Check permissions in the cron worker deleting the scheduled projects/groups:
- Pro: Covers all cases without needing a separate worker
- Con: Deletion may succeed if a user regains access just before the scheduled time
We chose option 3 as the best solution. Although there's a minor risk of deletion succeeding if a user regains access just before the scheduled time, this aligns with our documentation stating that the user should have access at the scheduled time for deletion to succeed.
Currently, we partially use option 2 for group schedule deletion upon membership removal. However, it only deletes the group deletion schedule if the user has direct membership to the group. For inherited or shared membership, the group deletion schedule remains intact. This new approach addresses these limitations and provides a more comprehensive solution.
This implementation checks user permissions during the cron job execution and restores the resource if the user doesn't have access, ensuring that scheduled deletions are handled appropriately.
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
How to set up and validate locally
Prerequisites: Ensure you have a Premium license or above
-
Create a test group
- Use
user1
to create a new group - Add
user2
to the group with owner access
- Use
-
Schedule group for deletion
- Login as
user2
- Schedule the group for deletion
- Login as
-
Update deletion schedule
- Run the following in rails console:
group = Group.last group.deletion_schedule.update(marked_for_deletion_on: 8.days.ago)
- Run the following in rails console:
-
Remove the user
- Remove
user2
from the test group
- Remove
-
Trigger restoration process
- Go to https://gdk.test:3000/admin/sidekiq/cron
- Enqueue the
adjourned_group_deletion_worker
worker
-
Verify restoration
- Check that the group has been restored instead of deleted
-
Repeat the above process for project
- Run the following in rails console:
project = Project.last project.update(marked_for_deletion_at: 8.days.ago)
- Run the following in rails console:
-
Trigger restoration process
- Go to https://gdk.test:3000/admin/sidekiq/cron
- Enqueue the
adjourned_project_deletion_worker
worker.