fix(pipeline): gate card writes on pipeline_items.update, not pipelines.update - #272
Conversation
…es.update PipelineItemsController authorized every card write (create, move_to_stage, edit) against the parent Pipeline's #update? — i.e. pipelines.update, the manager-level permission. So the default agent could delete a whole stage (pipeline_stages.delete) but not move a card, and unblocking cards by granting pipelines.update also unlocked archive/set_as_default/service_definitions (over-grant). Introduce PipelinePolicy#update_items? (pipeline_items.update + the same accessible_record? scope) and authorize card writes against it. Reads stay at #view?. Adds pipeline_items.update to the catalog mirror. Specs: pipeline_items_write_authz_spec now stubs update_items?; new pipeline_items_permission_rbac_spec proves the split (denied without the key, denied for a holder of pipelines.update alone, allowed with pipeline_items.update, and the key exists in the catalog). Deploy together with the evo-auth-service-community seed/catalog change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer's GuideIntroduces a new card-level pipeline_items.update authorization path in PipelinePolicy and PipelineItemsController so card mutations are gated by a dedicated permission instead of the broader pipelines.update, and adds corresponding RBAC catalog entry and tests to verify behavior and catalog presence. Sequence diagram for pipeline_items.write authorization using update_items?sequenceDiagram
actor User
participant PipelineItemsController
participant PipelinePolicy
User->>PipelineItemsController: create/move/update pipeline_item
PipelineItemsController->>PipelineItemsController: WRITE_ACTIONS.include?(action_name)
alt write action
PipelineItemsController->>PipelinePolicy: authorize(@pipeline, :update_items?)
PipelinePolicy->>PipelinePolicy: update_items?
PipelinePolicy->>PipelinePolicy: permitted_item_write?
PipelinePolicy->>PipelinePolicy: accessible_record?
else read action
PipelineItemsController->>PipelinePolicy: authorize(@pipeline, :view?)
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
🔗 Par de deploy com #85 (auth). Este é o gate; a chave Ordem geral da leva (desmembramento do antigo PR #84): Os 4 PRs do auth tocam os mesmos arquivos ( Par de deploy: #85 (auth, a chave Independentes (sem ordem): #281 (CRM-181 comentário), #282 (CRM-182 spec), #283 (CRM-190 spec), #310 (CRM-178 front). 🤖 Generated with Claude Code |
…M-178 review) Addresses the two CRM-178 review blockers on this gate: 1. destroy no longer grants the agent delete-card. `destroy` was in WRITE_ACTIONS, so it authorized against pipeline_items.update (which the agent seed grants) — letting an agent delete a card and cascade-delete its stage_movements/tasks/ products. It now authorizes against :update? (pipelines.update, manager), mirroring CRM-182 keeping deletes off the agent. Card create/move/edit stay on :update_items? (pipeline_items.update); reads on :view?. 2. CI regression fixed. pipeline_items_archived_spec stubbed PipelinePolicy#update?, the predicate this PR retired for card writes — the create/move_conversation examples fell through to the real #update_items? and 401'd. The spec now also stubs #update_items? so it stays focused on the archived-pipeline guard. The RBAC spec grows the proof: move_to_stage authz (agent key), and destroy is DENIED for a holder of pipeline_items.update (card survives) but ALLOWED for pipelines.update. 14 examples, 0 failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d) (CRM-178 review) Closes review finding 7: the card-write positive asserted only not-401. It now POSTs a real contact card and asserts the card is created (2xx + PipelineItem count +1) with pipeline_items.update, so the split is proven by an actual create, not just an open gate. move_to_stage keeps the gate-level assertion (its conversation-first lookup is out of scope for an authz spec). Verified: 14 examples, 0 failures; and the destroy pair is RED against the pre-fix controller (agent got 200 on delete), so the guard catches the leak, it does not lock it in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- M6: pipeline_items_write_authz_spec.rb -> *_write_authz_rbac_spec.rb so it
matches the community-parity `*_rbac_spec.rb` glob and actually runs in CI
(its update? -> update_items? change was never executed before).
- L8: products_controller comment was false ("mirrors ...: mutations on :update?").
Product-catalog writes are manager-level (pipelines.update via require_permissions);
only pipeline CARD writes moved to pipeline_items.update. Comment corrected.
- L9: register `pipeline_items.update` via EvoPermissionConcern.register_permission_key
so the catalog-conformance guard covers it, even though the gate is Pundit
(accessible_record? needs the resolved pipeline) not the named check_*_permission!.
- L10: `def update_notesconversation` was a typo — no route reached it while the
route/WRITE_ACTIONS/set_pipeline_item all referenced `update_conversation`,
so PATCH .../update_conversation hit a dead action. Renamed to update_conversation.
QA: pipeline_items_write_authz_rbac + permission_rbac + archived + catalog
conformance specs — 18 examples, 0 failures (1 pending: auth SSOT cross-check
skips when the sibling repo is absent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… move move_conversation relocates a card OUT of whatever pipeline currently holds it, but ensure_authorized_user only authorized the pipeline named in the URL. With card writes now agent-level, an agent could pull a card out of a private funnel it cannot see by naming one it can — the opposite of what update_items? + accessible_record? promise. Proven: without the guard the request answers 200 and the card leaves the private pipeline. Also covers update_conversation, which this PR brought back from a dead route (the action was defined as the typo update_notesconversation, so no request ever reached it): a gate example plus one proving the move and the note actually land. Both live in the *_rbac_spec that CI runs. Fixes the two stale comments the split left behind (PipelinePolicy#accessible_record? and the pipeline_items entry in mutating_actions_gate_guard_spec).
Problem
PipelineItemsControllerauthorized every card write (create,move_to_stage, edit) against the parent Pipeline's#update?— i.e.pipelines.update, the manager-level permission. Consequences: the default agent could delete a whole stage (pipeline_stages.delete) but not move a card, and the only way to unblock cards — grantingpipelines.update— also unlocked archive / set_as_default / pipeline_service_definitions / products (over-grant).Fix
PipelinePolicy#update_items?=pipeline_items.update+ the sameaccessible_record?scope.PipelineItemsController#ensure_authorized_userauthorizesWRITE_ACTIONSagainst:update_items?; reads stay:view?.pipeline_items.updateto the catalog mirror.Tests
spec/requests/api/v1/pipeline_items_write_authz_spec.rb— updated to the card-write policy level (update_items?).spec/requests/api/v1/pipeline_items_permission_rbac_spec.rb(new) — proves the split: denied without the key, denied for a holder ofpipelines.updatealone, allowed withpipeline_items.update, and the key exists in the catalog.Run via
spec/requests/api/v1/*_rbac_spec.rb+ the pipeline authz spec.Deploy together
With the evolution-foundation/evo-auth-service-community PR that adds
pipeline_items.updateto the catalog + agent seed.🤖 Generated with Claude Code
Summary by Sourcery
Gate pipeline card mutations with the dedicated
pipeline_items.updatepermission while keeping destructive operations manager-authorized.New Features:
pipeline_items.updatepermission for routine pipeline card writes while retaining manager-level authorization for card deletion.Bug Fixes:
Enhancements:
Tests:
Chores:
pipeline_items.updatein the permission catalog mirror and update authorization guard expectations.