fix(collaboration): [OCISDEV-781] return 200 OK for WOPI Lock in read-only modes

OnlyOffice sends a WOPI Lock request on document open regardless of whether
the user has write access. The Lock handler was calling SetLock with a
read-only CS3 token, which returned a permission error propagated as HTTP 500
to OnlyOffice, causing an error dialog on load.

Return 200 OK immediately for READ_ONLY and VIEW_ONLY view modes without
acquiring a CS3 lock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Julian Koberg <julian.koberg@kiteworks.com>
This commit is contained in:
Julian Koberg
2026-04-23 11:59:14 +02:00
parent dacca91f5c
commit 8e8041ac8b
3 changed files with 44 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
Bugfix: Return 200 OK for WOPI Lock requests in read-only and view-only modes
OnlyOffice sends a WOPI Lock request when opening any document, even when
the user only has read access. The WOPI Lock handler was attempting to acquire
a CS3 write lock regardless of the view mode, causing a permission error for
read-only tokens that OnlyOffice displayed as an error message on load.
The Lock handler now returns 200 OK immediately for READ_ONLY and VIEW_ONLY
view modes without attempting to acquire a lock, consistent with the WOPI spec.
https://github.com/owncloud/ocis/pull/12257

View File

@@ -212,6 +212,16 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*Co
return NewResponse(400), nil
}
// For read-only and view-only modes, the user has no write access.
// OnlyOffice sends a Lock request even for view-only documents, but
// attempting SetLock with a read-only token would fail at the CS3 layer.
// Return 200 OK immediately without acquiring a lock.
if wopiContext.ViewMode == appproviderv1beta1.ViewMode_VIEW_MODE_READ_ONLY ||
wopiContext.ViewMode == appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY {
logger.Debug().Msg("Lock: view-only mode, skipping lock")
return NewResponseWithVersion(nil), nil
}
var setOrRefreshStatus *rpcv1beta1.Status
if oldLockID == "" {
// If the oldLockID is empty, this is a "LOCK" request

View File

@@ -178,6 +178,28 @@ var _ = Describe("FileConnector", func() {
Expect(response.Headers).To(BeNil())
})
It("Read-only view mode returns 200 without locking", func() {
gatewaySelector.EXPECT().Next().Unset()
readOnlyCtx := wopiCtx
readOnlyCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_READ_ONLY
ctx := middleware.WopiContextToCtx(context.Background(), readOnlyCtx)
response, err := fc.Lock(ctx, "abcdef123", "")
Expect(err).ToNot(HaveOccurred())
Expect(response.Status).To(Equal(200))
})
It("View-only mode returns 200 without locking", func() {
gatewaySelector.EXPECT().Next().Unset()
viewOnlyCtx := wopiCtx
viewOnlyCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY
ctx := middleware.WopiContextToCtx(context.Background(), viewOnlyCtx)
response, err := fc.Lock(ctx, "abcdef123", "")
Expect(err).ToNot(HaveOccurred())
Expect(response.Status).To(Equal(200))
})
It("Set lock failed", func() {
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
@@ -2166,5 +2188,6 @@ var _ = Describe("FileConnector", func() {
// so we can't compare the whole url
Expect(templateSource).To(HavePrefix(expectedTemplateSource))
})
})
})