Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[FIX] Mark server-managed organization field non-editable (DRF 3.15 compat)#2092

Merged
chandrasekharan-zipstack merged 3 commits into
mainZipstack/unstract:mainfrom
fix/org-editable-falseZipstack/unstract:fix/org-editable-falseCopy head branch name to clipboard
Jun 19, 2026
Merged

[FIX] Mark server-managed organization field non-editable (DRF 3.15 compat)#2092
chandrasekharan-zipstack merged 3 commits into
mainZipstack/unstract:mainfrom
fix/org-editable-falseZipstack/unstract:fix/org-editable-falseCopy head branch name to clipboard

Conversation

@chandrasekharan-zipstack

@chandrasekharan-zipstack chandrasekharan-zipstack commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Problem

On DRF 3.15.2, every org-scoped create started failing with organization: This field is required (e.g. Add LLM adapter). Root cause: DRF 3.15 auto-derives a multi-field UniqueTogetherValidator from a model's UniqueConstraints, and that validator requires every field of the constraint in the request body. organization is set server-side (never sent by clients) but fields="__all__" exposed it as a writable field, so it landed in the validator's required set → 400.

This is a latent modeling issue DRF 3.15 surfaced: a server-managed column was client-writable (also a cross-tenant injection risk). Downgrading DRF was rejected — 3.14.0 carries CVE-2024-21520 (XSS).

Fix

Mark organization editable=False:

  • DefaultOrganizationMixin (covers adapter, connector, workflow, pipeline, api, prompt-studio, tags, platform-api, usage, dashboard-metrics, tenant OrganizationMember, and cloud lookups / manual_review_v2).
  • The five models that attach organization directly rather than via the mixin: Configuration, OrganizationGroup, ResourceGroupShare, account_v2.PlatformKey, notification_v2.NotificationBuffer.

DRF then builds organization read-only, which removes it from field_sources and drops the auto-generated validator for every org-scoped constraint (every such constraint includes organization, so one field change covers all). It also closes the latent injection — organization is no longer client-writable.

Verified on DRF 3.15.2 against the real serializers: before → organization writable, validator present, is_valid() fails with "required"; after → read-only, zero org validators, create payload without organization validates. A sweep of all 87 ModelSerializers confirms no org-scoped validator remains.

Migrations

15 AlterField(editable=False) migrations, generated via makemigrations (not hand-written). All state-onlyeditable is not a database attribute, so no DDL is emitted.

Notes

  • created_by/modified_by need no change: the only constraint that includes created_by (cloud AgenticProject) also includes organization, so it's already covered; and created_at/modified_at are auto_now* (already non-editable).
  • Cloud counterpart (migrations for lookups / manual_review_v2, which inherit the mixin): Zipstack/unstract-cloud#1574. Merge/deploy this OSS PR first.
  • Adds a DB-free regression test (adapter_processor_v2/tests/test_org_editable.py).

🤖 Generated with Claude Code

…ompat)

`organization` is set server-side and never accepted as client input, but
`fields="__all__"` exposed it as a writable serializer field. DRF 3.15 derives
multi-field UniqueTogetherValidators from model UniqueConstraints and requires
every field of the constraint in the request body, so every org-scoped create
started failing with `organization: This field is required`.

Mark `organization` editable=False on DefaultOrganizationMixin and on the five
models that attach it directly (Configuration, OrganizationGroup,
ResourceGroupShare, PlatformKey, NotificationBuffer). DRF then builds it
read-only, which drops it from the auto-generated uniqueness validators for
every org-scoped constraint. Migrations are state-only (editable is not a
database attribute, so no DDL is emitted).

Keeps DRF on 3.15.2, retaining the CVE-2024-21520 XSS patch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoRxNHky8RoutBCFrSzZQ7
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes
    • Standardized tenant scoping by making organization fields server-managed (read-only) across multiple models, with consistent nullable foreign-key behavior where applicable.
    • Updated admin views to keep organization read-only for organization rate limits.
  • Tests
    • Added coverage to ensure API/DRF serializers do not accept organization from client input and validate payloads correctly for organization-scoped models.

Walkthrough

organization ForeignKey fields across 14+ Django apps and DefaultOrganizationMixin are marked editable=False to prevent client/form editing. An anchor migration in account_v2 is introduced, and each affected app adds a chained AlterField migration. A new test verifies that AdapterInstanceSerializer exposes organization as read-only.

Changes

Server-managed organization FK enforcement

Layer / File(s) Summary
Base mixin and account_v2 anchor
backend/utils/models/organization_mixin.py, backend/account_v2/models.py, backend/account_v2/migrations/0005_alter_platformkey_organization.py
DefaultOrganizationMixin.organization drops default=None and gains editable=False; PlatformKey.organization is similarly updated; the anchor migration (depended on by all others) is introduced.
Direct model-level editable=False updates
backend/configuration/models.py, backend/notification_v2/models.py, backend/tenant_account_v2/models.py
Configuration.organization, NotificationBuffer.organization, OrganizationGroup.organization, and ResourceGroupShare.organization each gain editable=False and a server-managed comment.
Dependent app migrations
backend/configuration/migrations/0002_alter_configuration_organization.py, backend/adapter_processor_v2/migrations/0004_alter_adapterinstance_organization.py, backend/api_v2/migrations/0004_alter_apideployment_organization_and_more.py, backend/connector_v2/migrations/0006_alter_connectorinstance_organization.py, backend/dashboard_metrics/migrations/0003_alter_eventmetricsdaily_organization_and_more.py, backend/notification_v2/migrations/0003_alter_notificationbuffer_organization.py, backend/pipeline_v2/migrations/0004_alter_pipeline_organization.py, backend/platform_api/migrations/0004_alter_platformapikey_organization.py, backend/prompt_studio/prompt_studio_core_v2/migrations/0008_alter_customtool_organization.py, backend/prompt_studio/prompt_studio_registry_v2/migrations/0002_alter_promptstudioregistry_organization.py, backend/tags/migrations/0002_alter_tag_organization.py, backend/tenant_account_v2/migrations/0004_alter_organizationgroup_organization_and_more.py, backend/usage_v2/migrations/0006_alter_usage_organization.py, backend/workflow_manager/workflow_v2/migrations/0021_alter_workflow_organization.py
Thirteen dependent app migrations each declare dependencies on the anchor account_v2/0005, then apply AlterField to set null=True, blank=True, and editable=False on each app's organization ForeignKey field.
Serializer read-only and admin enforcement
backend/adapter_processor_v2/tests/test_org_editable.py, backend/api_v2/admin.py
New test module verifies AdapterInstanceSerializer marks organization as read_only, excludes it from UniqueTogetherValidator, and accepts creation payloads without organization. Admin configuration adds organization to readonly_fields in OrganizationRateLimitAdmin.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: marking server-managed organization fields as non-editable for DRF 3.15 compatibility.
Description check ✅ Passed The description is comprehensive and well-structured, covering problem statement, fix explanation, migration details, and testing notes, though it deviates from the repository's template format.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/org-editable-false

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a DRF 3.15 regression where org-scoped creates (e.g. Add LLM adapter) failed with organization: This field is required by marking the organization FK editable=False across all affected models, which removes it from DRF's auto-generated UniqueTogetherValidator and closes a latent cross-tenant injection vector.

  • Root fix in DefaultOrganizationMixin (editable=False) covers all mixin users with a single change; five models that declare the FK directly (Configuration, OrganizationGroup, ResourceGroupShare, PlatformKey, NotificationBuffer) are patched individually.
  • 15 state-only migrations (AlterField) are generated — editable is not a DB attribute so no DDL is emitted.
  • Admin guard in api_v2/admin.py adds organization to readonly_fields to prevent a FieldError at admin page load when the field appears in an explicit fieldset.

Confidence Score: 5/5

Safe to merge — all changes are model metadata adjustments with no DDL impact, and the fix is consistently applied across the mixin and all five direct-FK models.

Every model that declares organization either inherits from DefaultOrganizationMixin (now updated) or has an explicit editable=False added. Migrations are state-only. The only admin registration with explicit fieldsets was correctly updated. The regression test covers the core DRF-3.15 symptom and the most important models; the gap in test coverage for mixin-inherited models is a quality nit, not a correctness issue.

No files require special attention. The regression test in adapter_processor_v2/tests/test_org_editable.py would benefit from adding the remaining mixin-using models to its parametrized list.

Important Files Changed

Filename Overview
backend/utils/models/organization_mixin.py Core of the fix: adds editable=False to the shared organization FK and removes the now-redundant default=None (covered by null=True). Clean change.
backend/api_v2/admin.py Adds organization to readonly_fields to prevent FieldError when the admin form build encounters an editable=False field in an explicit fieldset. Correct handling.
backend/adapter_processor_v2/tests/test_org_editable.py New regression test verifying the DRF 3.15 fix; ORG_SCOPED_MODELS is incomplete — 8 models that received migrations are missing from the parametrized guard.
backend/configuration/models.py Adds editable=False to the directly-declared organization FK on Configuration (one of the five non-mixin models). Correct.
backend/tenant_account_v2/models.py Adds editable=False to OrganizationGroup and ResourceGroupShare FK declarations (non-mixin models). OrganizationMember is covered via the mixin.
backend/notification_v2/models.py Adds editable=False to NotificationBuffer.organization — the fifth non-mixin model. Correct.
backend/account_v2/migrations/0005_alter_platformkey_organization.py State-only AlterField migration (editable is not a DB attribute, no DDL emitted). Migration chain correctly depends on account_v2.0004.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Client sends create request\nno organization in payload] --> B[DRF ModelSerializer.is_valid]
    B --> C{organization\neditable?}
    C -- "Before fix\n(editable=True)" --> D[DRF builds UniqueTogetherValidator\nrequiring organization field]
    D --> E["400: organization\nThis field is required"]
    C -- "After fix\n(editable=False)" --> F[DRF marks organization read_only\nDrops UniqueTogetherValidator]
    F --> G[Validation passes]
    G --> H[serializer.save calls model.save]
    H --> I[DefaultOrganizationMixin.save\nsets organization from UserContext]
    I --> J[Record persisted with\ncorrect tenant scoping]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Client sends create request\nno organization in payload] --> B[DRF ModelSerializer.is_valid]
    B --> C{organization\neditable?}
    C -- "Before fix\n(editable=True)" --> D[DRF builds UniqueTogetherValidator\nrequiring organization field]
    D --> E["400: organization\nThis field is required"]
    C -- "After fix\n(editable=False)" --> F[DRF marks organization read_only\nDrops UniqueTogetherValidator]
    F --> G[Validation passes]
    G --> H[serializer.save calls model.save]
    H --> I[DefaultOrganizationMixin.save\nsets organization from UserContext]
    I --> J[Record persisted with\ncorrect tenant scoping]
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
backend/adapter_processor_v2/tests/test_org_editable.py:33-46
**Regression test doesn't cover all migrated models**

Eight models received `AlterField` migrations in this PR but are absent from `ORG_SCOPED_MODELS`: `dashboard_metrics.EventMetricsDaily/Hourly/Monthly`, `platform_api.PlatformApiKey`, `prompt_studio_core_v2.CustomTool`, `prompt_studio_registry_v2.PromptStudioRegistry`, `tenant_account_v2.OrganizationMember`, and `usage_v2.Usage`. Since they all inherit from `DefaultOrganizationMixin` the invariant holds today, but if a future developer overrides `organization` on one of these models without `editable=False`, the regression guard won't catch it. Adding the missing entries to the parametrize list would close that gap.

Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/org-editabl..." | Re-trigger Greptile

Comment thread backend/adapter_processor_v2/tests/test_org_editable.py
@jaseemjaskp jaseemjaskp self-requested a review June 19, 2026 09:43

@jaseemjaskp jaseemjaskp 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.

Automated PR review (PR Review Toolkit). Posting the one critical/high-confidence regression below; lower-severity notes are shared with the author separately for confirmation before posting.

@jaseemjaskp jaseemjaskp 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.

Follow-up: posting the medium/low findings from the PR Review Toolkit pass (the test silent-pass on line 51 is omitted — already covered by greptile's existing comment).

Comment thread backend/utils/models/organization_mixin.py
Comment thread backend/configuration/models.py
Comment thread backend/adapter_processor_v2/tests/test_org_editable.py Outdated
chandrasekharan-zipstack and others added 2 commits June 19, 2026 16:05
…uard

- OrganizationRateLimitAdmin: add organization to readonly_fields. It is now
  editable=False, and listing a non-editable field in fieldsets raises
  FieldError at admin form build (runtime 500, not caught by manage.py check).
- Expand regression test to assert the organization field stays editable=False
  across every org-scoped model (the root-cause invariant), plus the existing
  adapter serializer checks for the concrete DRF 3.15 symptom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoRxNHky8RoutBCFrSzZQ7

@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)
backend/api_v2/admin.py (1)

18-18: ⚡ Quick win

Use tuple instead of list for readonly_fields to follow Django convention.

Django admin conventionally uses immutable tuples for readonly_fields, list_display, and other field sequences. The current list triggers RUF012 (mutable default value for class attribute).

♻️ Proposed fix
-    readonly_fields = ["organization", "created_at", "modified_at"]
+    readonly_fields = ("organization", "created_at", "modified_at")
🤖 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 `@backend/api_v2/admin.py` at line 18, The readonly_fields attribute is defined
as a list but Django convention requires using an immutable tuple for field
sequences like readonly_fields, list_display, and similar admin attributes.
Convert the list syntax (using square brackets) to tuple syntax (using
parentheses) for the readonly_fields assignment that contains "organization",
"created_at", and "modified_at" to follow Django conventions and resolve the
RUF012 lint warning about mutable default values.
🤖 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.

Nitpick comments:
In `@backend/api_v2/admin.py`:
- Line 18: The readonly_fields attribute is defined as a list but Django
convention requires using an immutable tuple for field sequences like
readonly_fields, list_display, and similar admin attributes. Convert the list
syntax (using square brackets) to tuple syntax (using parentheses) for the
readonly_fields assignment that contains "organization", "created_at", and
"modified_at" to follow Django conventions and resolve the RUF012 lint warning
about mutable default values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac3d33fe-2262-47f2-be47-81e279d567c8

📥 Commits

Reviewing files that changed from the base of the PR and between b137c60 and c5e66c9.

📒 Files selected for processing (2)
  • backend/adapter_processor_v2/tests/test_org_editable.py
  • backend/api_v2/admin.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/adapter_processor_v2/tests/test_org_editable.py

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
unit-connectors unit 64 12 0 3 16.7
unit-core unit 0 0 4 0 1.2
unit-platform-service unit 9 0 1 0 1.3
unit-prompt-service unit 15 0 0 0 19.8
unit-rig unit 53 0 0 0 3.3
unit-runner unit 11 0 0 0 3.6
unit-sdk1 unit 390 0 0 0 20.4
unit-tool-registry unit 0 0 1 0 1.2
unit-workers unit 0 0 0 0 17.3
TOTAL 542 12 6 3 84.9

Critical paths

⚠️ Critical paths not yet covered

  • auth-login — User can log in and obtain a session cookie. (entry: POST /api/v1/auth/login; declared coverage: no groups declared)
  • adapter-register-llm — Register and validate an LLM adapter. (entry: POST /api/v1/adapter/; declared coverage: no groups declared)
  • workflow-create-execute — Create a workflow, configure source+destination, execute, poll, fetch result. (entry: POST /api/v1/workflow/{id}/execute/; declared coverage: e2e-workflow)
  • api-deployment-run — Deploy a workflow as an API, POST a document, receive structured JSON. (entry: POST /deployment/api/{org}/{name}/; declared coverage: e2e-api-deployment)
  • prompt-studio-fetch-response — Prompt Studio: create project, add prompt, run single-pass, get response. (entry: POST /api/v1/prompt-studio/prompt-studio-tool/{id}/fetch_response/; declared coverage: e2e-prompt-studio)
  • pipeline-etl-execute — Run an ETL pipeline from source connector to destination. (entry: POST /api/v1/pipeline/{id}/execute/; declared coverage: no groups declared)
  • usage-token-tracking — Per-execution token usage is recorded and retrievable. (entry: GET /api/v1/usage/get_token_usage/; declared coverage: no groups declared)
  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (entry: internal: backend → rabbitmq → workers/file_processing; declared coverage: no groups declared)
  • callback-result-delivery — Async results are posted back via the callback worker. (entry: internal: workers/callback → backend /internal endpoints; declared coverage: no groups declared)
✅ Covered critical paths
  • tool-sandbox-exec — covered by unit-runner

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit 64b06a7 into main Jun 19, 2026
9 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the fix/org-editable-false branch June 19, 2026 10:45
@sonarqubecloud

Copy link
Copy Markdown

ritwik-g pushed a commit that referenced this pull request Jun 21, 2026
…herValidator errors (#2098)

[FIX] Revert djangorestframework 3.15.2 -> 3.14.0 to unblock staging

The DRF 3.15.2 bump (#2087) regressed rc.343. DRF 3.15 auto-derives
multi-field UniqueTogetherValidators from model UniqueConstraints, which
3.14 only did for legacy unique_together. Two breakages followed for every
ModelSerializer(fields="__all__") over a model using Meta.constraints:

1. Server-set constraint fields (e.g. organization) -> "<field>: required"
   on create. Partially patched by #2092 for the 5 org-attached models.
2. Client-supplied constraint fields (TableSettings, ProfileManager,
   agentic table settings, lookups) -> "...must make a unique set" raised at
   is_valid(), short-circuiting the views' intended
   `except IntegrityError: raise DuplicateData(<friendly>)` path. This both
   replaced the friendly message and moved the error from a top-level
   `detail` string into nested `non_field_errors`, which the frontend does
   not surface -> silent failures (e.g. duplicate LLM profile name, table
   settings no longer editable after first save).

Pin back to 3.14.0 to restore the known-good behaviour across the whole
unique-constraint class at once. The CVE-2024-21520 XSS patch carried by
3.15.2 is intentionally deprioritized; the 3.15 upgrade will be reattempted
later with a serializer-level fix (drop auto-derived uniqueness validators).

Reverts only the DRF entry from #2087; other batched bumps untouched. The
org `editable=False` changes (#2092) remain correct no-ops under 3.14 (org
is set server-side in save() from UserContext), so no rollback is needed there.


Claude-Session: https://claude.ai/code/session_01G8hAHc4HUo42zY1g9LAjKu

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.