feat(backend): add share-file action node - #26
Conversation
88e5155 to
f864ec2
Compare
dj4oC
left a comment
There was a problem hiding this comment.
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: Invitei.e. the real path is /graph/v1beta1/drives/{drive-id}/items/{item-id}/invite — v1beta1, 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
readorwritebecause 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/resolveRecipientIDread naturally, andShare's structure mirrors the auth/request/status-check shape used elsewhere inocisclient). - The frontend
rolefield as a<select>with a typedShareRoleunion 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
f864ec2 to
1ae465a
Compare
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>
1ae465a to
a5f13af
Compare
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 patterntag/commentalready established for Graph-only (non-WebDAV) actions.GraphClient.Share(ctx, authHeader, itemID, recipient, role)inbackend/pkg/ocisclient/share.go, plus a small recipient-resolution helper that looks up a user, then a group, by search term. Wired intorunAction's newcase "share":inbackend/pkg/executor/executor.go, which renders therecipienttemplate againstvars(like every other action param), resolves the item id via the existingResolveItemID, defaultsroleto"viewer", and setsresult.Output = recipient.action-shareentry inNODE_TYPES(frontend/src/nodeTypes.ts) under the Actions category, withrecipient(text input) androle(viewer/editor select) fields added toNodeDetailsPanel.vue, mirroring thetag/commentfield-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 buildreuse lint🤖 Generated with Claude Code