From 08cb22850004d02aa0da2e2b4e8da4dd8b6db326 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Wed, 24 Jul 2024 21:02:54 +0200 Subject: [PATCH] feat: add new locks parser for microsoft office online server --- services/collaboration/pkg/config/app.go | 2 +- .../pkg/connector/httpadapter.go | 29 +++++--- services/collaboration/pkg/locks/parser.go | 72 +++++++++++++++++++ .../collaboration/pkg/server/http/server.go | 3 +- 4 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 services/collaboration/pkg/locks/parser.go diff --git a/services/collaboration/pkg/config/app.go b/services/collaboration/pkg/config/app.go index f54d058f6..e255f21e8 100644 --- a/services/collaboration/pkg/config/app.go +++ b/services/collaboration/pkg/config/app.go @@ -2,7 +2,7 @@ package config // App defines the available app configuration. type App struct { - Name string `yaml:"name" env:"COLLABORATION_APP_NAME" desc:"The name of the app, either Collabora, OnlyOffice or Microsoft365" introductionVersion:"6.0.0"` + Name string `yaml:"name" env:"COLLABORATION_APP_NAME" desc:"The name of the app, either Collabora, OnlyOffice, Microsoft365 or MicrosoftOfficeOnline" introductionVersion:"6.0.0"` Description string `yaml:"description" env:"COLLABORATION_APP_DESCRIPTION" desc:"App description" introductionVersion:"6.0.0"` Icon string `yaml:"icon" env:"COLLABORATION_APP_ICON" desc:"Icon for the app" introductionVersion:"6.0.0"` LockName string `yaml:"lockname" env:"COLLABORATION_APP_LOCKNAME" desc:"Name for the app lock" introductionVersion:"6.0.0"` diff --git a/services/collaboration/pkg/connector/httpadapter.go b/services/collaboration/pkg/connector/httpadapter.go index 4075722b4..5e7fb809d 100644 --- a/services/collaboration/pkg/connector/httpadapter.go +++ b/services/collaboration/pkg/connector/httpadapter.go @@ -5,9 +5,11 @@ import ( "errors" "net/http" "strconv" + "strings" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" "github.com/owncloud/ocis/v2/services/collaboration/pkg/config" + "github.com/owncloud/ocis/v2/services/collaboration/pkg/locks" "github.com/rs/zerolog" ) @@ -25,25 +27,34 @@ const ( // All operations are expected to follow the definitions found in // https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/endpoints type HttpAdapter struct { - con ConnectorService + con ConnectorService + config *config.Config + locks locks.LockParser } // NewHttpAdapter will create a new HTTP adapter. A new connector using the // provided gateway API client and configuration will be used in the adapter func NewHttpAdapter(gwc gatewayv1beta1.GatewayAPIClient, cfg *config.Config) *HttpAdapter { - return &HttpAdapter{ + httpAdapter := &HttpAdapter{ con: NewConnector( NewFileConnector(gwc, cfg), NewContentConnector(gwc, cfg), ), } + + httpAdapter.locks = &locks.NoopLockParser{} + if strings.ToLower(cfg.App.Name) == "microsoftofficeonline" { + httpAdapter.locks = &locks.LegacyLockParser{} + } + return httpAdapter } // NewHttpAdapterWithConnector will create a new HTTP adapter that will use // the provided connector service -func NewHttpAdapterWithConnector(con ConnectorService) *HttpAdapter { +func NewHttpAdapterWithConnector(con ConnectorService, l locks.LockParser) *HttpAdapter { return &HttpAdapter{ - con: con, + con: con, + locks: l, } } @@ -75,8 +86,8 @@ func (h *HttpAdapter) GetLock(w http.ResponseWriter, r *http.Request) { // The operation's response will be sent through the response writer and // the headers according to the spec func (h *HttpAdapter) Lock(w http.ResponseWriter, r *http.Request) { - oldLockID := r.Header.Get(HeaderWopiOldLock) - lockID := r.Header.Get(HeaderWopiLock) + oldLockID := h.locks.ParseLock(r.Header.Get(HeaderWopiOldLock)) + lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock)) fileCon := h.con.GetFileConnector() newLockID, err := fileCon.Lock(r.Context(), lockID, oldLockID) @@ -103,7 +114,7 @@ func (h *HttpAdapter) Lock(w http.ResponseWriter, r *http.Request) { // The operation's response will be sent through the response writer and // the headers according to the spec func (h *HttpAdapter) RefreshLock(w http.ResponseWriter, r *http.Request) { - lockID := r.Header.Get(HeaderWopiLock) + lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock)) fileCon := h.con.GetFileConnector() newLockID, err := fileCon.RefreshLock(r.Context(), lockID) @@ -128,7 +139,7 @@ func (h *HttpAdapter) RefreshLock(w http.ResponseWriter, r *http.Request) { // The operation's response will be sent through the response writer and // the headers according to the spec func (h *HttpAdapter) UnLock(w http.ResponseWriter, r *http.Request) { - lockID := r.Header.Get(HeaderWopiLock) + lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock)) fileCon := h.con.GetFileConnector() newLockID, err := fileCon.UnLock(r.Context(), lockID) @@ -211,7 +222,7 @@ func (h *HttpAdapter) GetFile(w http.ResponseWriter, r *http.Request) { // The operation's response will be sent through the response writer and // the headers according to the spec func (h *HttpAdapter) PutFile(w http.ResponseWriter, r *http.Request) { - lockID := r.Header.Get(HeaderWopiLock) + lockID := h.locks.ParseLock(r.Header.Get(HeaderWopiLock)) contentCon := h.con.GetContentConnector() newLockID, err := contentCon.PutFile(r.Context(), r.Body, r.ContentLength, lockID) diff --git a/services/collaboration/pkg/locks/parser.go b/services/collaboration/pkg/locks/parser.go new file mode 100644 index 000000000..022e59adf --- /dev/null +++ b/services/collaboration/pkg/locks/parser.go @@ -0,0 +1,72 @@ +// Package locks provides functionality to parse lockIDs. +// +// It can be used to bridge requests from different clients that send lockIDs in different formats. +// For example, Microsoft Office Online sends the lockID in a JSON string, +// while other clients send the lockID as a plain string. +package locks + +import ( + "encoding/json" + "strings" +) + +// LockParser is the interface that wraps the ParseLock method +type LockParser interface { + ParseLock(id string) string +} + +// LegacyLockParser is a lock parser that can extract the lockID from a JSON string +type LegacyLockParser struct{} + +// NoopLockParser is a lock parser that does not change the lockID +type NoopLockParser struct{} + +// ParseLock will return the lockID as is +func (*NoopLockParser) ParseLock(id string) string { + return id +} + +// ParseLock extracts the lockID from a JSON string. +// For Microsoft Office Online we need to extract the lockID from the JSON string +// that is sent by the WOPI client. +// The JSON string is expected to have the following format: +// +// { +// "L": "12345678", +// "F": 4, +// "E": 2, +// "C": "", +// "P": "3453345345346", +// "M": "12345678" +// } +// +// or +// +// { +// "S": "12345678", +// "F": 4, +// "E": 2, +// "C": "", +// "P": "3453345345346", +// "M": "12345678" +// } +// +// If the JSON string is not in the expected format, the original lockID will be returned. +func (*LegacyLockParser) ParseLock(id string) string { + var decodedValues map[string]interface{} + err := json.NewDecoder(strings.NewReader(id)).Decode(&decodedValues) + if err != nil || len(decodedValues) == 0 { + return id + } + if v, ok := decodedValues["L"]; ok { + if idString, ok := v.(string); ok { + return idString + } + } + if v, ok := decodedValues["S"]; ok { + if idString, ok := v.(string); ok { + return idString + } + } + return id +} diff --git a/services/collaboration/pkg/server/http/server.go b/services/collaboration/pkg/server/http/server.go index 5eab32766..6218d0383 100644 --- a/services/collaboration/pkg/server/http/server.go +++ b/services/collaboration/pkg/server/http/server.go @@ -118,8 +118,7 @@ func prepareRoutes(r *chi.Mux, options Options) { r.Use(func(h stdhttp.Handler) stdhttp.Handler { // authentication and wopi context return colabmiddleware.WopiContextAuthMiddleware(options.Config.Wopi.Secret, h) - }, - ) + }) r.Get("/", func(w stdhttp.ResponseWriter, r *stdhttp.Request) { adapter.CheckFileInfo(w, r)