Skip to content

feat: default to PulpServiceAccessPolicy for RBAC (PULP-2120) - #1452

Draft
CryptoRodeo wants to merge 1 commit into
pulp:mainfrom
CryptoRodeo:feat/pulp-2120
Draft

feat: default to PulpServiceAccessPolicy for RBAC (PULP-2120)#1452
CryptoRodeo wants to merge 1 commit into
pulp:mainfrom
CryptoRodeo:feat/pulp-2120

Conversation

@CryptoRodeo

@CryptoRodeo CryptoRodeo commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Make PulpServiceAccessPolicy the default DRF permission class instead of DomainBasedPermission. The API is now governed by object-level RBAC (pulpcore's AccessPolicyFromDB) rather than the old org_id-match check.

Changes

  • Config: flip PULP_REST_FRAMEWORK__DEFAULT_PERMISSION_CLASSES in clowdapp.yaml and dev-container/settings.py.
  • PyPIYankMonitorViewSet: drop its DomainBasedPermission override so it picks up the new default.
  • CreateDomainView / MigrateDomainView: switch to IsAuthenticated. DomainBasedPermission used to set the ContextVars the dual-write reads, so CreateDomainView now calls set_domain_create_context(request) instead.
  • MigrateDomainView also enforces core.change_domain directly now that it no longer gets domain scoping from DomainBasedPermission.
  • authorization.py: add set_domain_create_context(request), which pulls org_id/user_id from X-RH-IDENTITY for the dual-write.

Heads up for callers

  • Non-admins can't create domains via the generic DomainsApi anymore (needs core.add_domain, else 403). Use POST /api/pulp/create-domain/.
  • Unviewable domains return 404, not 403.
  • Cross-org listing on non-PyPI endpoints returns an empty, scoped 200 instead of a 403 — no rows leak.

Tests

  • Non-admin domain creation moved off DomainsApi onto a new create_service_domain fixture (test_authentication, test_group_based_permissions, test_domain_dual_write, test_content_guard_permission, test_domain_based_permissions).
  • test_authentication: accept 405 for PUT/DELETE/PATCH on the PyPI simple API — pulp_python's SimpleView only maps GET/POST, so the router 405s before any permission check.
  • test_content_guard_permission: cross-org non-PyPI listing now expects 200 + count == 0.
  • test_domain_dual_write: both DomainsApi tests drive the create-domain endpoint; the non-admin autocommit branch is now unreachable (covered by unit tests).

Known regressions / out of scope

  • Lightwell readonly-group and subscription content-listing tests: that behavior is DomainBasedPermission-only and deliberately not ported. Expected.
  • Features-Service tests fail closed to 403 in a bare dev container — needs the real service, not a code regression.
  • test_content_view_search: blocked on missing client bindings + fixture from feature cross domain search initial commit #1439, unrelated to this change.

Summary by Sourcery

Adopt PulpServiceAccessPolicy as the default API authorization model and align domain operations and coverage with object-level RBAC.

New Features:

  • Make PulpServiceAccessPolicy the default DRF permission class, moving API authorization to object-level RBAC.

Bug Fixes:

  • Preserve domain dual-write identity context for self-service domain creation and enforce change permission during domain migration.
  • Prevent unauthorized domain access from leaking object existence by returning scoped results or 404 responses.

Enhancements:

  • Update domain creation and migration endpoints to use authentication with explicit authorization appropriate to each operation.
  • Remove the redundant PyPI yank monitor permission override so it follows the service-wide RBAC policy.

Tests:

  • Update authentication, domain visibility, group permissions, content guard, and dual-write coverage for RBAC behavior and the self-service domain creation flow.

Chores:

  • Add functional fixtures and cleanup support for domains created through the self-service endpoint.

@sourcery-ai

sourcery-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR switches the REST API from DomainBasedPermission to PulpServiceAccessPolicy/object-level RBAC, adds explicit authentication and domain authorization behavior for the special domain views, preserves dual-write context propagation, and updates functional coverage to use the supported self-service creation endpoint and RBAC response semantics.

Sequence diagram for authenticated domain creation with dual-write context

sequenceDiagram
    actor User
    participant API as CreateDomainView
    participant Context as authorization.py
    participant Domain as Domain creation
    participant Signal as post_create_domain

    User->>API: POST /api/pulp/create-domain/
    API->>API: IsAuthenticated
    API->>Context: set_domain_create_context(request)
    Context-->>API: org_id_var and user_id_var set
    API->>Domain: create domain
    Domain->>Signal: post_create_domain
    Signal-->>Signal: dual-write using context vars
    Domain-->>User: Domain response
Loading

Sequence diagram for RBAC-protected domain migration

sequenceDiagram
    actor User
    participant API as MigrateDomainView
    participant Domain as Target domain
    participant RBAC as User permissions

    User->>API: POST migrate domain
    API->>API: IsAuthenticated
    API->>Domain: resolve target domain
    alt Domain not viewable
        Domain-->>API: Not found
        API-->>User: 404
    else Domain resolved
        API->>RBAC: has_perm(core.change_domain, domain)
        alt Permission granted
            API-->>User: Migration response
        else Permission denied
            API-->>User: 403
        end
    end
Loading

File-Level Changes

Change Details Files
Make object-level RBAC the default authorization model for the REST API.
  • Changed production and development DRF defaults to PulpServiceAccessPolicy.
  • Removed the PyPI yank monitor’s legacy permission override.
  • Updated functional expectations for RBAC-scoped 404 and empty-list responses.
deploy/clowdapp.yaml
dev-container/settings.py
pulp_service/pulp_service/app/viewsets.py
pulp_service/pulp_service/tests/functional/test_authentication.py
pulp_service/pulp_service/tests/functional/test_content_guard_permission.py
pulp_service/pulp_service/tests/functional/test_domain_based_permissions.py
CHANGES/2120.feature
Preserve domain creation dual-write behavior while replacing legacy permission checks on domain API views.
  • Changed create and migrate views to require authentication.
  • Explicitly populates identity context for domain creation dual-write.
  • Added a direct core.change_domain object permission check before migration.
pulp_service/pulp_service/app/authorization.py
pulp_service/pulp_service/app/viewsets.py
Refactor functional tests around the supported self-service domain creation endpoint.
  • Added a reusable fixture that creates domains through POST /api/pulp/create-domain/ and performs asynchronous cleanup.
  • Migrated authentication, group/RBAC, dual-write, content-guard, and domain-permission tests to the fixture.
  • Covered header/basic-auth creation, group scoping, role assignments, and updated method/status expectations.
pulp_service/pulp_service/tests/functional/conftest.py
pulp_service/pulp_service/tests/functional/test_authentication.py
pulp_service/pulp_service/tests/functional/test_domain_dual_write.py
pulp_service/pulp_service/tests/functional/test_group_based_permissions.py
pulp_service/pulp_service/tests/functional/test_domain_based_permissions.py
pulp_service/pulp_service/tests/functional/test_content_guard_permission.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pulp_service/pulp_service/tests/functional/conftest.py" line_range="123-128" />
<code_context>
+        # the domain's distributions and repositories here first, else the domain delete task
+        # fails with ProtectedError and the domain leaks into later same-org list assertions.
+        try:
+            for api in (pulpcore_bindings.DistributionsApi, pulpcore_bindings.RepositoriesApi):
+                for obj in api.list(pulp_domain=name).results:
+                    _delete_href(obj.pulp_href)
+            _delete_href(href)
+        except Exception:
+            pass
+
+
</code_context>
<issue_to_address>
**issue (testing):** The create_service_domain fixture catches every exception during cleanup and silently passes, so failed distribution/repository/domain deletions leave domains and content in the test stack while the tests still pass. Those leaked objects alter later list/count assertions and can make the functional suite order-dependent.

**Triggers:** When an asynchronous cleanup task fails, deletion returns an unexpected status, or a protected related object cannot be removed.

**Suggested fix:** Fail the fixture teardown or at least log and re-raise cleanup errors after attempting all created objects, rather than swallowing the exception.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and this changes the global API permission default and the authentication and object-level authorization decisions for domain creation and migration, so an incorrect policy could deny access or expose protected operations from the moment it ships. Reverting restores the prior checks, but requests processed under an overly permissive policy cannot be undone.

Blocking findings: pulp_service/pulp_service/tests/functional/conftest.py:128


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread pulp_service/pulp_service/tests/functional/conftest.py Outdated
Make `PulpServiceAccessPolicy` the default DRF permission class instead of
`DomainBasedPermission`. The API is now governed by object-level RBAC
(pulpcore's `AccessPolicyFromDB`) rather than the old org_id-match check.

Changes
- Config: flip PULP_REST_FRAMEWORK__DEFAULT_PERMISSION_CLASSES in
  clowdapp.yaml and dev-container/settings.py.
- PyPIYankMonitorViewSet: drop its DomainBasedPermission override so it picks
  up the new default.
- CreateDomainView / MigrateDomainView: switch to IsAuthenticated.
  DomainBasedPermission used to set the ContextVars the dual-write reads, so
  CreateDomainView now calls set_domain_create_context(request) instead.
- MigrateDomainView also enforces core.change_domain directly now that it no
  longer gets domain scoping from DomainBasedPermission.
- authorization.py: add set_domain_create_context(request), which pulls
  org_id/user_id from X-RH-IDENTITY for the dual-write.

Heads up for callers
- Non-admins can't create domains via the generic DomainsApi anymore (needs
  core.add_domain, else 403). Use POST /api/pulp/create-domain/.
- Unviewable domains return 404, not 403.
- Cross-org listing on non-PyPI endpoints returns an empty, scoped 200 instead
  of a 403 — no rows leak.

Tests
- Non-admin domain creation moved off DomainsApi onto a new
  create_service_domain fixture (test_authentication, test_group_based_permissions,
  test_domain_dual_write, test_content_guard_permission, test_domain_based_permissions).
- test_authentication: accept 405 for PUT/DELETE/PATCH on the PyPI simple API —
  pulp_python's SimpleView only maps GET/POST, so the router 405s before any
  permission check.
- test_content_guard_permission: cross-org non-PyPI listing now expects
  200 + count == 0.
- test_domain_dual_write: both DomainsApi tests drive the create-domain
  endpoint; the non-admin autocommit branch is now unreachable (covered by unit
  tests).

Known regressions / out of scope
- Lightwell readonly-group and subscription content-listing tests: that
  behavior is DomainBasedPermission-only and deliberately not ported. Expected.
- Features-Service tests fail closed to 403 in a bare dev container — needs the
  real service, not a code regression.
- test_content_view_search: blocked on missing client bindings + fixture from
  feature pulp#1439, unrelated to this change.

Signed-off-by: Bryan Ramos <bramos@redhat.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CryptoRodeo
CryptoRodeo marked this pull request as draft September 3, 2026 19:33
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.

1 participant