Authorize metadata-column deletion on parent corpus and add regression test#2085
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…lumn-deletion-permission-issue
Code ReviewThis PR correctly fixes a privilege escalation bug where a user with direct 1. Service-layer regression in
|
…lumn-deletion-permission-issue
…DELETE positive test Replace the raw ORM column fetch with BaseService.get_or_none so the lookup is READ-gated through the service layer (matching CreateMetadataColumn/ UpdateMetadataColumn) before the parent-corpus DELETE check, restoring IDOR-safe not-found semantics and satisfying the config/graphql service-layer rule. Add test_corpus_delete_without_column_delete_can_delete proving corpus DELETE alone authorizes deletion. Add security changelog fragment.
Review: Authorize metadata-column deletion on parent corpus (#2085)This PR fixes a real authorization bypass: 1.
|
UpdateMetadataColumn gated on column.user_can(UPDATE), the same bypass
DeleteMetadataColumn had before this PR: a user granted change_column
directly on a corpus-metadata column, without corpus UPDATE, could
rename it or swap its validation_config/data_type. Metadata columns are
corpus-scoped objects, so both mutations now authorize destructive/
mutating writes against column.fieldset.corpus.
Extract the shared column+corpus lookup into
_get_metadata_column_with_corpus(), used by both Update and Delete, so
the corpus-scoped gate can't drift back to a column-scoped one in only
one of them. The helper also fetches column, fieldset, and corpus in a
single select_related("fieldset__corpus") query instead of two extra
lazy round-trips.
Add regression tests: UpdateMetadataColumnTestCase mirrors
DeleteMetadataColumnTestCase (corpus permission alone suffices; column
permission alone does not), plus a test pinning the fieldset-with-no-
corpus guard path for both mutations.
|
Review: Authorize metadata-column mutations on parent corpus Solid fix. I reviewed the full diff plus the final state of What's good
Items worth addressing
None of these block the fix itself — the authorization change and its regression tests look correct and are a clear net security improvement. Items 1–2 are the ones I'd want resolved (or explicitly deferred) before merge; item 3 is just an FYI. |
Motivation
deleteMetadataColumnonly checkedColumn-level permissions, allowing a column creator or direct grantee to delete a corpus-scoped metadata column without having DELETE permission on the parent corpus.Datacellvalues, so destructive schema changes must be authorized against the containing corpus.Description
DeleteMetadataColumn.mutateinconfig/graphql/extract_mutations.pyto load theColumnwithselect_related("fieldset__corpus"), require the column to belong to a corpus metadata schema, and authorize the operation withBaseService.require_permission(corpus, user, PermissionTypes.DELETE, request=info.context)instead of authorizing on the childColumn.is_manual_entryguard so extract-managed columns remain undeletable via this surface.opencontractserver/tests/test_metadata_columns_graphql.pyto attach the testFieldsetto theCorpus, add acolumn_creatortest user and client, and addtest_column_creator_without_corpus_delete_cannot_deleteto assert that a column creator with directCRUDon the childColumnbut without corpusDELETEcannot delete the corpus metadata column.Testing
python -m py_compile config/graphql/extract_mutations.py opencontractserver/tests/test_metadata_columns_graphql.pyand parsed both files withast.parse, which succeeded.git diff --check(no issues found).pytestforDeleteMetadataColumnTestCase, but the test run was blocked by an environment dependency error (ModuleNotFoundError: No module named 'django'), so full integration tests could not be executed in this environment.Codex Task