Skip to content

feat(core,api,agent): edit organization-owned items - #55

Open
UnbreakableMJ wants to merge 1 commit into
mainfrom
feat/org-item-edit
Open

feat(core,api,agent): edit organization-owned items#55
UnbreakableMJ wants to merge 1 commit into
mainfrom
feat/org-item-edit

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

What

Enables vault edit (and TUI e) on organization-owned items — previously
refused with "editing organization items is not supported yet." On a vault
that is ~99% org-owned, that refusal made editing effectively unusable.

Along the way it fixes a latent wire-body gap that also affected personal
items.

The wire-body gap (fix)

CipherRequest (the POST/PUT /api/ciphers body) only serialized
type/folderId/name/notes/login/secureNote. It silently dropped card,
identity, and custom fields
— so personal card/identity edits never
reached the server, despite the in-memory cipher being built correctly. The body
now carries card, identity, fields, and — for org support — organizationId
and key (a PUT omitting those strips the item's org membership and its
per-cipher key).

Org-aware edit

An org item's fields are encrypted under a per-cipher key that is itself
wrapped under the organization key. The edit path now:

  1. Resolves the base key via Vault::edit_base_keys — the org key when held,
    else the user key. An org item whose key this session does not hold is
    refused up front (it can't be decrypted, so it can't be safely re-encrypted)
    rather than corrupted.
  2. Resolves the per-cipher key via the new Cipher::field_keys (symmetric to
    Cipher::decrypt on the read path) and re-encrypts only changed fields under
    it, echoing organizationId/key back on the PUT.

Out of scope: adding a new item directly into an org/collection (needs
collection selection) — vault add still creates personal items.

Tests (no live writes)

  • vault-core: field_keys round-trip (re-encrypt under the resolved item key
    → decrypts back), keyless fallback, wrong-base-key error.
  • vault-api: CipherRequest carries organizationId/key/card/identity/
    fields when present, and omits organizationId/key for personal items.
  • vault-agent: org-edit gating — an org item whose key we hold reaches the
    network step (proving crypto passed), one whose key we lack is refused.

Local gates green via nix develop: cargo fmt --check, clippy --workspace --all-targets --all-features -D warnings, cargo test. No writes to any real
vault
— the end-to-end write against a live org is a maintainer step
(docs/m2-vaultwarden.md).

🤖 Generated with Claude Code

`vault edit` refused organization ciphers ("editing organization items is
not supported yet") — a blocker on a vault that is ~99% org-owned. Enable it,
and fix the wire-body gap that made it (and personal card/identity edits)
impossible.

- vault-api: CipherRequest (the POST/PUT body) only sent
  type/folderId/name/notes/login/secureNote, silently dropping card,
  identity, and custom fields — so personal card/identity edits never reached
  the server. It now also carries organizationId and key, which an org PUT
  must echo back or it strips the item's org membership and per-cipher key.
- vault-core: add Cipher::field_keys — resolves the (enc, mac) a cipher's
  fields are encrypted under, unwrapping the per-cipher key under the base
  (org/user) key or falling back to it. Symmetric to Cipher::decrypt on the
  read path, so a re-encrypted field round-trips.
- vault-agent: edit_cipher resolves the base key via the new
  Vault::edit_base_keys (org key when held, else user key) and re-encrypts
  changed fields under the resolved per-cipher key. An org item whose key this
  session does not hold is refused up front rather than corrupted.

Adding a new item directly into an org/collection stays out of scope (needs
collection selection); vault add still creates personal items.

Tests: field_keys round-trip + wrong-base + keyless (vault-core);
CipherRequest carries org/key/card/identity/fields and omits them for
personal items (vault-api); org-edit gating — held key reaches the network,
unheld key refused (vault-agent). No live writes; the end-to-end write
against a real org is a maintainer step (docs/m2-vaultwarden.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23d16c7200

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +570 to +571
#[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")]
organization_id: Option<&'a str>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve collection IDs when updating organization ciphers

When the organization item belongs to one or more collections, the /sync cipher's collectionIds are ignored by vault_core::Cipher, and this newly enabled organization PUT consequently cannot echo them in CipherRequest. Bitwarden uses those IDs to retain collection membership, so such edits can be rejected for lacking an accessible collection or disassociate the item from its existing collections; model and round-trip collectionIds before allowing organization edits.

Useful? React with 👍 / 👎.

Comment on lines +588 to +589
#[serde(skip_serializing_if = "Option::is_none")]
fields: Option<&'a [vault_core::cipher::CustomField]>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve linked IDs when serializing custom fields

When an item contains a type-3 linked custom field, serializing c.fields now emits only the fields represented by vault_core::cipher::CustomField, which has name, value, and type but no Bitwarden linkedId. The update therefore sends an incomplete linked field, causing the server either to reject the edit or lose the field's link target; add linkedId to the sync model and round-trip it in this body.

Useful? React with 👍 / 👎.

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