Remove the deprecated owncloudsql driver configuration from the storage-users,
auth-basic, users, groups and sharing services and from the migrate command.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fix(idm): sync bind config when resetting a service user password
Resetting an IDM service user password with `ocis idm resetpassword
--user-type service` only changed the entry in the IDM directory. Services
such as auth-basic, users and groups still bound to LDAP as the reva service
user using the old bind_password from their configuration, so after a restart
those binds failed and regular admin login started returning 401 Unauthorized.
The command now rewrites the matching bind_password and service_user_passwords
keys in ocis.yaml when it is present, and always prints the environment
variables that must carry the new password for env-var and distributed
deployments it cannot rewrite.
Fixes: https://github.com/owncloud/ocis/issues/12569
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Save the MFA status after the user is resolved so signed-URL downloads
inherit it, and check its age on read. Build the signing-key URL with
the URL API so it works with the vault base URL.
* fix: [OCISDEV-1476] fix AuthApp requests failing with a custom PROXY_USER_OIDC_CLAIM
* fix: [OCISDEV-1476] map ErrAccountNotFound to 401 in verifyUser
AccountResolver.verifyUser only mapped ErrAccountDisabled to 401 before
falling through to a generic 500. The identity backend excludes disabled
accounts from by-username lookups and returns ErrAccountNotFound instead,
so a disabled user authenticating via AppAuth or basic auth got a 500
instead of a 401, caught by the acceptance test suite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: [OCISDEV-1523] fix the LDAP master-ID filter for an empty base filter
EnhanceFilterWithMasterID replaced an empty LDAP filter with a master-ID-only
filter. This action restricted an unrestricted search to master-ID users
only. The bug broke the cross-instance user search in multi-instance
deployments. The function now keeps an empty filter unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: [OCISDEV-1523] require an explicit LDAP user filter with a master ID
EnhanceFilterWithMasterID in ocis-pkg/ldap/filter.go now keeps an empty
LDAP filter unchanged instead of restricting it to master-ID users only.
The auth-basic, users, groups, and graph services reuse this function to
compute their default LDAP user filter at startup.
An unset filter now means these services resolve or authenticate every
user in the shared LDAP directory, not only master-ID users. This action
is a behavior change for a deployment that sets
OCIS_MULTI_INSTANCE_MASTER_ID without an explicit OCIS_LDAP_USER_FILTER.
Each service now rejects startup when OCIS_MULTI_INSTANCE_MASTER_ID is
set and its own user filter is empty. This action keeps a multi-instance
deployment scoped to its own users by default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor: [OCISDEV-1523] simplify EnhanceFilterWithMasterID guard clauses
The function collapses three early-return guards into one condition.
This action removes a duplicated rationale comment between filter.go
and filter_test.go.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: [OCISDEV-1523] point the changelog entry at the master backport PR
The changelog entry pointed at PR 13096, the stable-8.2 backport. This
commit points the entry at the master-branch PR number instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Requests on unprotected routes can carry a reva token without a resolved
user: reva's internal downloads to the data gateway (/data) are sent with
the caller's token. create_home logged "no user in context" at error level
for each of them, so every download added an error line. Log at debug with
the path instead; the request is passed on unchanged, as before.
Fixes#13101
Claude-Session: https://claude.ai/code/session_01WpoeSoKYEdgQcWjEzTVKpT
Signed-off-by: Paul Faure <paul@faure.ca>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The file and folder converters filled User from the event's Owner, which
reva does not set, so the field was empty on every upload, download, move,
delete, restore and version restore; where Owner was set it named the owner
instead of the actor. Space, user and group converters hard-coded User to
an empty string although every one of those events carries the Executant.
User now comes from the event's Executant everywhere an actor exists. The
Owner field of file events is unchanged; the failed public-link access
event has no actor and keeps an empty User.
Tests give the fixtures different owner and actor ids so they can tell the
two apart (they fail on the previous code), and add cases for the user and
group converters. The group member added/removed messages passed the user
and group ids in swapped order; fixed.
Fixes#4789
Signed-off-by: Paul Faure <paul@faure.ca>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Juan Pablo Villafañez <jpvillafanez@izertis.com>
The next oCIS release is 9.0.0, so all envvars that were annotated with
the previously planned 8.3.0 introduction version need to be updated.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(search): add a --timeout flag to the index command
ocis search index called the search service with a hard-coded 10 minute
request timeout. The service walks the whole space inside that request, so
when the timeout expired the walk was cancelled (error walking the tree:
context canceled) and everything after that point stayed unindexed. A space
whose walk takes longer than ten minutes could never be fully indexed from
the command line.
The timeout is now a flag: --timeout takes a duration, default 10m so the
behaviour is unchanged out of the box; 0 waits indefinitely (go-micro treats
a zero RequestTimeout as an already-expired deadline, so a far-off one is
substituted).
Fixes#13052
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Paul Faure <paul@faure.ca>
* docs(search): say the index command needs the running search service
The index command is a gRPC client of the search service and never opens
the Bleve index, so it works with the default exclusive lock
(SEARCH_ENGINE_BLEVE_SCALE=false) but needs the service running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpoeSoKYEdgQcWjEzTVKpT
Signed-off-by: Paul Faure <paul@faure.ca>
---------
Signed-off-by: Paul Faure <paul@faure.ca>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat(audit): record tag changes in the audit log
Graph publishes TagsAdded and TagsRemoved on every tag assign and unassign,
but only the search service listened, so a tag change left no trace in the
audit log.
The audit service now registers both events and logs them as file_tags_add
and file_tags_remove, with the acting user (Executant), the space owner, the
file id and path, and the comma-separated tags. Graph sets the Timestamp on
both events, which it left unset. Two test cases use different owner and
actor ids; the audit README documents the two actions.
Fixes#13054
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Paul Faure <paul@faure.ca>
* fix(audit): record only the tags that actually changed; skip no-op tag events
Review follow-up for #13055:
- graph: TagsAdded/TagsRemoved now carry the tags that were actually added
or removed (requesting an existing tag, or removing an absent one, is not
a change). Search only uses the event's Ref, so it is unaffected.
- audit: skip tag events whose tag list is empty. Graph still publishes
TagsRemoved for search when nothing was present; that is not an audit event.
- audit: document why User is the executant for tag events; describe Tags
as carried by the event; remove a stray copy-pasted doc comment.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpoeSoKYEdgQcWjEzTVKpT
Signed-off-by: Paul Faure <paul@faure.ca>
* fix(audit): carry the requested tags in tag events, as before
Per review: TagsAdded/TagsRemoved go back to carrying the tags as
requested, and the audit log records every request, including tags that
were already present (or absent, for a removal), so repeated requests stay
visible. Computing the actual delta belongs in the tags package, not here.
Removes the graph-side tagsDiff helper and its test, and the audit
service's skip for empty tag lists. The other review fixes stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpoeSoKYEdgQcWjEzTVKpT
Signed-off-by: Paul Faure <paul@faure.ca>
---------
Signed-off-by: Paul Faure <paul@faure.ca>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(storage-users): [OCISDEV-1382] list upload sessions through the coordinator
Signed-off-by: Firas Frikha <firas.frikha@kiteworks.com>
* fix(storage-users): [OCISDEV-1382] use a safe type assertion for the driver config
Signed-off-by: Firas Frikha <firas.frikha@kiteworks.com>
---------
Signed-off-by: Firas Frikha <firas.frikha@kiteworks.com>
Remove the "Session expiring" extend/dismiss popup and its expiry
worker. Drop the frontend session_duration capability and config;
OCIS_MFA_SESSION_DURATION stays in use by the proxy.
* chore(OCISDEV-900): bump reva to pr7-switchover, add upload_directory and mount_id to revaconfig
* chore(OCISDEV-900): bump reva to the switchover rebased on the upload write fix
* chore(OCISDEV-900): bump reva to main with the upload coordinator switchover
---------
Co-authored-by: Firas Frikha <firas.frikha@kiteworks.com>
* fix: the public share configuration and fixed graceful shutdown
* fix: [OCISDEV-1433] remove GRPC_MAX_CONNECTION_AGE
The setting closed healthy grpc connections on a timer so that clients would redo
their DNS lookup. It never helped against the failure it was reached for - a
connection whose peer stopped answering - because the age limit is enforced by
the server, the grace period was left at infinity so a request already in flight
was never ended, and any value without a unit suffix silently disabled it again.
Detecting a peer that stops answering is now the job of the grpc clients, which
ping the peer while a request is in flight. The servers here got the matching
enforcement policy so they accept those pings; the client side arrives with the
next reva bump.
* fix: Added the new variable OCDAV_DISABLE_PROPFIND_PUBLIC_LINK_RESOLUTION that disable the resolution of public link shares when answering PROPFIND requests.
* chore: bump reva
* fix: The keepalive added to grpc clinet
* chore: bump reva
* build(deps): bump go 1.26.8, grpc-go dependency
---------
(cherry picked from commit e86eb4d210)
For the 9.0 branch, the reva dependency was pointed at reva's main-branch
merge of PR #748 (commit 373f9509a, pseudo-version
v2.0.0-20261005091647-373f9509a363) instead of the 8.2-targeted
e0beaa380005, since the 8.2 merge predates the VaultMode/UploadSessionFilter.Orphaned
APIs already present on the 9.0 reva lineage.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
# changelog/unreleased/security-bump-grpc.md
# go.mod
# go.sum
# vendor/google.golang.org/grpc/server.go
# vendor/google.golang.org/grpc/stream.go
# vendor/google.golang.org/grpc/version.go
# vendor/modules.txt
* chore: bump reva
* chore: bump pnpm dependencies
* fix: avoid go vet false positive in userlog conversion.go
The go.mod bump to go 1.26.8 made go vet flag loadTemplates' calls to
t.Get(nt.Subject/nt.Message) as non-constant format strings, breaking
the build for services/userlog/pkg/service. These are translation
lookup keys, not printf format strings, so route the call through a
local func value to defeat vet's static trace.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* chore: bump grpc
Fixes GO-2026-6443, a server panic when handling requests missing
authority or Host headers, flagged by govulncheck in CI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Searching for share recipients in the vault ("Safe") could take minutes,
because each search scanned the role assignments of every account in the
system. Vault eligibility is now checked only for the users the search
matched, a few at a time in parallel, so the search is about as fast as
outside the vault.
* fix: use "ownCloudUUID" as attribute name everywhere
* fix: use "displayName" as LDAP attribute
* fix: ensure the WopiFolderURLBaseURL always ends with "/"
* fix: restore wrong substitution
* Revert "fix: use "displayName" as LDAP attribute"
This reverts commit 02de7a2db7.
Note that the default values outside of the graph service still remains
* fix: improve contrast on file name/extension in editor topbar
The file name and extension shown in the topbar when a file is open
were rendered in white on top of a light brand-colored background in
the vault Light Theme, and fell back to the same failing combination
in the vault Dark Theme - High Contrast (where the color was left
unset). Both now use a dark color that reads correctly against that
background.
* fix: improve contrast on share role dropdown hover text
The share permission dropdown's role options (e.g. "Can view",
"Can edit") rendered their hovered text in a color too close in
lightness to the hover background in the vault Dark Theme,
producing insufficient contrast. Lightened the color slightly to
restore adequate contrast without affecting its other uses.
* fix: improve low contrast on disabled danger buttons
Disabled danger-filled buttons had insufficient contrast between their
label text and background, caused by a disabled-state opacity fade
fighting against the muted background color. Danger buttons now keep
full opacity when disabled and use a darker muted background.
* feat(web): add mermaid-editor app for standalone .mmd diagrams
md-editor-v3's mermaid support only fires inside a Markdown fenced
code block, so there was no way to edit a Mermaid diagram on its own.
This adds a dedicated split-pane editor/preview app (mirroring
web-app-html-editor's shape) for the mmd/mermaid extensions, feeding
raw diagram source straight into mermaid.render() - no Markdown
wrapping involved. Invalid syntax surfaces an inline error instead of
crashing the preview pane, and the rendered SVG is sanitized with
DOMPurify the same way the embedded markdown-mermaid renderer already
does.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(web): add e2e coverage and WCAG 2.2 checks for mermaid-editor
Wires the new 'mmdFile' resource type into the create/edit e2e
resource helpers (mirroring txtFile/mdFile) and adds a spec covering
diagram rendering, invalid-syntax error surfacing, and the view-mode
toggle. The new mermaidEditor.ts page-object asserts the existing
WCAG 2.1 a11y baseline every other e2e spec uses, plus an additional
WCAG 2.2 tag scan (target-size, focus-appearance, etc.) scoped to
this file only - the shared a11y helper used by every other spec
stays on its current WCAG 2.1 tag set, since widening it repo-wide is
a separate decision with its own pre-existing-violation triage cost.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): register mermaid-editor in the default enabled apps list
CI's e2e run for the new mermaid-editor app timed out waiting for the
"New > Mermaid diagram" menu entry: config.json.dist (a reference/dev
file) had "mermaid-editor" added, but the actual runtime default config
a packaged oCIS binary serves comes from services/web's Go defaults,
which didn't. Also runs Prettier over the 3 files check:format flagged.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): correct vue3-gettext version, add changelog entry
vue3-gettext was pinned to an exact "2.4.0" in web-app-mermaid-editor's
package.json (a copy-paste slip) instead of "^4.0.1" like every other
web app package, pulling in a stray v2 dependency tree with its own
deprecated transitives (glob@7.2.3, inflight@1.0.6). Also adds the
changelog/unreleased entry this PR was missing.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): resolve stale mermaid lockfile entry from merge conflict
web/pnpm-lock.yaml pinned mermaid to 11.16.0 for the new
web-app-mermaid-editor importer, left over from a badly-resolved merge
with master (which resolves mermaid to 11.16.1 in every other
importer and in the packages snapshot). pnpm install --frozen-lockfile
failed in CI with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY since 11.16.0
had no corresponding packages entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
* test(web): register mermaid-editor in the e2e CI oCIS config
tests/config/ci/ocis-config.json is the WEB_UI_CONFIG_FILE the e2e
docker-compose stack (tests/acceptance/docker/src/ocis-base.yml) feeds
the oCIS instance under test. It keeps its own "apps" allowlist,
separate from services/web's Go defaults (already fixed) and
config.json.dist (also already fixed) - and it never got
"mermaid-editor" added. Without it, the running instance never
registers the app, so the "New > Mermaid diagram" menu entry the
mermaidEditor.spec.ts e2e test waits for never renders, and the click
times out after all retries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
* fix(web): give the mermaid-editor CodeMirror pane an accessible name
The plain-text CodeMirror content div renders with role="textbox"
aria-multiline="true" but no aria-label/aria-labelledby/title, which
axe-core (and screen readers) flag as a serious WCAG violation - this
is exactly what the e2e a11y scan caught on the new mermaidEditor
spec. Fixed via EditorView.contentAttributes, the same mechanism
CodeMirror exposes for this. MermaidEditorPane.spec.ts is updated to
install defaultPlugins() like its sibling specs already do, since the
component now calls useGettext() for the label text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
---------
Signed-off-by: David Walter <david.walter@kiteworks.com>
Signed-off-by: dj4oC <david.walter@kiteworks.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The proxy public share authenticator set the brute force protection skip
marker on every authentication, including requests where the proxy is the
only authenticator. Failed password attempts on those requests were never
counted, so the rate limit for password protected public links was not
enforced.
The skip marker is now only set for requests that are re-authenticated
downstream, so failed attempts are reliably counted and the throttle is
applied consistently.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change the documented introductionVersion of the
OCIS_LDAP_INSTANCE_MAPPER_CACHE_TTL environment variable from 8.0.0 to 8.3.0.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document that the *_TLS_INSECURE options for the nats-js-kv store, cache and
service registry disable authentication of the NATS server and are for testing
only, so operators securing a multi-node deployment are not misled into
weakening the connection they just enabled TLS on.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: [OCISDEV-115] colour tag chips by tag name
Tag chips were all the same grey, so a file's tags were hard to tell apart
at a glance and the same tag looked like nothing in particular across the app.
Fill each chip with a colour derived from the tag name: `hash(tag) % length`
picks a slot in the theme's new `designTokens.tagColorsList`, emitted as
`--oc-color-tag-N`. The colour is derived, never stored, so a tag looks the
same for every user on every device with no API, storage or migration change.
The label colour is computed per fill rather than pinned to a token: the
fills come from the theme, so one token cannot stay legible against all of
them, and `--oc-color-text-inverse` gets it backwards in the light theme. The
calculation lives in `useTagColor` rather than in `OcTag` because the theme
store is a reactive dependency and `getComputedStyle` is not — resolving it
in the component would leave the label stale after a theme switch. The fill
is read straight out of `tagColorsList` rather than from the emitted css var,
since `getHexFromCssVar` reports an unresolvable var as `#000000` and would
hand a pale fill an illegible white label.
A filled chip carries `oc-tag-filled`, which forces the colours of everything
nested inside it down as `inherit`/`currentColor`. Those nested elements
otherwise repaint themselves — a `router-link` takes the global anchor colour,
a raw `OcButton` its own swatch, `.oc-tag .oc-icon > svg` a muted fill —
which left navy labels and a washed-out close icon on the deep light-theme
fills, and labels that were too light on the pale dark-theme ones. Nothing
hardcodes a colour, so both themes are covered by construction.
`OcTag` keeps its default appearance: it is the generic badge (the app-store
"most recent" pill, instance badges, the disabled-space indicator, the `+N`
counter), so both `fillColor` and `labelColor` are opt-in.
`initializeThemes` now merges with replace semantics for arrays. deepmerge
concatenates by default, which would turn a `tagColorsList` present in both
`defaults` and a theme into 60 entries and shift every hue.
Also make the tag overflow indicator on the files list a real, keyboard
reachable button that shows the hidden tags in a popover instead of opening
the sidebar. That popover is sized to its tags: `OcDrop` is a fixed 300px
wide, which left it nearly empty when it held a single chip capped at 80px by
`.resource-table-tag`, with the right alignment the tags column passes down
pushing that chip to the far edge. 300px is kept only as a cap so a long tag
still truncates.
Both `theme.json` copies gain 30 placeholder fills per theme, per theme
rather than in `defaults`, so the feature can be exercised end to end while
design's real values are outstanding. They were generated by binary-searching
HSL lightness for a target relative luminance per hue, since equal-lightness
hues differ a lot in perceived brightness: deep fills with white labels in
the light themes, pale fills with black labels in the dark ones. Worst-case
label contrast is 6.31:1 (light) and 8.52:1 (dark), higher in both
high-contrast themes. Both copies are needed — web-runtime's file is what
`pnpm build` publishes to `web/dist/themes`, while an ocis binary serves the
go:embed-ed `services/web/assets/themes` copy instead
(services/web/pkg/server/http/server.go layers it over the embedded core
assets). Every theme in a file has the same list length, so a tag keeps its
colour slot across a theme switch.
The env var was tagged introductionVersion:"NEXT", a placeholder the docs
process rejects. The var was introduced by #12467 and is still unreleased;
master is on the 8.3.0 dev cycle, so pin it to 8.3.0.
* fix: [OCISDEV-128] recover login page after the current user was deleted
When a user account was deleted while its session was still stored in the
browser, opening the web interface redirected to an authentication error page
and looped there. lico kept accepting the stale logon session but could no
longer resolve the deleted user, so the sign-in flow re-issued the authorization
request without its parameters and lico answered with a self-referential
error redirect that the browser followed forever.
Add an idp middleware that detects this stuck authorization loop (an OAuth2
error parameter on the authorize endpoint, which never occurs in a healthy
inbound request), expires the stale logon cookie server-side and restarts a
clean login so a different user can sign in.
* chore: reference PR number in changelog
* feat: add ownCloud branding background to login screens
Ships the ownCloud branding image as the default IDP login background and
aligns the web and Keycloak login screens to match.
Reworked for the no-npm IDP (services/idp/src was removed by #12086): the
branding is applied directly in the committed bundle. The default background
image lives at services/idp/assets/identifier/static/media and is referenced
from theme.css, which now anchors it to the top right (background-position) so
the ownCloud logo stays fully visible at every viewport aspect ratio instead
of being cropped by center positioning. Operators can still override the image
via IDP_LOGIN_BACKGROUND_URL (unchanged on master).
Also embeds the same image in the web service theme assets and adds an
"owncloud" Keycloak login theme (already anchored top right) to the
ocis_keycloak deployment example.
Signed-off-by: David Walter <david.walter@kiteworks.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
* docs(deployment): title-case ocis_keycloak README headings
Co-authored-by: Martin <github@diemattels.at>
Signed-off-by: Lukas Hirt <info@hirt.cz>
---------
Signed-off-by: David Walter <david.walter@kiteworks.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
Co-authored-by: kobergj <jkoberg@owncloud.com>
Co-authored-by: Martin <github@diemattels.at>
A theme can define its own logo per UI variant under
clients.web.themes[].logo, including the built-in "owncloud" theme, most
of whose variants (dark, high contrast, ...) already do this. The admin
logo upload endpoint only ever patched clients.web.defaults.logo, so the
active theme's own per-variant logo kept winning over a logo an admin
explicitly uploaded through the admin settings web app.
Force the branding (admin-uploaded) logo onto every theme variant's own
logo entries after the theme merge, so it always takes precedence.
Signed-off-by: Lukas Hirt <info@hirt.cz>
* feat(collaboration): add Version to Collabora WOPI CheckFileInfo
Add Version field to the Collabora struct to match Microsoft and OnlyOffice
implementations. This allows Collabora Online to properly detect stale
documents for cache invalidation and document reload detection, which is
particularly important in multi-node Kubernetes deployments.
- Add Version field to Collabora struct with json:"Version,omitempty" tag
- Add case for KeyVersion in Collabora.SetProperties method
- Update existing Collabora integration tests to verify Version is populated
- Add three unit tests for Version handling in collabora_test.go
- Add integration test verifying Collabora CheckFileInfo response includes Version
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add LastModifiedTime to Collabora CheckFileInfo for conflict detection
Add LastModifiedTime to the Collabora struct to match Microsoft and OnlyOffice,
and implement the X-COOL-WOPI-Timestamp conflict detection PutFile relies on to
use it. Collabora Online tracks the LastModifiedTime it received in
CheckFileInfo and sends it back during PutFile; without this field the WOPI
host has no way to detect that a file changed since Collabora last checked it,
so concurrent edits could silently overwrite each other.
- Add LastModifiedTime field to Collabora struct (json:"LastModifiedTime,omitempty")
and wire it in Collabora.SetProperties via the existing KeyLastModifiedTime constant
- fileconnector.go already populates KeyLastModifiedTime unconditionally for all
targets, so no gating changes were needed there
- Add HeaderCoolWopiTimestamp ("X-COOL-WOPI-Timestamp") constant and thread it
from HttpAdapter.PutFile through the ContentConnectorService.PutFile interface
- ContentConnector.PutFile compares the client-supplied timestamp against the
file's current mtime and returns a new NewResponseCOOLConflict() (409,
COOLStatusCode 1010) on mismatch; an empty timestamp (Microsoft/OnlyOffice,
and file-creation call sites in PutRelativeFileSuggested/Relative) skips the
check entirely, so existing WOPI clients see no behavior change
- Hand-updated the mockery-generated ContentConnectorService mock (mockery
itself fails to build under the local Go toolchain) and every PutFile call
site/mock expectation across fileconnector.go, fileconnector_test.go,
contentconnector_test.go and httpadapter_test.go for the new 5-arg signature
- Add fileinfo/collabora_test.go unit tests for LastModifiedTime SetProperties/
JSON marshalling/omitempty, update the 3 existing Collabora CheckFileInfo
integration tests with the expected LastModifiedTime, and add PutFile tests
covering COOL timestamp mismatch (409) and match (success) in
contentconnector_test.go
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(collaboration): handle nil Mtime and add HttpAdapter test coverage for COOL WOPI timestamp
The COOL conflict-detection check in ContentConnector.PutFile treated a
nil Mtime as an automatic mismatch, causing a permanent 409 on every
save for files with no recorded mtime. Skip the comparison (fail open)
when Mtime is nil, matching CheckFileInfo's fallback posture. Also add
a regression test for that case, plus a test asserting HttpAdapter.PutFile
correctly reads the X-COOL-WOPI-Timestamp header and passes it through
to the content connector, which previously had no coverage.
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add ReadOnly to Collabora WOPI CheckFileInfo
Add ReadOnly to the Collabora struct to match Microsoft and OnlyOffice, and
populate it in CheckFileInfo for all three targets. KeyReadOnly already
existed as a constant and both Microsoft.SetProperties and
OnlyOffice.SetProperties already had a `case KeyReadOnly:`, but nothing in
fileconnector.go ever put a value into the properties map for it - so
ReadOnly was silently false for every WOPI target, regardless of whether the
user actually had write access.
- Add ReadOnly field to the Collabora struct (json:"ReadOnly", no omitempty,
consistent with Microsoft/OnlyOffice since false is meaningful) and wire it
in Collabora.SetProperties via the existing KeyReadOnly constant
- In fileconnector.go's CheckFileInfo, set
infoMap[fileinfo.KeyReadOnly] = wopiContext.ViewMode != VIEW_MODE_READ_WRITE
unconditionally for all targets. wopiContext.ViewMode is already resolved
upstream from the actual CS3 permissions, space status and lock state, so
this derives ReadOnly from that single source of truth instead of
re-deriving the same signals in the connector
- Add fileinfo/collabora_test.go unit tests for ReadOnly SetProperties (true/
false) and JSON marshalling (asserting the field is present for both true
and false, since there is no omitempty)
- Add two new CheckFileInfo integration tests asserting ReadOnly=true for a
view-only mode and ReadOnly=false for read-write, and add ReadOnly: true to
the 4 existing CheckFileInfo integration tests that assert a full
Microsoft/Collabora/OnlyOffice struct under a non-read-write ViewMode
(the 2 ViewMode assignments in the Lock/GetLock test block were left
untouched since those tests don't assert against a FileInfo struct)
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add SupportsUpdate to Collabora WOPI CheckFileInfo
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(collaboration): add missing godoc comment to Collabora.SupportsUpdate
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add IsAnonymousUser to Collabora WOPI CheckFileInfo
Implement support for the IsAnonymousUser property in the Collabora struct,
enabling Collabora Online to distinguish between authenticated and anonymous
users (public share access). This property is derived from the user context's
public-share-role indicator and is already computed in fileconnector.go.
Changes:
- Add IsAnonymousUser field to Collabora struct with omitempty tag
- Wire KeyIsAnonymousUser case in SetProperties
- Add unit tests: SetProperties behavior, JSON omit/include when false/true
- Add integration tests: anonymous user context returns true, authenticated
user returns false, UserId still populated for anonymous users
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add URL properties to Collabora WOPI CheckFileInfo
Add the 7 MS-WOPI file URL properties (CloseUrl, DownloadUrl,
FileSharingUrl, FileVersionUrl, HostEditUrl, HostViewUrl, SignoutUrl)
to the Collabora fileinfo struct, mirroring the Microsoft struct's
existing fields and JSON tags. This is struct/SetProperties wiring
only; fileconnector.go population follows in a subsequent commit.
SignoutUrl is intentionally left unpopulated in CheckFileInfo (no
fileconnector.go change): the collaboration service has no OIDC
issuer/logout configuration to build a real sign-out URL from, so
Collabora reaches parity with Microsoft/OnlyOffice's existing
"field exists but is never populated" gap rather than introducing a
new one.
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): populate CloseURL and DownloadURL in CheckFileInfo
CloseURL and DownloadURL were never populated in CheckFileInfo for any
WOPI target (Microsoft, OnlyOffice, or Collabora), despite Microsoft's
struct having had these fields all along.
- CloseURL reuses the existing parentFolderURL (the same "folder
containing the file" URL already used for BreadcrumbFolderURL)
rather than building a second URL for the same concept. This flows
through the shared infoMap, so it now also reaches Microsoft and
OnlyOffice, both of which already declare KeyCloseURL in their
SetProperties switch; existing full-struct-equality test
expectations are updated accordingly.
- DownloadURL is built by a new createFileDownloadURL helper pointing
at this service's own GET /wopi/files/{fileid}/contents route,
authenticated with a freshly generated WOPI access token. It shares
its "generate token + build hashed-resource-id path + attach
access_token query param" logic with the existing template
createDownloadURL via an extracted createWopiResourceURL helper,
rather than duplicating it. Population is defensive: if WOPI token
generation fails, DownloadURL is simply omitted (logged) instead of
failing the whole CheckFileInfo request, mirroring the existing
TemplateSource pattern.
HostViewURL, HostEditURL, FileSharingURL, and FileVersionURL needed no
fileconnector.go change - they were already unconditionally computed
into infoMap; only Collabora's struct lacked the fields to receive
them (added in the previous commit).
Adds integration test coverage for: CloseURL/BreadcrumbFolderURL
reuse, HostEditURL vs HostViewURL distinctness, the DownloadURL route
pattern, and graceful omission of DownloadURL when WOPI token
generation fails (via ShortTokens with no store configured) without
the overall request failing.
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): complete Breadcrumb properties in Collabora WOPI CheckFileInfo
Add the four Breadcrumb* fields to the Collabora fileinfo struct that were
already present on Microsoft/OnlyOffice (BreadcrumbFolderName/URL just
needed struct wiring; BreadcrumbBrandName/URL needed new logic in
CheckFileInfo, derived from the CS3 StorageSpace Name/Root).
BreadcrumbBrandURL is withheld for public-share (anonymous) access, mirroring
the existing precedent for parentFolderURL/CloseURL, since it would expose
an internal space/resource id an anonymous user has no other way to reach.
BreadcrumbBrandName is just a display string and stays populated either way.
Also guards the new brand URL construction against a nil space root so it
degrades to an empty string instead of a misleading bare ".../f" URL when
no space info is available.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(collaboration): cover BreadcrumbBrandURL when space has no root
Add a new test case to verify that when a StorageSpace has a Name but
a nil Root, the BreadcrumbBrandName is populated while BreadcrumbBrandURL
remains empty, ensuring the nil-guard doesn't allow malformed URLs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add PostMessage properties to Collabora WOPI CheckFileInfo
Add ClosePostMessage, EditModePostMessage, EditNotificationPostMessage,
FileSharingPostMessage, and FileVersionPostMessage to the Collabora
fileinfo struct with SetProperties wiring and godoc comments, mirroring
what OnlyOffice already exposes.
Only EditModePostMessage is actually populated in CheckFileInfo, gated
to the Collabora target: the oCIS web frontend's iframe wrapper
(web-app-external/src/App.vue, shared by all WOPI targets) only has a
real postMessage handler for UI_Edit today. The other four flags are
left unset so Collabora falls back to the corresponding URL properties
(HostEditURL/HostViewURL/FileSharingURL/FileVersionURL, added in an
earlier task) instead of firing PostMessages nobody listens for.
Because OnlyOffice's SetProperties already implements these same keys,
the Collabora-only behavior requires an explicit `if` gate on
f.cfg.App.Product rather than an unconditional map write, to avoid
silently changing OnlyOffice's current (all-absent) behavior.
Signed-off-by: David Walter <david.walter@kiteworks.com>
* feat(collaboration): add Collabora-specific IsUserLocked and IsAdminUser to WOPI CheckFileInfo
Add the two Collabora-only WOPI CheckFileInfo properties: IsUserLocked
(applies coolwsd.xml locked_commands to the user) and IsAdminUser
(unlocks Collabora's server audit panel).
- IsUserLocked is derived from the existing "secure view" condition
already used to gate DisableExport/DisableCopy/DisablePrint/
WatermarkText (ViewMode == VIEW_MODE_VIEW_ONLY with a non-empty
ViewOnlyToken), applied regardless of public-share vs authenticated
user.
- IsAdminUser is derived from the CS3 user's Opaque.Map["roles"] JSON
array (the same raw data ocis-pkg/middleware/account.go lifts into
go-micro metadata), compared against
defaults.BundleUUIDRoleAdmin only - SpaceAdmin and other roles do
not qualify.
Both keys have no matching SetProperties case in Microsoft/OnlyOffice,
so they are silently dropped for those targets, consistent with the
existing pattern for other Collabora-only properties in this
connector. No new config surface was added, matching the scoping
approach used throughout this properties-gap series.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(collaboration): remove redundant Collabora omitempty test duplicates
TestCollaboraVersionOmitEmpty and TestCollaboraLastModifiedTimeOmitEmpty
fully duplicated the coverage already provided by
TestCollaboraJSONMarshallingIncludesVersion and
TestCollaboraJSONMarshallingIncludesLastModifiedTime (both already assert
JSON inclusion/omission via a shouldInclude flag). Removing the duplicates
also drops an unused `expected` table field flagged by the final
whole-branch review.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* docs(changelog): add changelog entry for Collabora WOPI CheckFileInfo gaps
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(collaboration): fix checkFileInfo acceptance tests for ReadOnly derivation
Four "check file info with different mode(s)" scenario outlines
(onlyOffice/fakeOffice, personal space and project space) hardcoded
"ReadOnly": {"const": false} for every view mode. That was accurate only
because ReadOnly was never actually populated in CheckFileInfo before this
PR (it silently defaulted to Go's zero value). Now that ReadOnly correctly
reflects whether the WOPI session grants write access
(ReadOnly = ViewMode != READ_WRITE, the exact negation of UserCanWrite),
"view" and "read" mode responses correctly report ReadOnly: true, which
these tests now assert via a new "read-only" Examples column instead of a
static const.
The "check file info with different view-mode (collabora)" scenario is
unaffected: it never asserted ReadOnly in its schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* revert(collaboration): remove X-COOL-WOPI-Timestamp PutFile conflict detection
CI on owncloud/ocis PR #12593 reproducibly failed the e2e test
web/tests/e2e/specs/app-provider/officeSuites.spec.ts:17:3 "create an
OpenDocument file with Collabora" across two independent full CI runs,
timing out after 180s behind a Collabora "document conflict" popup that
intercepts all clicks.
Server logs showed the new PutFile conflict check firing on literally
the first save of a freshly-created document, with a consistent ~8s gap
between the client's cached X-COOL-WOPI-Timestamp and the server's
current mtime that never resolved across 6+ retries over 3+ minutes.
This is most likely oCIS's async post-processing (search indexing,
thumbnailing, etc.) legitimately bumping the file's mtime shortly after
creation - something this comparison can't distinguish from a real
edit conflict, and which Collabora's client doesn't recover from
automatically.
Revert just the conflict-detection portion of task 02: the
coolWopiTimestamp parameter on ContentConnectorService.PutFile / its
implementation, the X-COOL-WOPI-Timestamp header handling in
HttpAdapter.PutFile, and NewResponseCOOLConflict, plus their tests and
mock signatures. The LastModifiedTime WOPI property (and everything
else task 02 added) is untouched and remains fully intact.
🤖 Generated with Claude Code
Signed-off-by: David Walter <david.walter@kiteworks.com>
* test(web): add e2e Playwright coverage for WOPI CheckFileInfo properties
Adds dedicated Playwright specs, each covering both Collabora and
OnlyOffice, for the WOPI CheckFileInfo properties this PR added that
have a genuinely observable, already-proven-safe browser-level signal:
- wopiReadOnly.spec.ts: false for the file owner, true for a
view-only share recipient (via the existing canEditCollaboraDocument/
canEditOnlyOfficeDocument-backed userShouldNotBeAbleToEditContentOfResources
step).
- wopiSupportsUpdate.spec.ts: a new document created via the office
suite's own "New" menu (PutRelativeFile) persists its content across
a reopen.
- wopiVersionAndLastModifiedTime.spec.ts: an edit saved by one actor is
visible to a second actor opening the file afterward, rather than a
stale cached copy.
- wopiCloseUrl.spec.ts: closing the editor navigates the top-level page
back into the files app.
All four are built exclusively from step-level helpers already proven
reliable elsewhere in this suite (officeSuites.spec.ts, secureView.spec.ts)
- no new selectors into Collabora's or OnlyOffice's own iframe DOM.
Not added: dedicated e2e tests for BreadcrumbBrandName/Url,
EditModePostMessage, IsUserLocked, and IsAdminUser. CheckFileInfo is a
server-to-server call the office suite's own backend makes directly to
the collaboration service - it is never visible to the browser, so
Playwright cannot observe the JSON response for any property. These
four also have no independently observable browser effect without
either inventing unverified selectors into third-party iframe internals
(breadcrumb bar, edit-mode button) or Collabora-side configuration
(locked_commands, audit panel) this test environment doesn't set up.
They remain correctly covered by the Go unit/integration tests
(fileinfo/collabora_test.go, fileconnector_test.go) and the
tests/acceptance/features/apiCollaboration/checkFileInfo.feature
acceptance suite, which assert the actual JSON response server-side.
An initial IsAnonymousUser spec was written and dropped after review:
it only asserted that content loads, which IsAnonymousUser does not
gate, making it indistinguishable from a regression (already covered
correctly at the Go level in fileconnector_test.go).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(web): reformat wopiVersionAndLastModifiedTime.spec.ts for prettier
pnpm check:format (web-build-test CI job) flagged the test.describe()
call: its 3 arguments (title, options, callback) didn't fit within
this repo's printWidth 100 on one line, so prettier expects them
wrapped one per line. Reformatted to match and reindented the
describe block body accordingly. No logic change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
* fix(collaboration): address PR #12593 review findings
- Restructure the VIEW_ONLY watermark check to nest inside the
ViewOnlyToken check instead of repeating the condition, per
jvillafanez's review comment.
- Add a reva token mint/dismantle round-trip test covering the admin
role Opaque entry, exercising the real JWT serialization seam
IsAdminUser depends on rather than only direct context injection,
per DeepDiver1975's review.
- Correct the changelog entry: ReadOnly/CloseUrl (and DownloadUrl for
Microsoft) already had matching `case` handling in microsoft.go/
onlyoffice.go, so populating them unconditionally changes those
targets' output too, not just Collabora's. Only IsUserLocked/
IsAdminUser are genuinely Collabora-only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: David Walter <david.walter@kiteworks.com>
---------
Signed-off-by: David Walter <david.walter@kiteworks.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Juan Pablo Villafañez <jpvillafanez@izertis.com>