Skip to content

feat(backend): add share-file action node - #26

Open
LukasHirt wants to merge 2 commits into
mainfrom
feat/action-share-file
Open

feat(backend): add share-file action node#26
LukasHirt wants to merge 2 commits into
mainfrom
feat/action-share-file

Conversation

@LukasHirt

@LukasHirt LukasHirt commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the "Share File" action node end to end: actionType: 'share' grants viewer/editor access on the workflow's current file to a specified user or group via oCIS's Graph API, the same pattern tag/comment already established for Graph-only (non-WebDAV) actions.

  • Backend: GraphClient.Share(ctx, authHeader, itemID, recipient, role) in backend/pkg/ocisclient/share.go, plus a small recipient-resolution helper that looks up a user, then a group, by search term. Wired into runAction's new case "share": in backend/pkg/executor/executor.go, which renders the recipient template against vars (like every other action param), resolves the item id via the existing ResolveItemID, defaults role to "viewer", and sets result.Output = recipient.
  • Frontend: new action-share entry in NODE_TYPES (frontend/src/nodeTypes.ts) under the Actions category, with recipient (text input) and role (viewer/editor select) fields added to NodeDetailsPanel.vue, mirroring the tag/comment field-rendering pattern and the existing select-field convention used for trigger/event type.

Test plan

  • cd backend && go build ./... && go vet ./... && go test ./...
  • cd frontend && npm run test:unit && npm run check:types && npm run lint && npm run build
  • reuse lint
  • Manual verification against a live oCIS instance

🤖 Generated with Claude Code

@LukasHirt
LukasHirt requested a review from a team as a code owner July 24, 2026 16:20
@LukasHirt LukasHirt self-assigned this Jul 24, 2026
@LukasHirt
LukasHirt force-pushed the feat/action-share-file branch 2 times, most recently from 88e5155 to f864ec2 Compare July 27, 2026 14:33

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

Review: feat(backend): add share-file action node

Stats: +394/-4 across 8 files (backend + frontend)

Overview

Adds a share action: resolves a recipient (email or group name) via Graph API search, resolves the current file's item id, and POSTs an invite granting viewer/editor access. The PR is unusually candid about its own biggest risk — a code comment in share.go explicitly flags that the invite endpoint's shape "has NOT been verified against a live oCIS instance," specifically calling out uncertainty about whether roles expects literal strings or role-definition UUIDs. That's exactly the right thing to flag, so I went and checked it against the primary source (owncloud/libre-graph-api-go's OpenAPI spec, the canonical schema for this API) rather than leaving it as an open question — and the self-flagged risk is real, plus there are two more issues in the same area.

Potential issues & risks

1. Wrong API path — this will 404 against a real oCIS instance (Critical)

Share() builds %s/graph/v1.0/drives/%s/items/%s/invite. Per the official OpenAPI spec (owncloud/libre-graph-api-go/api/openapi.yaml), the invite operation is defined at:

servers:
  - url: https://ocis.../graph
paths:
  /v1beta1/drives/{drive-id}/items/{item-id}/invite:
    post:
      operationId: Invite

i.e. the real path is /graph/v1beta1/drives/{drive-id}/items/{item-id}/invitev1beta1, not v1.0. The PR's comment justifies the v1.0 choice by analogy to ItemPath in driveitem.go (GET /graph/v1.0/drives/{id}/items/{id}), but that's a basic, stable driveItem lookup — sharing/permissions ("Sharing NG") is a separate, newer surface versioned under v1beta1 in the spec. As written, every Share call will hit a route that doesn't exist.

2. roles must be role-definition UUIDs, not "viewer"/"editor" literals (Critical)

The same spec, directly in the Invite operation description:

Roles property values — For now, roles are only identified by a uuid. There are no hardcoded aliases like read or write because role actions can be completely customized.

The spec's own examples send roles: [b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5] (viewer) / [fb6c3e19-e378-47e5-b277-9732f9de6e21] (editor-equivalent) — actual UUIDs, resolved per-instance. This PR sends the literal string "viewer" or "editor" straight from the frontend's <select> through runAction into the request body unchanged. This needs a lookup against /graph/v1.0/roleManagement/permissions/roleDefinitions (filtering by display name, since role UUIDs aren't guaranteed stable across oCIS versions/deployments) before building the invite request — a hardcoded UUID constant would be fragile, but a hardcoded string alias won't work at all.

3. Group recipients aren't annotated, and will likely be mis-typed or rejected (High)

The spec: "Sharing with a groups requires setting the @libre.graph.recipient.type annotation" — e.g. {'@libre.graph.recipient.type': group, objectId: ...}. resolveRecipientID deliberately falls back to a group search when no user matches, but the request body this PR builds:

type shareRecipient struct {
    ObjectID string `json:"objectId"`
}

never sets that annotation, so a recipient resolved via the group branch is sent identically to a user recipient (and per the SDK docs, LibreGraphRecipientType defaults to "user" when absent). Once findings #1 and #2 are fixed, group-name recipients specifically would still be the one path most likely to silently misbehave.

4. Recipient resolution takes the first search hit with no exact-match check (Medium)

searchGraphCollection sends the term through Graph's $search (a fuzzy match against name/mail fields) and returns col.Value[0].ID — the first result — without verifying it's an exact match for the given email/group name. If $search for "john@example.com" also fuzzy-matches a differently-named user or group, this would silently grant access to the wrong recipient. Worth at minimum filtering results for an exact mail/displayName match and failing loudly on ambiguity, rather than trusting result ordering.

Code quality & style

  • Aside from the issues above, the code itself is clean and consistent with this package's existing patterns (searchGraphCollection/resolveRecipientID read naturally, and Share's structure mirrors the auth/request/status-check shape used elsewhere in ocisclient).
  • The frontend role field as a <select> with a typed ShareRole union is a sensible UI choice — the fix for #2 belongs entirely on the backend (resolving the display value to a role UUID before the API call), no frontend change needed.
  • Genuinely appreciate the explicit "NOTE ON VERIFICATION" comment — flagging your own uncertainty honestly is exactly the right call, and it's what let this review confirm and pinpoint the exact problems rather than just gesturing at "should verify against oCIS."

Test coverage

The backend tests are solid for the logic they can cover without a live oCIS instance — template rendering into recipient, the viewer-role default, and the required-recipient validation are all directly exercised through fakeGraph. But since fakeGraph.Share is a hand-written stand-in rather than something checking the real request shape, none of the four issues above are (or could be) caught by this suite — they're all about what actually gets sent to a real oCIS server. Given share.go itself flags "should be checked before relying on this in production," a small integration/manual-verification checklist item for the exact request shape (path, roles, recipient-type annotation) before merge would be more valuable here than more unit tests.

Security

Finding #4 (recipient ambiguity) is the one with real security weight — a fuzzy search resolving to the wrong person or group would grant unintended file access, silently. Findings #1#3 are correctness bugs that (fortunately) fail closed — they'd currently produce request failures rather than mis-scoped shares, since the malformed request likely errors out before granting anything. Once #1/#2/#3 are fixed and the feature actually starts working, #4 becomes the thing to prioritize before shipping.

Summary

The action's shape and test structure are good, but this shouldn't merge as-is — the endpoint version, the roles value format, and the group-recipient annotation are all things I can point at a primary source (the libre-graph-api-go OpenAPI spec) and say fairly confidently are wrong, not just unverified. The PR body's own checklist already has "Manual verification against a live oCIS instance" unchecked — that step will surface all three.


🤖 Generated with Claude Code

Grants viewer/editor access on a workflow's current file to a user or group
via oCIS's Graph API, following the same GraphClient.ResolveItemID pattern
tag/comment already use. Adds GraphClient.Share plus a recipient-resolution
helper (email/group name -> Graph user/group id) and a "share" runAction case.

The invite endpoint shape (POST /graph/v1.0/drives/{driveID}/items/{itemID}/
invite) and its request body are inferred from this codebase's existing Graph
client conventions and general oCIS/libre-graph API knowledge, not verified
against a live oCIS instance -- see the doc comment on Share for specifics.
Tests cover the runAction wiring (template rendering, ResolveItemID -> Share
call, default role, missing-recipient failure) via the existing fakeGraph
mock; no new HTTP-level test harness was added since ocisclient has none yet.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Adds a "Share File" entry to the Actions palette (action-share, actionType
"share") mirroring the existing tag/comment action pattern: a recipient text
input (rendered against workflow vars server-side, like every other action
param) and a viewer/editor role select in NodeDetailsPanel.vue, matching the
select-field convention already used for trigger/event type.

Covered by new Vitest specs: nodeTypes.spec.ts asserts the node type is
registered under the Actions category and discoverable like other actions;
NodeDetailsPanel.spec.ts mounts the panel (stubbing oc-icon/oc-button/
oc-text-input, the first component-mount test in this suite) to assert the
recipient input and role select are wired to actionParams and emit updates.

Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt
LukasHirt force-pushed the feat/action-share-file branch from 1ae465a to a5f13af Compare July 29, 2026 09:16
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.

2 participants