diff --git a/changelog/unreleased/fix-wopi-lock-read-only-view-mode.md b/changelog/unreleased/fix-wopi-lock-read-only-view-mode.md new file mode 100644 index 00000000000..2442f03e21f --- /dev/null +++ b/changelog/unreleased/fix-wopi-lock-read-only-view-mode.md @@ -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 diff --git a/services/collaboration/pkg/connector/fileconnector.go b/services/collaboration/pkg/connector/fileconnector.go index ddef048147f..a9915588abd 100644 --- a/services/collaboration/pkg/connector/fileconnector.go +++ b/services/collaboration/pkg/connector/fileconnector.go @@ -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 diff --git a/services/collaboration/pkg/connector/fileconnector_test.go b/services/collaboration/pkg/connector/fileconnector_test.go index 8096856a06b..ca49d5f9514 100644 --- a/services/collaboration/pkg/connector/fileconnector_test.go +++ b/services/collaboration/pkg/connector/fileconnector_test.go @@ -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)) }) + }) })