Skip to content

fix(perms): restrict job deletion to superusers#1284

Merged
mihow merged 3 commits into
mainfrom
fix/restrict-job-delete-to-superusers
Apr 30, 2026
Merged

fix(perms): restrict job deletion to superusers#1284
mihow merged 3 commits into
mainfrom
fix/restrict-job-delete-to-superusers

Conversation

@mihow

@mihow mihow commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

The MLDataManager role granted delete_job from the initial roles framework (#693), and ProjectManager inherited it via the permission-set union. Both roles could DELETE /api/v2/jobs/{id}/ and remove Job rows.

Job records are kept in the DB for audit / traceability — only superusers should delete them. Roles that need to stop in-flight work already have the run_*_job permission, which the cancel endpoint uses to revoke and clean up without dropping the row.

Changes

  • ami/users/roles.py — drop DELETE_JOB from MLDataManager.permissions. ProjectManager loses it via inherited union, no separate edit.
  • ami/main/migrations/0084_revoke_delete_job_from_roles.py — data migration that strips delete_job from existing per-project MLDataManager and ProjectManager guardian groups, so deployed environments lose the perm immediately rather than waiting for the next post_migrate to re-run create_roles_for_project.
  • ami/jobs/tests/test_jobs.py — new TestJobDeletePermission covering:
    • role permission sets no longer contain delete_job
    • MLDataManager and ProjectManager users get 403 on DELETE
    • superuser gets 204 and the row is gone

The delete_job permission codename remains a valid permission on Project.Meta.permissions — it's just not granted to any role. Superusers bypass via is_superuser.

Out of scope (followup)

Currently a superuser DELETE drops the Job row but does not revoke the Celery task or clean up NATS/Redis async state. That belongs to issue #1283 (cleanup-on-revoke / cleanup-before-restart). Posted an implementation proposal at #1283#issuecomment-4356728574 with a generic Job.terminate() helper and the call sites that should funnel through it.

Test plan

  • python manage.py test ami.jobs.tests.test_jobs.TestJobDeletePermission --keepdb — 5 tests pass locally
  • python manage.py makemigrations --check --dry-run — no missing migrations
  • python manage.py migrate main — 0084 applies cleanly on dev DB
  • CI green
  • Manual: log in as MLDataManager / ProjectManager and confirm DELETE on a job returns 403 in the UI; superuser DELETE works

Summary by CodeRabbit

  • Bug Fixes

    • Job deletion is now restricted to superusers only. Users with data manager and project manager roles can no longer delete jobs.
  • Tests

    • Added test coverage for job deletion permission enforcement.

The MLDataManager role granted delete_job, which ProjectManager
inherited via permission union. Both roles could delete Job records
through the API, but Job rows are kept in the DB for audit and
traceability — only superusers should remove them.

- Drop DELETE_JOB from MLDataManager.permissions (ProjectManager
  loses it via inherited union)
- Add a data migration to scrub delete_job from existing
  per-project MLDataManager and ProjectManager guardian groups so
  the change takes effect on already-deployed environments
- Add API tests covering MLDataManager / ProjectManager (403) and
  superuser (204)

Roles that can run ML jobs retain the run_*_job permission, which
the cancel endpoint already uses to terminate in-flight work
without deleting the row. A follow-up to issue #1283 will
generalize the cancel/cleanup helper and wire it into the destroy
path so superuser deletions also tear down NATS/Redis state.

Co-Authored-By: Claude <noreply@anthropic.com>
@netlify

netlify Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-ssec canceled.

Name Link
🔨 Latest commit 3bb34bf
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/69f3e723ade1850008e345df

@netlify

netlify Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-preview canceled.

Name Link
🔨 Latest commit 3bb34bf
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/69f3e7231886710008a143e0

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@mihow has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 23 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a96dd95c-580a-4534-b5c4-9317c4f58699

📥 Commits

Reviewing files that changed from the base of the PR and between 990db07 and 3bb34bf.

📒 Files selected for processing (1)
  • ami/main/tests.py
📝 Walkthrough

Walkthrough

This change revokes the delete_job permission from MLDataManager and ProjectManager roles through a Django migration, updates the role definition to reflect this restriction, and adds a comprehensive test suite validating that only superusers can delete jobs while scoped roles receive 403 Forbidden responses.

Changes

Cohort / File(s) Summary
Permission and Role Configuration
ami/users/roles.py
Removed DELETE_JOB permission from MLDataManager role and added documentation clarifying that job deletion is restricted to superusers.
Database Migration
ami/main/migrations/0084_revoke_delete_job_from_roles.py
Django migration that revokes delete_job permission from per-project role groups for MLDataManager and ProjectManager, including cleanup of related guardian GroupObjectPermission entries.
Test Suite
ami/jobs/tests/test_jobs.py
Added end-to-end tests exercising job deletion via the api:job-detail endpoint, verifying that scoped roles receive 403 Forbidden, superusers receive 204 No Content with successful deletion, and that user_permissions array correctly reflects permission availability.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A humble rabbit hops with glee,
Deletes are now for superusers, you see!
Managers manage, but delete they shan't,
Permissions refined with security's chant. 🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(perms): restrict job deletion to superusers' clearly and concisely summarizes the main change—removing delete_job permission from non-superuser roles.
Description check ✅ Passed The PR description is comprehensive and covers all required template sections: Summary, detailed Changes, Testing, and Deployment Notes. It explains the rationale, lists specific file changes, and documents test results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/restrict-job-delete-to-superusers

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
Review rate limit: 0/1 reviews remaining, refill in 38 minutes and 23 seconds.

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

The frontend's ``canDelete`` getter (ui/src/data-services/models/job.ts)
checks for ``"delete"`` in the serialized ``user_permissions`` array,
and the jobs list only renders the hover trash icon when ``canDelete``
is true. Add detail-endpoint tests asserting the exact contents of that
array for each role:

- MLDataManager: no ``"delete"`` → no trash icon
- ProjectManager: no ``"delete"`` → no trash icon
- Superuser: ``"delete"`` present → trash icon renders

This covers the surface the user actually interacts with, not just the
DELETE endpoint return code.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow mihow marked this pull request as ready for review April 30, 2026 23:13
Copilot AI review requested due to automatic review settings April 30, 2026 23:13
@mihow

mihow commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator Author

Confirmed that a project manager or owner does not see the Trash icon for jobs in the UI and cannot delete Job records, only cancel them.

image

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

Restricts destructive job deletion in the API by removing the delete_job permission from all project-scoped roles and revoking it from existing role groups in deployed environments, while keeping deletion available to superusers for audit/traceability needs.

Changes:

  • Removed delete_job from the MLDataManager role (and therefore from ProjectManager via inherited union).
  • Added a data migration to revoke delete_job from existing per-project Guardian role groups (MLDataManager, ProjectManager).
  • Added API tests ensuring role users receive 403 on DELETE, while superusers can delete (204) and the row is removed; also verifies user_permissions does/doesn’t include "delete" accordingly.

Reviewed changes

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

File Description
ami/users/roles.py Stops granting delete_job via role permission sets.
ami/main/migrations/0084_revoke_delete_job_from_roles.py Revokes delete_job from existing role groups immediately on deploy.
ami/jobs/tests/test_jobs.py Adds coverage for DELETE authorization + user_permissions serialization behavior.

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

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
ami/main/migrations/0084_revoke_delete_job_from_roles.py (1)

22-67: ⚡ Quick win

Add a regression test for the migration path.

This protects the durable fix for deployed environments. The current API tests cover fresh role creation, but not a legacy group already holding delete_job before the migration runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ami/main/migrations/0084_revoke_delete_job_from_roles.py` around lines 22 -
67, Add a regression test that seeds a legacy Group that already has the
delete_job Permission on the Project content type, runs the migration function
remove_delete_job_from_role_groups (or applies the migration via Migration) and
then asserts the group's permissions no longer include the delete_job Permission
and any guardian GroupObjectPermission rows referencing that permission and the
Project content type are removed; use apps.get_model to fetch auth.Group,
auth.Permission, contenttypes.ContentType and guardian.GroupObjectPermission in
the test, create the pre-migration state (assign permission both via
group.permissions.add and a GroupObjectPermission row), invoke the migration,
and assert both the permission removal from Group and deletion of
GroupObjectPermission entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ami/main/migrations/0084_revoke_delete_job_from_roles.py`:
- Around line 22-67: Add a regression test that seeds a legacy Group that
already has the delete_job Permission on the Project content type, runs the
migration function remove_delete_job_from_role_groups (or applies the migration
via Migration) and then asserts the group's permissions no longer include the
delete_job Permission and any guardian GroupObjectPermission rows referencing
that permission and the Project content type are removed; use apps.get_model to
fetch auth.Group, auth.Permission, contenttypes.ContentType and
guardian.GroupObjectPermission in the test, create the pre-migration state
(assign permission both via group.permissions.add and a GroupObjectPermission
row), invoke the migration, and assert both the permission removal from Group
and deletion of GroupObjectPermission entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00df36c3-ed3c-4c6e-9cc7-de6aafc28dfc

📥 Commits

Reviewing files that changed from the base of the PR and between c8e5c72 and 990db07.

📒 Files selected for processing (3)
  • ami/jobs/tests/test_jobs.py
  • ami/main/migrations/0084_revoke_delete_job_from_roles.py
  • ami/users/roles.py

The new TestJobDeletePermission class duplicated coverage already
provided by TestRolePermissions in ami/main/tests.py, which asserts
the user_permissions array on list responses (the same array the UI
trash icon reads via Job.canDelete). Update the project_manager job
permissions map to expect delete=False so existing coverage passes.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow mihow merged commit 500b20b into main Apr 30, 2026
7 checks passed
@mihow mihow deleted the fix/restrict-job-delete-to-superusers branch April 30, 2026 23:58
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.

2 participants