Skip to content

feat(OIDC): Add Trust Relationship CRUD API#8042

Open
khvn26 wants to merge 6 commits into
mainfrom
feat/trust-relationships-model
Open

feat(OIDC): Add Trust Relationship CRUD API#8042
khvn26 wants to merge 6 commits into
mainfrom
feat/trust-relationships-model

Conversation

@khvn26

@khvn26 khvn26 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to #8040

In this PR, we add a CRUD API for managing Trust Relationships.

Trust relationships are stored as a one-to-one metadata row for a MasterAPIKey, so the RBAC logic and audit logs are naturally inherited. Trust relationships' master API keys are impossible to use on their own, and excluded from existing master API Key APIs.

The CRUD API at /api/v1/organisations/{id}/trust-relationships/ can only be accessed by organisation admins. The POST reponse includes the backing key's id and prefix so the dashboard can reuse the existing role assignment flow.

How did you test this code?

Added new unit and integration tests.

@khvn26
khvn26 requested review from a team as code owners July 19, 2026 16:50
@khvn26
khvn26 requested review from emyller and removed request for a team July 19, 2026 16:50
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Jul 20, 2026 12:51pm
flagsmith-frontend-preview Ignored Ignored Preview Jul 20, 2026 12:51pm
flagsmith-frontend-staging Ignored Ignored Preview Jul 20, 2026 12:51pm

Request Review

@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates labels Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds a Django trust relationships application with a soft-deletable model, conditional issuer/audience uniqueness, backing master API key lifecycle management, validation, admin registration, and structured logging. It exposes organisation-scoped CRUD endpoints restricted to authenticated organisation administrators and excludes machine credentials. Master API key listings hide relationship backing keys. Integration and unit tests cover validation, authentication, CRUD behaviour, key synchronisation, deletion, uniqueness, and logging. Runtime logger configuration and event documentation are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes


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.

@github-actions github-actions Bot added feature New feature or request and removed docs Documentation updates labels Jul 19, 2026
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-e2e:pr-8042 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api-test:pr-8042 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-frontend:pr-8042 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8042 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8042 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-api:pr-8042 Finished ✅ Results

@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 4ea4cd6 to 70667c2 Compare July 19, 2026 16:54
@github-actions github-actions Bot added the docs Documentation updates label Jul 19, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 19, 2026

@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: 5


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b5d1f87-d919-473d-8dd9-dfead0062902

📥 Commits

Reviewing files that changed from the base of the PR and between 49ea147 and 4ea4cd6.

📒 Files selected for processing (19)
  • Dockerfile
  • api/api_keys/views.py
  • api/app/settings/common.py
  • api/organisations/urls.py
  • api/tests/integration/trust_relationships/__init__.py
  • api/tests/integration/trust_relationships/conftest.py
  • api/tests/integration/trust_relationships/test_viewset.py
  • api/tests/unit/trust_relationships/__init__.py
  • api/tests/unit/trust_relationships/test_services.py
  • api/trust_relationships/__init__.py
  • api/trust_relationships/admin.py
  • api/trust_relationships/apps.py
  • api/trust_relationships/migrations/0001_initial.py
  • api/trust_relationships/migrations/__init__.py
  • api/trust_relationships/models.py
  • api/trust_relationships/serializers.py
  • api/trust_relationships/services.py
  • api/trust_relationships/views.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md

Comment thread api/tests/integration/trust_relationships/test_viewset.py
Comment thread api/trust_relationships/admin.py
Comment thread api/trust_relationships/admin.py
Comment thread api/trust_relationships/serializers.py
Comment thread api/trust_relationships/services.py
@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 70667c2 to 5220d77 Compare July 19, 2026 17:00
@github-actions

This comment was marked as outdated.

@github-actions github-actions Bot added the docs Documentation updates label Jul 19, 2026
@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 19, 2026
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

19 screenshots compared. See report for details.
View full report

@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: 1

♻️ Duplicate comments (3)
api/trust_relationships/admin.py (1)

6-10: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Django admin delete bypasses backing-key revocation, leaving a live, invisible credential.

TrustRelationshipAdmin doesn't override the delete behaviour. The default admin delete flow calls TrustRelationship.delete() (single) or queryset.delete() (bulk), both of which soft-delete the row directly without going through services.delete_trust_relationship(). As a result, the backing MasterAPIKey is never revoked.

Please override delete_model and delete_queryset to call delete_trust_relationship, or disable the delete permission on this admin entirely.

🛡️ Proposed fix routing admin deletes through the service layer
 from django.contrib import admin

 from trust_relationships.models import TrustRelationship
+from trust_relationships.services import delete_trust_relationship


 `@admin.register`(TrustRelationship)
 class TrustRelationshipAdmin(admin.ModelAdmin[TrustRelationship]):
-    list_display = ("name", "organisation", "issuer", "audience", "created_at")
+    list_display = ("name", "organisation", "issuer", "audience", "created_at")
+    list_select_related = ("organisation",)
     list_filter = ("issuer",)
     search_fields = ("name", "issuer", "audience")
+
+    def delete_model(self, request, obj):
+        delete_trust_relationship(trust_relationship=obj)
+
+    def delete_queryset(self, request, queryset):
+        for obj in queryset:
+            delete_trust_relationship(trust_relationship=obj)
api/trust_relationships/serializers.py (1)

22-28: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

is_admin should not default at the serializer field level.

A full PUT that omits the is_admin field will inject True into validated_data, causing update_trust_relationship(...) to flip an existing non-admin trust relationship back to admin, even though the caller never explicitly requested the change.

Please move the default logic into the create() method or make is_admin explicitly required.

🛡️ Proposed fix
 class TrustRelationshipSerializer(serializers.ModelSerializer[TrustRelationship]):
-    is_admin = serializers.BooleanField(default=True)
+    is_admin = serializers.BooleanField(required=False)
     claim_rules = ClaimRuleSerializer(many=True, required=False)
     master_api_key_id = serializers.CharField(read_only=True)

And in the create method:

     def create(self, validated_data: dict[str, Any]) -> TrustRelationship:
+        validated_data.setdefault("is_admin", True)
         validated_data.setdefault("claim_rules", [])
         return create_trust_relationship(**validated_data)

Also applies to: 73-83

api/trust_relationships/services.py (1)

48-53: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Audit logs fire even if an enclosing transaction rolls back.

The service functions emit their structured logs immediately after their own inner transaction.atomic() block exits. If any of these are invoked from within an outer transaction (such as ATOMIC_REQUESTS), the inner atomic() block acts merely as a savepoint. If the outer transaction subsequently rolls back, the log line will have already been emitted, producing an audit trail that does not match the persisted state.

Please wrap the log emissions in transaction.on_commit(...) so they only fire once the database changes are durably committed.

  • api/trust_relationships/services.py#L48-L53: wrap the "created" log emission in transaction.on_commit(...).
  • api/trust_relationships/services.py#L79-L84: wrap the "updated" log emission in transaction.on_commit(...).
  • api/trust_relationships/services.py#L94-L98: wrap the "deleted" log emission in transaction.on_commit(...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 799b0dc7-b3c6-4177-b82d-53138d3683ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4ea4cd6 and 5220d77.

📒 Files selected for processing (21)
  • Dockerfile
  • api/api_keys/views.py
  • api/app/settings/common.py
  • api/organisations/models.py
  • api/organisations/urls.py
  • api/tests/integration/trust_relationships/__init__.py
  • api/tests/integration/trust_relationships/conftest.py
  • api/tests/integration/trust_relationships/test_viewset.py
  • api/tests/unit/trust_relationships/__init__.py
  • api/tests/unit/trust_relationships/test_services.py
  • api/trust_relationships/__init__.py
  • api/trust_relationships/admin.py
  • api/trust_relationships/apps.py
  • api/trust_relationships/migrations/0001_initial.py
  • api/trust_relationships/migrations/__init__.py
  • api/trust_relationships/models.py
  • api/trust_relationships/serializers.py
  • api/trust_relationships/services.py
  • api/trust_relationships/views.py
  • docs/docs/deployment-self-hosting/observability/_events-catalogue.md
  • openapi.yaml

Comment thread api/trust_relationships/views.py Outdated
@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.65%. Comparing base (0838195) to head (09c0d03).

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #8042    +/-   ##
========================================
  Coverage   98.64%   98.65%            
========================================
  Files        1506     1517    +11     
  Lines       59573    59877   +304     
========================================
+ Hits        58767    59071   +304     
  Misses        806      806            

☔ 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.

@github-actions github-actions Bot added feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 20, 2026
@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 94dff4e to 4a9b748 Compare July 20, 2026 09:55
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 20, 2026
@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 4a9b748 to 94dff4e Compare July 20, 2026 10:03
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 20, 2026
@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 94dff4e to 1c5bc06 Compare July 20, 2026 10:23
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 20, 2026
khvn26 added 6 commits July 20, 2026 13:50
… CRUD API

Each trust relationship owns a hidden backing MasterAPIKey whose plaintext
is discarded at creation: it carries is_admin and RBAC role attachments,
and attributes audit records, but can never authenticate a request itself.
Backing keys are excluded from the master-api-keys list endpoint, and
master API keys cannot manage trust relationships.

beep boop
The default admin delete flow calls TrustRelationship.delete() and
queryset.delete() directly, soft-deleting the row without revoking the
backing MasterAPIKey. The soft-deleted row also keeps the key out of the
master-api-keys list (which filters on trust_relationship__isnull=True),
making the still-active key un-revocable from the dashboard.

Route delete_model and delete_queryset through delete_trust_relationship
so the backing key is always revoked.

beep boop
The organisation column in list_display triggers a query per row in the
changelist. Add list_select_related to fetch it in the initial query.

beep boop
The get_authenticators override that drops MasterAPIKeyAuthentication was
duplicated in MasterAPIKeyViewSet and TrustRelationshipViewSet. Since it
enforces a security-relevant invariant (machine credentials must not
manage credentials), extract it into ExcludeMasterAPIKeyAuthenticationMixin
so the two viewsets cannot diverge.

beep boop
drf-spectacular could not introspect get_queryset (it raises on
self.kwargs during schema generation), so organisation_pk and id were
emitted as type: string rather than integer. Guard get_queryset with
swagger_fake_view, matching the pattern used elsewhere, and regenerate
the schema so the path parameters are typed as integer.

beep boop
The name, issuer and audience columns had no help_text, which the
model-help-text convention flags. Add it so the intent is documented on
the model, in the migration, and in the generated OpenAPI schema.

beep boop
@khvn26
khvn26 force-pushed the feat/trust-relationships-model branch from 1c5bc06 to 09c0d03 Compare July 20, 2026 12:50
@github-actions github-actions Bot added docs Documentation updates feature New feature or request and removed feature New feature or request docs Documentation updates labels Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #18527 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  58.3 seconds
commit  09c0d03
info  🔄 Run: #18527 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-16 — run #18527 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  40 seconds
commit  09c0d03
info  🔄 Run: #18527 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #18527 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  39.5 seconds
commit  09c0d03
info  🔄 Run: #18527 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #18527 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  40.3 seconds
commit  09c0d03
info  🔄 Run: #18527 (attempt 1)

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

Labels

api Issue related to the REST API feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant