feat: office 365 proxy support
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
|||||||
|
|
||||||
connector "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector"
|
connector "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector"
|
||||||
|
|
||||||
|
http "net/http"
|
||||||
|
|
||||||
io "io"
|
io "io"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
@@ -25,17 +27,17 @@ func (_m *ContentConnectorService) EXPECT() *ContentConnectorService_Expecter {
|
|||||||
return &ContentConnectorService_Expecter{mock: &_m.Mock}
|
return &ContentConnectorService_Expecter{mock: &_m.Mock}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile provides a mock function with given fields: ctx, writer
|
// GetFile provides a mock function with given fields: ctx, w
|
||||||
func (_m *ContentConnectorService) GetFile(ctx context.Context, writer io.Writer) error {
|
func (_m *ContentConnectorService) GetFile(ctx context.Context, w http.ResponseWriter) error {
|
||||||
ret := _m.Called(ctx, writer)
|
ret := _m.Called(ctx, w)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for GetFile")
|
panic("no return value specified for GetFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, io.Writer) error); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, http.ResponseWriter) error); ok {
|
||||||
r0 = rf(ctx, writer)
|
r0 = rf(ctx, w)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@@ -50,14 +52,14 @@ type ContentConnectorService_GetFile_Call struct {
|
|||||||
|
|
||||||
// GetFile is a helper method to define mock.On call
|
// GetFile is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx context.Context
|
||||||
// - writer io.Writer
|
// - w http.ResponseWriter
|
||||||
func (_e *ContentConnectorService_Expecter) GetFile(ctx interface{}, writer interface{}) *ContentConnectorService_GetFile_Call {
|
func (_e *ContentConnectorService_Expecter) GetFile(ctx interface{}, w interface{}) *ContentConnectorService_GetFile_Call {
|
||||||
return &ContentConnectorService_GetFile_Call{Call: _e.mock.On("GetFile", ctx, writer)}
|
return &ContentConnectorService_GetFile_Call{Call: _e.mock.On("GetFile", ctx, w)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ContentConnectorService_GetFile_Call) Run(run func(ctx context.Context, writer io.Writer)) *ContentConnectorService_GetFile_Call {
|
func (_c *ContentConnectorService_GetFile_Call) Run(run func(ctx context.Context, w http.ResponseWriter)) *ContentConnectorService_GetFile_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(io.Writer))
|
run(args[0].(context.Context), args[1].(http.ResponseWriter))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,7 @@ func (_c *ContentConnectorService_GetFile_Call) Return(_a0 error) *ContentConnec
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *ContentConnectorService_GetFile_Call) RunAndReturn(run func(context.Context, io.Writer) error) *ContentConnectorService_GetFile_Call {
|
func (_c *ContentConnectorService_GetFile_Call) RunAndReturn(run func(context.Context, http.ResponseWriter) error) *ContentConnectorService_GetFile_Call {
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ type App struct {
|
|||||||
Addr string `yaml:"addr" env:"COLLABORATION_APP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"6.0.0"`
|
Addr string `yaml:"addr" env:"COLLABORATION_APP_ADDR" desc:"The URL where the WOPI app is located, such as https://127.0.0.1:8080." introductionVersion:"6.0.0"`
|
||||||
Insecure bool `yaml:"insecure" env:"COLLABORATION_APP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"6.0.0"`
|
Insecure bool `yaml:"insecure" env:"COLLABORATION_APP_INSECURE" desc:"Skip TLS certificate verification when connecting to the WOPI app" introductionVersion:"6.0.0"`
|
||||||
|
|
||||||
ProofKeys ProofKeys `yaml:"proofkeys"`
|
ProofKeys ProofKeys `yaml:"proofkeys"`
|
||||||
|
LicenseCheckEnable bool `yaml:"licensecheckenable" env:"COLLABORATION_APP_LICENSE_CHECK_ENABLE" desc:"Enable license check for edit" introductionVersion:"%%NEXT%%"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProofKeys struct {
|
type ProofKeys struct {
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ type Wopi struct {
|
|||||||
WopiSrc string `yaml:"wopisrc" env:"COLLABORATION_WOPI_SRC" desc:"The WOPISrc base URL containing schema, host and port. Set this to the schema and domain where the collaboration service is reachable for the wopi app, such as https://office.owncloud.test." introductionVersion:"6.0.0"`
|
WopiSrc string `yaml:"wopisrc" env:"COLLABORATION_WOPI_SRC" desc:"The WOPISrc base URL containing schema, host and port. Set this to the schema and domain where the collaboration service is reachable for the wopi app, such as https://office.owncloud.test." introductionVersion:"6.0.0"`
|
||||||
Secret string `yaml:"secret" env:"COLLABORATION_WOPI_SECRET" desc:"Used to mint and verify WOPI JWT tokens and encrypt and decrypt the REVA JWT token embedded in the WOPI JWT token." introductionVersion:"6.0.0"`
|
Secret string `yaml:"secret" env:"COLLABORATION_WOPI_SECRET" desc:"Used to mint and verify WOPI JWT tokens and encrypt and decrypt the REVA JWT token embedded in the WOPI JWT token." introductionVersion:"6.0.0"`
|
||||||
DisableChat bool `yaml:"disable_chat" env:"COLLABORATION_WOPI_DISABLE_CHAT;OCIS_WOPI_DISABLE_CHAT" desc:"Disable chat in the frontend." introductionVersion:"%%NEXT%%"`
|
DisableChat bool `yaml:"disable_chat" env:"COLLABORATION_WOPI_DISABLE_CHAT;OCIS_WOPI_DISABLE_CHAT" desc:"Disable chat in the frontend." introductionVersion:"%%NEXT%%"`
|
||||||
|
ProxyURL string `yaml:"proxy_url" env:"COLLABORATION_WOPI_PROXY_URL" desc:"The URL to the ownCloud Office365 WOPI proxy." introductionVersion:"%%NEXT%%"`
|
||||||
|
ProxySecret string `yaml:"proxy_secret" env:"COLLABORATION_WOPI_PROXY_SECRET" desc:"The secret to authenticate against the ownCloud Office365 WOPI proxy." introductionVersion:"%%NEXT%%"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package connector
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
||||||
|
)
|
||||||
|
|
||||||
// ConnectorResponse represent a response from the FileConnectorService.
|
// ConnectorResponse represent a response from the FileConnectorService.
|
||||||
// The ConnectorResponse is oriented to HTTP, so it has the Status, Headers
|
// The ConnectorResponse is oriented to HTTP, so it has the Status, Headers
|
||||||
// and Body that the actual HTTP response should have. This includes HTTP
|
// and Body that the actual HTTP response should have. This includes HTTP
|
||||||
@@ -33,6 +38,61 @@ func NewResponseWithLock(status int, lockID string) *ConnectorResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewResponseLockConflict creates a new ConnectorResponse with the status 409
|
||||||
|
// and the "X-WOPI-Lock" header having the value in the lockID parameter.
|
||||||
|
//
|
||||||
|
// This is used for conflict responses where the current lock id needs
|
||||||
|
// to be returned, although the `GetLock` method also uses this method for a
|
||||||
|
// successful response (with the lock id included)
|
||||||
|
// The lockFailureReason parameter will be included in the "X-WOPI-LockFailureReason".
|
||||||
|
func NewResponseLockConflict(lockID string, lockFailureReason string) *ConnectorResponse {
|
||||||
|
return &ConnectorResponse{
|
||||||
|
Status: 409,
|
||||||
|
Headers: map[string]string{
|
||||||
|
HeaderWopiLock: lockID,
|
||||||
|
HeaderWopiLockFailureReason: lockFailureReason,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponseWithVersion creates a new ConnectorResponse with the specified status
|
||||||
|
// and the "X-WOPI-ItemVersion" header having the value in the mtime parameter.
|
||||||
|
func NewResponseWithVersion(status int, mtime *types.Timestamp) *ConnectorResponse {
|
||||||
|
return &ConnectorResponse{
|
||||||
|
Status: status,
|
||||||
|
Headers: map[string]string{
|
||||||
|
HeaderWopiVersion: helpers.GetVersion(mtime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponseConflictWithVersion creates a new ConnectorResponse with the status 409
|
||||||
|
// and the "X-WOPI-ItemVersion" header having the value in the mtime parameter.
|
||||||
|
// The lockFailureReason parameter will be included in the "X-WOPI-LockFailureReason".
|
||||||
|
func NewResponseConflictWithVersion(mtime *types.Timestamp, lockFailureReason string) *ConnectorResponse {
|
||||||
|
return &ConnectorResponse{
|
||||||
|
Status: 409,
|
||||||
|
Headers: map[string]string{
|
||||||
|
HeaderWopiVersion: helpers.GetVersion(mtime),
|
||||||
|
HeaderWopiLockFailureReason: lockFailureReason,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResponseWithVersionAndLock creates a new ConnectorResponse with the specified status
|
||||||
|
// and the "X-WOPI-ItemVersion" header and the "X-WOPI-Lock" header
|
||||||
|
// having the values in the mtime and lockID parameters.
|
||||||
|
func NewResponseWithVersionAndLock(status int, mtime *types.Timestamp, lockID string) *ConnectorResponse {
|
||||||
|
r := &ConnectorResponse{
|
||||||
|
Status: status,
|
||||||
|
Headers: map[string]string{
|
||||||
|
HeaderWopiVersion: helpers.GetVersion(mtime),
|
||||||
|
HeaderWopiLock: lockID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// NewResponseSuccessBody creates a new ConnectorResponse with a fixed 200
|
// NewResponseSuccessBody creates a new ConnectorResponse with a fixed 200
|
||||||
// (success) status and the specified body. The headers will be nil.
|
// (success) status and the specified body. The headers will be nil.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
@@ -29,7 +30,7 @@ import (
|
|||||||
// Target file is within the WOPI context
|
// Target file is within the WOPI context
|
||||||
type ContentConnectorService interface {
|
type ContentConnectorService interface {
|
||||||
// GetFile downloads the file and write its contents in the provider writer
|
// GetFile downloads the file and write its contents in the provider writer
|
||||||
GetFile(ctx context.Context, writer io.Writer) error
|
GetFile(ctx context.Context, w http.ResponseWriter) error
|
||||||
// PutFile uploads the stream up to the stream length. The file should be
|
// PutFile uploads the stream up to the stream length. The file should be
|
||||||
// locked beforehand, so the lockID needs to be provided.
|
// locked beforehand, so the lockID needs to be provided.
|
||||||
// The current lockID will be returned ONLY if a conflict happens (the file is
|
// The current lockID will be returned ONLY if a conflict happens (the file is
|
||||||
@@ -61,9 +62,11 @@ func NewContentConnector(gwc gatewayv1beta1.GatewayAPIClient, cfg *config.Config
|
|||||||
// You can pass a pre-configured zerologger instance through the context that
|
// You can pass a pre-configured zerologger instance through the context that
|
||||||
// will be used to log messages.
|
// will be used to log messages.
|
||||||
//
|
//
|
||||||
// The contents of the file will be written directly into the writer passed as
|
// The contents of the file will be written directly into the http Response writer passed as
|
||||||
// parameter.
|
// parameter.
|
||||||
func (c *ContentConnector) GetFile(ctx context.Context, writer io.Writer) error {
|
// Be aware that the body of the response will be written during the execution of this method.
|
||||||
|
// Any further modifications to the response headers or body will be ignored.
|
||||||
|
func (c *ContentConnector) GetFile(ctx context.Context, w http.ResponseWriter) error {
|
||||||
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -74,6 +77,16 @@ func (c *ContentConnector) GetFile(ctx context.Context, writer io.Writer) error
|
|||||||
Logger()
|
Logger()
|
||||||
logger.Debug().Msg("GetFile: start")
|
logger.Debug().Msg("GetFile: start")
|
||||||
|
|
||||||
|
sResp, err := c.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||||
|
Ref: wopiContext.FileReference,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("GetFile: Stat Request failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sResp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK {
|
||||||
|
return NewConnectorError(500, sResp.GetStatus().GetCode().String()+" "+sResp.GetStatus().GetMessage())
|
||||||
|
}
|
||||||
// Initiate download request
|
// Initiate download request
|
||||||
req := &providerv1beta1.InitiateFileDownloadRequest{
|
req := &providerv1beta1.InitiateFileDownloadRequest{
|
||||||
Ref: wopiContext.FileReference,
|
Ref: wopiContext.FileReference,
|
||||||
@@ -168,13 +181,14 @@ func (c *ContentConnector) GetFile(ctx context.Context, writer io.Writer) error
|
|||||||
return NewConnectorError(500, "GetFile: Downloading the file failed")
|
return NewConnectorError(500, "GetFile: Downloading the file failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
helpers.SetVersionHeader(w, sResp.GetInfo().GetMtime())
|
||||||
|
|
||||||
// Copy the download into the writer
|
// Copy the download into the writer
|
||||||
_, err = io.Copy(writer, httpResp.Body)
|
_, err = io.Copy(w, httpResp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Msg("GetFile: copying the file content to the response body failed")
|
logger.Error().Msg("GetFile: copying the file content to the response body failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug().Msg("GetFile: success")
|
logger.Debug().Msg("GetFile: success")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -199,6 +213,8 @@ func (c *ContentConnector) GetFile(ctx context.Context, writer io.Writer) error
|
|||||||
// lock ID that should be used in the X-WOPI-Lock header. In other error
|
// lock ID that should be used in the X-WOPI-Lock header. In other error
|
||||||
// cases or if the method is successful, an empty string will be returned
|
// cases or if the method is successful, an empty string will be returned
|
||||||
// (check for err != nil to know if something went wrong)
|
// (check for err != nil to know if something went wrong)
|
||||||
|
//
|
||||||
|
// On success, the method will return the new mtime of the file
|
||||||
func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, streamLength int64, lockID string) (*ConnectorResponse, error) {
|
func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, streamLength int64, lockID string) (*ConnectorResponse, error) {
|
||||||
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -230,13 +246,14 @@ func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, stream
|
|||||||
return NewResponse(500), nil
|
return NewResponse(500), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtime := statRes.GetInfo().GetMtime()
|
||||||
// If there is a lock and it mismatches, return 409
|
// If there is a lock and it mismatches, return 409
|
||||||
if statRes.GetInfo().GetLock() != nil && statRes.GetInfo().GetLock().GetLockId() != lockID {
|
if statRes.GetInfo().GetLock() != nil && statRes.GetInfo().GetLock().GetLockId() != lockID {
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Str("LockID", statRes.GetInfo().GetLock().GetLockId()).
|
Str("LockID", statRes.GetInfo().GetLock().GetLockId()).
|
||||||
Msg("PutFile: wrong lock")
|
Msg("PutFile: wrong lock")
|
||||||
// onlyoffice says it's required to send the current lockId, MS doesn't say anything
|
// onlyoffice says it's required to send the current lockId, MS doesn't say anything
|
||||||
return NewResponseWithLock(409, statRes.GetInfo().GetLock().GetLockId()), nil
|
return NewResponseLockConflict(statRes.GetInfo().GetLock().GetLockId(), "Lock Mismatch"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// only unlocked uploads can go through if the target file is empty,
|
// only unlocked uploads can go through if the target file is empty,
|
||||||
@@ -246,7 +263,7 @@ func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, stream
|
|||||||
if lockID == "" && statRes.GetInfo().GetLock() == nil && statRes.GetInfo().GetSize() > 0 {
|
if lockID == "" && statRes.GetInfo().GetLock() == nil && statRes.GetInfo().GetSize() > 0 {
|
||||||
logger.Error().Msg("PutFile: file must be locked first")
|
logger.Error().Msg("PutFile: file must be locked first")
|
||||||
// onlyoffice says to send an empty string if the file is unlocked, MS doesn't say anything
|
// onlyoffice says to send an empty string if the file is unlocked, MS doesn't say anything
|
||||||
return NewResponseWithLock(409, ""), nil
|
return NewResponseLockConflict("", "Cannot PutFile on unlocked file"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the data to initiate the upload
|
// Prepare the data to initiate the upload
|
||||||
@@ -367,8 +384,25 @@ func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, stream
|
|||||||
Msg("UploadHelper: Put request to the upload endpoint failed with unexpected status")
|
Msg("UploadHelper: Put request to the upload endpoint failed with unexpected status")
|
||||||
return NewResponse(500), nil
|
return NewResponse(500), nil
|
||||||
}
|
}
|
||||||
|
// We need a stat call on the target file after the upload to get the
|
||||||
|
// new mtime
|
||||||
|
statResAfter, err := c.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||||
|
Ref: wopiContext.FileReference,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("PutFile: stat after upload failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statResAfter.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK {
|
||||||
|
logger.Error().
|
||||||
|
Str("StatusCode", statRes.GetStatus().GetCode().String()).
|
||||||
|
Str("StatusMsg", statRes.GetStatus().GetMessage()).
|
||||||
|
Msg("PutFile: stat after upload failed with unexpected status")
|
||||||
|
return NewResponse(500), nil
|
||||||
|
}
|
||||||
|
mtime = statResAfter.GetInfo().GetMtime()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug().Msg("PutFile: success")
|
logger.Debug().Msg("PutFile: success")
|
||||||
return NewResponse(200), nil
|
return NewResponseWithVersion(200, mtime), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cs3org/reva/v2/pkg/utils"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||||
@@ -52,7 +53,6 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
},
|
},
|
||||||
Path: ".",
|
Path: ".",
|
||||||
},
|
},
|
||||||
User: &userv1beta1.User{}, // Not used for now
|
|
||||||
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,15 +77,28 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Describe("GetFile", func() {
|
Describe("GetFile", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(context.Background()),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "abc",
|
||||||
|
OpaqueId: "12345",
|
||||||
|
SpaceId: "zzz",
|
||||||
|
},
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
})
|
||||||
It("No valid context", func() {
|
It("No valid context", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err := cc.GetFile(ctx, sb)
|
err := cc.GetFile(ctx, sb)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Initiate download failed", func() {
|
It("Initiate download failed", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
targetErr := errors.New("Something went wrong")
|
targetErr := errors.New("Something went wrong")
|
||||||
@@ -98,7 +111,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Initiate download status not ok", func() {
|
It("Initiate download status not ok", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
||||||
@@ -112,7 +125,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Missing download endpoint", func() {
|
It("Missing download endpoint", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
||||||
@@ -126,7 +139,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Download request failed", func() {
|
It("Download request failed", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
||||||
@@ -149,7 +162,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Download request success", func() {
|
It("Download request success", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
gatewayClient.On("InitiateFileDownload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileDownloadResponse{
|
||||||
@@ -167,11 +180,11 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
||||||
Expect(srvReqHeader.Get("X-Reva-Transfer")).To(Equal("MyDownloadToken"))
|
Expect(srvReqHeader.Get("X-Reva-Transfer")).To(Equal("MyDownloadToken"))
|
||||||
Expect(err).To(Succeed())
|
Expect(err).To(Succeed())
|
||||||
Expect(sb.String()).To(Equal(randomContent))
|
Expect(sb.Body.String()).To(Equal(randomContent))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("ViewOnlyMode Download request success", func() {
|
It("ViewOnlyMode Download request success", func() {
|
||||||
sb := &strings.Builder{}
|
sb := httptest.NewRecorder()
|
||||||
|
|
||||||
wopiCtx = middleware.WopiContext{
|
wopiCtx = middleware.WopiContext{
|
||||||
AccessToken: "abcdef123456",
|
AccessToken: "abcdef123456",
|
||||||
@@ -184,7 +197,6 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
},
|
},
|
||||||
Path: ".",
|
Path: ".",
|
||||||
},
|
},
|
||||||
User: &userv1beta1.User{}, // Not used for now
|
|
||||||
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY,
|
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +220,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.ViewOnlyToken))
|
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.ViewOnlyToken))
|
||||||
Expect(srvReqHeader.Get("X-Reva-Transfer")).To(Equal("MyDownloadToken"))
|
Expect(srvReqHeader.Get("X-Reva-Transfer")).To(Equal("MyDownloadToken"))
|
||||||
Expect(err).To(Succeed())
|
Expect(err).To(Succeed())
|
||||||
Expect(sb.String()).To(Equal(randomContent))
|
Expect(sb.Body.String()).To(Equal(randomContent))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -244,7 +256,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "notARandomLockId")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "notARandomLockId")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -264,9 +276,11 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "notARandomLockId")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "notARandomLockId")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
|
Expect(response.Headers).To(HaveLen(2))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("goodAndValidLock"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("goodAndValidLock"))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiLockFailureReason]).To(Equal("Lock Mismatch"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Upload without lockId but on a non empty file", func() {
|
It("Upload without lockId but on a non empty file", func() {
|
||||||
@@ -282,7 +296,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
||||||
})
|
})
|
||||||
@@ -332,7 +346,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -348,7 +362,8 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
LockId: "goodAndValidLock",
|
LockId: "goodAndValidLock",
|
||||||
Type: providerv1beta1.LockType_LOCK_TYPE_WRITE,
|
Type: providerv1beta1.LockType_LOCK_TYPE_WRITE,
|
||||||
},
|
},
|
||||||
Size: uint64(123456789),
|
Size: uint64(123456789),
|
||||||
|
Mtime: utils.TimeToTS(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
@@ -357,9 +372,10 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v16094592000"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Missing upload endpoint", func() {
|
It("Missing upload endpoint", func() {
|
||||||
@@ -382,7 +398,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
||||||
Expect(err).To(BeNil())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -414,7 +430,7 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
||||||
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -434,6 +450,23 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Lock: &providerv1beta1.Lock{
|
||||||
|
LockId: "goodAndValidLock",
|
||||||
|
Type: providerv1beta1.LockType_LOCK_TYPE_WRITE,
|
||||||
|
},
|
||||||
|
Size: uint64(123456789),
|
||||||
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageID",
|
||||||
|
OpaqueId: "opaqueID",
|
||||||
|
SpaceId: "spaceID",
|
||||||
|
},
|
||||||
|
Mtime: utils.TimeToTS(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
gatewayClient.On("InitiateFileUpload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileUploadResponse{
|
gatewayClient.On("InitiateFileUpload", mock.Anything, mock.Anything).Times(1).Return(&gateway.InitiateFileUploadResponse{
|
||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
Protocols: []*gateway.FileUploadProtocol{
|
Protocols: []*gateway.FileUploadProtocol{
|
||||||
@@ -446,9 +479,10 @@ var _ = Describe("ContentConnector", func() {
|
|||||||
|
|
||||||
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
response, err := cc.PutFile(ctx, reader, reader.Size(), "goodAndValidLock")
|
||||||
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
Expect(srvReqHeader.Get("X-Access-Token")).To(Equal(wopiCtx.AccessToken))
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v16094592000"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,12 +18,15 @@ import (
|
|||||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
|
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||||
"github.com/cs3org/reva/v2/pkg/utils"
|
"github.com/cs3org/reva/v2/pkg/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/connector/fileinfo"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/connector/fileinfo"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/wopisrc"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ type FileConnectorService interface {
|
|||||||
// needs to be provided.
|
// needs to be provided.
|
||||||
// The current lockID will be returned if a conflict happens
|
// The current lockID will be returned if a conflict happens
|
||||||
RefreshLock(ctx context.Context, lockID string) (*ConnectorResponse, error)
|
RefreshLock(ctx context.Context, lockID string) (*ConnectorResponse, error)
|
||||||
// Unlock will unlock the target file. The current lockID needs to be
|
// UnLock will unlock the target file. The current lockID needs to be
|
||||||
// provided.
|
// provided.
|
||||||
// The current lockID will be returned if a conflict happens
|
// The current lockID will be returned if a conflict happens
|
||||||
UnLock(ctx context.Context, lockID string) (*ConnectorResponse, error)
|
UnLock(ctx context.Context, lockID string) (*ConnectorResponse, error)
|
||||||
@@ -172,6 +174,8 @@ func (f *FileConnector) GetLock(ctx context.Context) (*ConnectorResponse, error)
|
|||||||
// the method will return an empty lock id.
|
// the method will return an empty lock id.
|
||||||
//
|
//
|
||||||
// For the "unlock and relock" operation, the behavior will be the same.
|
// For the "unlock and relock" operation, the behavior will be the same.
|
||||||
|
//
|
||||||
|
// On success, the mtime of the file will be returned in the X-Wopi-Version header.
|
||||||
func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*ConnectorResponse, error) {
|
func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*ConnectorResponse, error) {
|
||||||
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,11 +240,26 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*Co
|
|||||||
setOrRefreshStatus = resp.GetStatus()
|
setOrRefreshStatus = resp.GetStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statResp, err := f.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||||
|
Ref: wopiContext.FileReference,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Lock failed trying to get the file info")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statResp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK {
|
||||||
|
logger.Error().
|
||||||
|
Str("StatusCode", statResp.GetStatus().GetCode().String()).
|
||||||
|
Str("StatusMsg", statResp.GetStatus().GetMessage()).
|
||||||
|
Msg("Lock failed trying to get the file info with unexpected status")
|
||||||
|
return NewResponse(500), nil
|
||||||
|
}
|
||||||
|
|
||||||
// we're checking the status of either the "SetLock" or "RefreshLock" operations
|
// we're checking the status of either the "SetLock" or "RefreshLock" operations
|
||||||
switch setOrRefreshStatus.GetCode() {
|
switch setOrRefreshStatus.GetCode() {
|
||||||
case rpcv1beta1.Code_CODE_OK:
|
case rpcv1beta1.Code_CODE_OK:
|
||||||
logger.Debug().Msg("SetLock successful")
|
logger.Debug().Msg("SetLock successful")
|
||||||
return NewResponse(200), nil
|
return NewResponseWithVersion(200, statResp.GetInfo().GetMtime()), nil
|
||||||
|
|
||||||
case rpcv1beta1.Code_CODE_FAILED_PRECONDITION, rpcv1beta1.Code_CODE_ABORTED:
|
case rpcv1beta1.Code_CODE_FAILED_PRECONDITION, rpcv1beta1.Code_CODE_ABORTED:
|
||||||
// Code_CODE_FAILED_PRECONDITION -> Lock operation mismatched lock
|
// Code_CODE_FAILED_PRECONDITION -> Lock operation mismatched lock
|
||||||
@@ -270,7 +289,7 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*Co
|
|||||||
logger.Warn().
|
logger.Warn().
|
||||||
Str("LockID", resp.GetLock().GetLockId()).
|
Str("LockID", resp.GetLock().GetLockId()).
|
||||||
Msg("SetLock conflict")
|
Msg("SetLock conflict")
|
||||||
return NewResponseWithLock(409, resp.GetLock().GetLockId()), nil
|
return NewResponseLockConflict(resp.GetLock().GetLockId(), "Conflicting LockID"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: according to the spec we need to treat this as a RefreshLock
|
// TODO: according to the spec we need to treat this as a RefreshLock
|
||||||
@@ -281,7 +300,7 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*Co
|
|||||||
logger.Warn().
|
logger.Warn().
|
||||||
Str("LockID", resp.GetLock().GetLockId()).
|
Str("LockID", resp.GetLock().GetLockId()).
|
||||||
Msg("SetLock lock refreshed instead")
|
Msg("SetLock lock refreshed instead")
|
||||||
return NewResponse(200), nil // no need to send the lockID for a 200 code
|
return NewResponseWithVersionAndLock(200, statResp.GetInfo().GetMtime(), resp.GetLock().GetLockId()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error().Msg("SetLock failed and could not refresh")
|
logger.Error().Msg("SetLock failed and could not refresh")
|
||||||
@@ -313,6 +332,8 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (*Co
|
|||||||
// return an empty lock id.
|
// return an empty lock id.
|
||||||
// The conflict happens if the provided lockID doesn't match the one actually
|
// The conflict happens if the provided lockID doesn't match the one actually
|
||||||
// applied in the target file.
|
// applied in the target file.
|
||||||
|
//
|
||||||
|
// On success, the mtime of the file will be returned in the X-Wopi-Version header.
|
||||||
func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*ConnectorResponse, error) {
|
func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*ConnectorResponse, error) {
|
||||||
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -347,10 +368,27 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*Connec
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statResp, err := f.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||||
|
Ref: wopiContext.FileReference,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("RefreshLock failed trying to get the file info")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statResp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK {
|
||||||
|
logger.Error().
|
||||||
|
Str("StatusCode", statResp.GetStatus().GetCode().String()).
|
||||||
|
Str("StatusMsg", statResp.GetStatus().GetMessage()).
|
||||||
|
Msg("RefreshLock failed trying to get the file info with unexpected status")
|
||||||
|
return NewResponse(500), nil
|
||||||
|
}
|
||||||
|
|
||||||
switch resp.GetStatus().GetCode() {
|
switch resp.GetStatus().GetCode() {
|
||||||
case rpcv1beta1.Code_CODE_OK:
|
case rpcv1beta1.Code_CODE_OK:
|
||||||
logger.Debug().Msg("RefreshLock successful")
|
logger.Debug().Msg("RefreshLock successful")
|
||||||
return NewResponse(200), nil
|
// The current lock should not be returned in the headers on success
|
||||||
|
// https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/files/refreshlock#response-headers
|
||||||
|
return NewResponseWithVersion(200, statResp.GetInfo().GetMtime()), nil
|
||||||
|
|
||||||
case rpcv1beta1.Code_CODE_NOT_FOUND:
|
case rpcv1beta1.Code_CODE_NOT_FOUND:
|
||||||
logger.Error().
|
logger.Error().
|
||||||
@@ -390,7 +428,7 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*Connec
|
|||||||
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
||||||
Str("StatusMsg", resp.GetStatus().GetMessage()).
|
Str("StatusMsg", resp.GetStatus().GetMessage()).
|
||||||
Msg("RefreshLock failed, no lock on file")
|
Msg("RefreshLock failed, no lock on file")
|
||||||
return NewResponseWithLock(409, ""), nil
|
return NewResponseConflictWithVersion(statResp.GetInfo().GetMtime(), "No lock on file"), nil
|
||||||
} else {
|
} else {
|
||||||
// lock is different than the one requested, otherwise we wouldn't reached this point
|
// lock is different than the one requested, otherwise we wouldn't reached this point
|
||||||
logger.Error().
|
logger.Error().
|
||||||
@@ -398,7 +436,7 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*Connec
|
|||||||
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
||||||
Str("StatusMsg", resp.GetStatus().GetMessage()).
|
Str("StatusMsg", resp.GetStatus().GetMessage()).
|
||||||
Msg("RefreshLock failed, lock mismatch")
|
Msg("RefreshLock failed, lock mismatch")
|
||||||
return NewResponseWithLock(409, resp.GetLock().GetLockId()), nil
|
return NewResponseLockConflict(resp.GetLock().GetLockId(), "Lock mismatch"), nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logger.Error().
|
logger.Error().
|
||||||
@@ -422,6 +460,8 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (*Connec
|
|||||||
// return an empty lock id.
|
// return an empty lock id.
|
||||||
// The conflict happens if the provided lockID doesn't match the one actually
|
// The conflict happens if the provided lockID doesn't match the one actually
|
||||||
// applied in the target file.
|
// applied in the target file.
|
||||||
|
//
|
||||||
|
// On success, the mtime of the file will be returned in the X-Wopi-Version header.
|
||||||
func (f *FileConnector) UnLock(ctx context.Context, lockID string) (*ConnectorResponse, error) {
|
func (f *FileConnector) UnLock(ctx context.Context, lockID string) (*ConnectorResponse, error) {
|
||||||
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
wopiContext, err := middleware.WopiContextFromCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -452,14 +492,29 @@ func (f *FileConnector) UnLock(ctx context.Context, lockID string) (*ConnectorRe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statResp, err := f.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||||
|
Ref: wopiContext.FileReference,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().Err(err).Msg("Unlock failed trying to get the file info")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statResp.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK {
|
||||||
|
logger.Error().
|
||||||
|
Str("StatusCode", statResp.GetStatus().GetCode().String()).
|
||||||
|
Str("StatusMsg", statResp.GetStatus().GetMessage()).
|
||||||
|
Msg("Unlock failed trying to get the file info with unexpected status")
|
||||||
|
return NewResponse(500), nil
|
||||||
|
}
|
||||||
|
|
||||||
switch resp.GetStatus().GetCode() {
|
switch resp.GetStatus().GetCode() {
|
||||||
case rpcv1beta1.Code_CODE_OK:
|
case rpcv1beta1.Code_CODE_OK:
|
||||||
logger.Debug().Msg("Unlock successful")
|
logger.Debug().Msg("Unlock successful")
|
||||||
return NewResponse(200), nil
|
return NewResponseWithVersion(200, statResp.GetInfo().GetMtime()), nil
|
||||||
case rpcv1beta1.Code_CODE_ABORTED:
|
case rpcv1beta1.Code_CODE_ABORTED:
|
||||||
// File isn't locked. Need to return 409 with empty lock
|
// File isn't locked. Need to return 409 with empty lock
|
||||||
logger.Error().Err(err).Msg("Unlock failed, file isn't locked")
|
logger.Error().Err(err).Msg("Unlock failed, file isn't locked")
|
||||||
return NewResponseWithLock(409, ""), nil
|
return NewResponseLockConflict("", "File isn't locked"), nil
|
||||||
case rpcv1beta1.Code_CODE_LOCKED:
|
case rpcv1beta1.Code_CODE_LOCKED:
|
||||||
// We need to return 409 with the current lock
|
// We need to return 409 with the current lock
|
||||||
req := &providerv1beta1.GetLockRequest{
|
req := &providerv1beta1.GetLockRequest{
|
||||||
@@ -496,7 +551,7 @@ func (f *FileConnector) UnLock(ctx context.Context, lockID string) (*ConnectorRe
|
|||||||
Msg("Unlock failed, lock mismatch")
|
Msg("Unlock failed, lock mismatch")
|
||||||
outLockId = resp.GetLock().GetLockId()
|
outLockId = resp.GetLock().GetLockId()
|
||||||
}
|
}
|
||||||
return NewResponseWithLock(409, outLockId), nil
|
return NewResponseLockConflict(outLockId, "Lock mismatch"), nil
|
||||||
default:
|
default:
|
||||||
logger.Error().
|
logger.Error().
|
||||||
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
Str("StatusCode", resp.GetStatus().GetCode().String()).
|
||||||
@@ -612,7 +667,7 @@ func (f *FileConnector) PutRelativeFileSuggested(ctx context.Context, ccs Conten
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
wopiSrcURL, err := f.generateWOPISrc(ctx, wopiContext, newLogger)
|
wopiSrcURL, err := f.generateWOPISrc(wopiContext, newLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("PutRelativeFileSuggested: error generating the WOPISrc parameter")
|
logger.Error().Err(err).Msg("PutRelativeFileSuggested: error generating the WOPISrc parameter")
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -708,7 +763,7 @@ func (f *FileConnector) PutRelativeFileRelative(ctx context.Context, ccs Content
|
|||||||
}
|
}
|
||||||
// if conflict generate a different name and retry.
|
// if conflict generate a different name and retry.
|
||||||
// this should happen only once
|
// this should happen only once
|
||||||
wopiSrcURL, err2 := f.generateWOPISrc(ctx, wopiContext, newLogger)
|
wopiSrcURL, err2 := f.generateWOPISrc(wopiContext, newLogger)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
newLogger.Error().
|
newLogger.Error().
|
||||||
Err(err2).
|
Err(err2).
|
||||||
@@ -724,12 +779,13 @@ func (f *FileConnector) PutRelativeFileRelative(ctx context.Context, ccs Content
|
|||||||
Str("LockID", lockID).
|
Str("LockID", lockID).
|
||||||
Msg("PutRelativeFileRelative: error conflict")
|
Msg("PutRelativeFileRelative: error conflict")
|
||||||
|
|
||||||
// need to build the response ourselves
|
// need to build the response ourselves
|
||||||
return &ConnectorResponse{
|
return &ConnectorResponse{
|
||||||
Status: 409,
|
Status: 409,
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
HeaderWopiValidRT: finalTarget,
|
HeaderWopiValidRT: finalTarget,
|
||||||
HeaderWopiLock: lockID,
|
HeaderWopiLock: lockID,
|
||||||
|
HeaderWopiLockFailureReason: "Lock Conflict",
|
||||||
},
|
},
|
||||||
Body: map[string]interface{}{
|
Body: map[string]interface{}{
|
||||||
"Name": target,
|
"Name": target,
|
||||||
@@ -747,7 +803,7 @@ func (f *FileConnector) PutRelativeFileRelative(ctx context.Context, ccs Content
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
wopiSrcURL, err := f.generateWOPISrc(ctx, wopiContext, newLogger)
|
wopiSrcURL, err := f.generateWOPISrc(wopiContext, newLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newLogger.Error().Err(err).Msg("PutRelativeFileRelative: error generating the WOPISrc parameter")
|
newLogger.Error().Err(err).Msg("PutRelativeFileRelative: error generating the WOPISrc parameter")
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -798,7 +854,8 @@ func (f *FileConnector) DeleteFile(ctx context.Context, lockID string) (*Connect
|
|||||||
if deleteRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_TOO_EARLY {
|
if deleteRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_TOO_EARLY {
|
||||||
// starting from 20ms, double the waiting time for each retry
|
// starting from 20ms, double the waiting time for each retry
|
||||||
// capping at 5 secs
|
// capping at 5 secs
|
||||||
waitingTime := (20 * time.Millisecond) << retries
|
var waitingTime time.Duration
|
||||||
|
waitingTime = (20 * time.Millisecond) << retries
|
||||||
if waitingTime.Seconds() > 5 {
|
if waitingTime.Seconds() > 5 {
|
||||||
waitingTime = 5 * time.Second
|
waitingTime = 5 * time.Second
|
||||||
}
|
}
|
||||||
@@ -849,7 +906,7 @@ func (f *FileConnector) DeleteFile(ctx context.Context, lockID string) (*Connect
|
|||||||
logger.Error().
|
logger.Error().
|
||||||
Str("LockID", resp.GetLock().GetLockId()).
|
Str("LockID", resp.GetLock().GetLockId()).
|
||||||
Msg("DeleteFile: file is locked")
|
Msg("DeleteFile: file is locked")
|
||||||
return NewResponseWithLock(409, resp.GetLock().GetLockId()), nil
|
return NewResponseLockConflict(resp.GetLock().GetLockId(), "File is locked"), nil
|
||||||
} else {
|
} else {
|
||||||
// return the original error since the file isn't locked
|
// return the original error since the file isn't locked
|
||||||
logger.Error().Msg("DeleteFile: delete failed on unlocked file")
|
logger.Error().Msg("DeleteFile: delete failed on unlocked file")
|
||||||
@@ -942,7 +999,7 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) (
|
|||||||
Str("StatusCode", moveRes.GetStatus().GetCode().String()).
|
Str("StatusCode", moveRes.GetStatus().GetCode().String()).
|
||||||
Str("StatusMsg", moveRes.GetStatus().GetMessage()).
|
Str("StatusMsg", moveRes.GetStatus().GetMessage()).
|
||||||
Msg("RenameFile: conflict")
|
Msg("RenameFile: conflict")
|
||||||
return NewResponseWithLock(409, currentLockID), nil
|
return NewResponseLockConflict(currentLockID, "Lock Conflict"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_ALREADY_EXISTS {
|
if moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_ALREADY_EXISTS {
|
||||||
@@ -1018,7 +1075,6 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
|
|||||||
}
|
}
|
||||||
|
|
||||||
hexEncodedOwnerId := hex.EncodeToString([]byte(statRes.GetInfo().GetOwner().GetOpaqueId() + "@" + statRes.GetInfo().GetOwner().GetIdp()))
|
hexEncodedOwnerId := hex.EncodeToString([]byte(statRes.GetInfo().GetOwner().GetOpaqueId() + "@" + statRes.GetInfo().GetOwner().GetIdp()))
|
||||||
version := strconv.FormatUint(statRes.GetInfo().GetMtime().GetSeconds(), 10) + "." + strconv.FormatUint(uint64(statRes.GetInfo().GetMtime().GetNanos()), 10)
|
|
||||||
|
|
||||||
// UserId must use only alphanumeric chars (https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/files/checkfileinfo/checkfileinfo-response#requirements-for-user-identity-properties)
|
// UserId must use only alphanumeric chars (https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/files/checkfileinfo/checkfileinfo-response#requirements-for-user-identity-properties)
|
||||||
// assign userId, userFriendlyName and isAnonymousUser
|
// assign userId, userFriendlyName and isAnonymousUser
|
||||||
@@ -1029,30 +1085,44 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
|
|||||||
isAnonymousUser := true
|
isAnonymousUser := true
|
||||||
|
|
||||||
isPublicShare := false
|
isPublicShare := false
|
||||||
if wopiContext.User != nil {
|
user := ctxpkg.ContextMustGetUser(ctx)
|
||||||
|
if user.String() != "" {
|
||||||
// if we have a wopiContext.User
|
// if we have a wopiContext.User
|
||||||
isPublicShare = utils.ExistsInOpaque(wopiContext.User.GetOpaque(), "public-share-role")
|
isPublicShare = utils.ExistsInOpaque(user.GetOpaque(), "public-share-role")
|
||||||
if !isPublicShare {
|
if !isPublicShare {
|
||||||
hexEncodedWopiUserId := hex.EncodeToString([]byte(wopiContext.User.GetId().GetOpaqueId() + "@" + wopiContext.User.GetId().GetIdp()))
|
hexEncodedWopiUserId := hex.EncodeToString([]byte(user.GetId().GetOpaqueId() + "@" + user.GetId().GetIdp()))
|
||||||
isAnonymousUser = false
|
isAnonymousUser = false
|
||||||
userFriendlyName = wopiContext.User.GetDisplayName()
|
userFriendlyName = user.GetDisplayName()
|
||||||
userId = hexEncodedWopiUserId
|
userId = hexEncodedWopiUserId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
breadcrumbFolderName := path.Dir(statRes.Info.Path)
|
||||||
|
if breadcrumbFolderName == "." || breadcrumbFolderName == "" {
|
||||||
|
breadcrumbFolderName = statRes.GetInfo().GetSpace().GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
ocisUrl, err := url.Parse(f.cfg.Commons.OcisURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
breadcrumbFolderURL, viewAppUrl, editAppUrl := *ocisUrl, *ocisUrl, *ocisUrl
|
||||||
|
breadcrumbFolderURL.Path = path.Join(breadcrumbFolderURL.Path, "f", storagespace.FormatResourceID(statRes.GetInfo().GetId()))
|
||||||
|
viewAppUrl.Path = path.Join(viewAppUrl.Path, "external"+strings.ToLower(f.cfg.App.Name))
|
||||||
|
editAppUrl.Path = path.Join(editAppUrl.Path, "external"+strings.ToLower(f.cfg.App.Name))
|
||||||
// fileinfo map
|
// fileinfo map
|
||||||
infoMap := map[string]interface{}{
|
infoMap := map[string]interface{}{
|
||||||
fileinfo.KeyOwnerID: hexEncodedOwnerId,
|
fileinfo.KeyOwnerID: hexEncodedOwnerId,
|
||||||
fileinfo.KeySize: int64(statRes.GetInfo().GetSize()),
|
fileinfo.KeySize: int64(statRes.GetInfo().GetSize()),
|
||||||
fileinfo.KeyVersion: version,
|
fileinfo.KeyVersion: helpers.GetVersion(statRes.GetInfo().GetMtime()),
|
||||||
fileinfo.KeyBaseFileName: path.Base(statRes.GetInfo().GetPath()),
|
fileinfo.KeyBaseFileName: path.Base(statRes.GetInfo().GetPath()),
|
||||||
fileinfo.KeyBreadcrumbDocName: path.Base(statRes.GetInfo().GetPath()),
|
fileinfo.KeyBreadcrumbDocName: path.Base(statRes.GetInfo().GetPath()),
|
||||||
// to get the folder we actually need to do a GetPath() request
|
// to get the folder we actually need to do a GetPath() request
|
||||||
//BreadcrumbFolderName: path.Dir(statRes.Info.Path),
|
fileinfo.KeyBreadcrumbFolderName: breadcrumbFolderName,
|
||||||
|
fileinfo.KeyBreadcrumbFolderURL: breadcrumbFolderURL.String(),
|
||||||
|
|
||||||
// TODO: these URLs must point to ocis, which is hosting the editor's iframe
|
//fileinfo.KeyHostViewURL: viewAppUrl.String(),
|
||||||
//fileinfo.KeyHostViewURL: wopiContext.ViewAppUrl,
|
//fileinfo.KeyHostEditURL: editAppUrl.String(),
|
||||||
//fileinfo.KeyHostEditURL: wopiContext.EditAppUrl,
|
|
||||||
|
|
||||||
fileinfo.KeyEnableOwnerTermination: true, // only for collabora
|
fileinfo.KeyEnableOwnerTermination: true, // only for collabora
|
||||||
fileinfo.KeySupportsExtendedLockLength: true,
|
fileinfo.KeySupportsExtendedLockLength: true,
|
||||||
@@ -1066,7 +1136,8 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
|
|||||||
fileinfo.KeyUserFriendlyName: userFriendlyName,
|
fileinfo.KeyUserFriendlyName: userFriendlyName,
|
||||||
fileinfo.KeyUserID: userId,
|
fileinfo.KeyUserID: userId,
|
||||||
|
|
||||||
fileinfo.KeyPostMessageOrigin: f.cfg.Commons.OcisURL,
|
fileinfo.KeyPostMessageOrigin: f.cfg.Commons.OcisURL,
|
||||||
|
fileinfo.KeyLicenseCheckForEditIsEnabled: f.cfg.App.LicenseCheckEnable,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch wopiContext.ViewMode {
|
switch wopiContext.ViewMode {
|
||||||
@@ -1082,7 +1153,7 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (*ConnectorResponse,
|
|||||||
infoMap[fileinfo.KeyDisableCopy] = true // only for collabora
|
infoMap[fileinfo.KeyDisableCopy] = true // only for collabora
|
||||||
infoMap[fileinfo.KeyDisablePrint] = true
|
infoMap[fileinfo.KeyDisablePrint] = true
|
||||||
if !isPublicShare {
|
if !isPublicShare {
|
||||||
infoMap[fileinfo.KeyWatermarkText] = f.watermarkText(wopiContext.User) // only for collabora
|
infoMap[fileinfo.KeyWatermarkText] = f.watermarkText(user) // only for collabora
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1162,7 +1233,7 @@ func (f *FileConnector) generatePrefix() string {
|
|||||||
// contains the resource id of the target file without the path
|
// contains the resource id of the target file without the path
|
||||||
// (storage, opaque and space points directly to the file). The path component
|
// (storage, opaque and space points directly to the file). The path component
|
||||||
// will be ignored
|
// will be ignored
|
||||||
func (f *FileConnector) generateWOPISrc(ctx context.Context, wopiContext middleware.WopiContext, logger zerolog.Logger) (*url.URL, error) {
|
func (f *FileConnector) generateWOPISrc(wopiContext middleware.WopiContext, logger zerolog.Logger) (*url.URL, error) {
|
||||||
// get the WOPI token for the new file
|
// get the WOPI token for the new file
|
||||||
accessToken, _, err := middleware.GenerateWopiToken(wopiContext, f.cfg)
|
accessToken, _, err := middleware.GenerateWopiToken(wopiContext, f.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1174,16 +1245,14 @@ func (f *FileConnector) generateWOPISrc(ctx context.Context, wopiContext middlew
|
|||||||
fileRef := helpers.HashResourceId(wopiContext.FileReference.GetResourceId())
|
fileRef := helpers.HashResourceId(wopiContext.FileReference.GetResourceId())
|
||||||
|
|
||||||
// generate the URL for the WOPI app to access the new created file
|
// generate the URL for the WOPI app to access the new created file
|
||||||
wopiSrcURL, err := url.Parse(f.cfg.Wopi.WopiSrc)
|
wopiSrcURL, err := wopisrc.GenerateWopiSrc(fileRef, f.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("generateWOPISrc: failed to generate WOPISrc URL for the new file")
|
logger.Error().Err(err).Msg("generateWOPISrc: failed to generate WOPISrc URL for the new file")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
wopiSrcURL.Path = path.Join("wopi", "files", fileRef)
|
|
||||||
q := wopiSrcURL.Query()
|
q := wopiSrcURL.Query()
|
||||||
q.Add("access_token", accessToken)
|
q.Add("access_token", accessToken)
|
||||||
wopiSrcURL.RawQuery = q.Encode()
|
wopiSrcURL.RawQuery = q.Encode()
|
||||||
|
|
||||||
return wopiSrcURL, nil
|
return wopiSrcURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
|
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
|
||||||
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
cs3mocks "github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
@@ -63,25 +64,6 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
Path: ".",
|
Path: ".",
|
||||||
},
|
},
|
||||||
User: &userv1beta1.User{
|
|
||||||
Id: &userv1beta1.UserId{
|
|
||||||
Idp: "inmemory",
|
|
||||||
OpaqueId: "opaqueId",
|
|
||||||
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
|
|
||||||
},
|
|
||||||
Username: "Shaft",
|
|
||||||
DisplayName: "Pet Shaft",
|
|
||||||
Mail: "shaft@example.com",
|
|
||||||
// Opaque is here for reference, not used by default but might be needed for some tests
|
|
||||||
//Opaque: &typesv1beta1.Opaque{
|
|
||||||
// Map: map[string]*typesv1beta1.OpaqueEntry{
|
|
||||||
// "public-share-role": &typesv1beta1.OpaqueEntry{
|
|
||||||
// Decoder: "plain",
|
|
||||||
// Value: []byte("viewer"),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
//},
|
|
||||||
},
|
|
||||||
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -116,7 +98,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.GetLock(ctx)
|
response, err := fc.GetLock(ctx)
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(404))
|
Expect(response.Status).To(Equal(404))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -134,7 +116,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.GetLock(ctx)
|
response, err := fc.GetLock(ctx)
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
})
|
})
|
||||||
@@ -153,7 +135,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "", "")
|
response, err := fc.Lock(ctx, "", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(400))
|
Expect(response.Status).To(Equal(400))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -179,10 +161,25 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(
|
||||||
|
&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{
|
||||||
|
Seconds: 12345,
|
||||||
|
Nanos: 6789,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Set lock mismatches error getting lock", func() {
|
It("Set lock mismatches error getting lock", func() {
|
||||||
@@ -197,6 +194,9 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInternal(ctx, "lock mismatch"),
|
Status: status.NewInternal(ctx, "lock mismatch"),
|
||||||
}, targetErr)
|
}, targetErr)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err).To(Equal(targetErr))
|
Expect(err).To(Equal(targetErr))
|
||||||
@@ -218,10 +218,15 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
|
Expect(response.Headers).To(HaveLen(2))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiLockFailureReason]).To(Equal("Conflicting LockID"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Set lock mismatches but get lock matches", func() {
|
It("Set lock mismatches but get lock matches", func() {
|
||||||
@@ -239,10 +244,20 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(2))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("abcdef123"))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Set lock mismatches but get lock doesn't return lockId", func() {
|
It("Set lock mismatches but get lock doesn't return lockId", func() {
|
||||||
@@ -256,8 +271,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -269,8 +287,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewNotFound(ctx, "file not found"),
|
Status: status.NewNotFound(ctx, "file not found"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(404))
|
Expect(response.Status).To(Equal(404))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -282,8 +303,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "")
|
response, err := fc.Lock(ctx, "abcdef123", "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -301,7 +325,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "", "oldLock")
|
response, err := fc.Lock(ctx, "", "oldLock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(400))
|
Expect(response.Status).To(Equal(400))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -327,10 +351,19 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "oldLock")
|
response, err := fc.Lock(ctx, "abcdef123", "oldLock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Refresh lock mismatches error getting lock", func() {
|
It("Refresh lock mismatches error getting lock", func() {
|
||||||
@@ -345,6 +378,9 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInternal(ctx, "lock mismatch"),
|
Status: status.NewInternal(ctx, "lock mismatch"),
|
||||||
}, targetErr)
|
}, targetErr)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err).To(Equal(targetErr))
|
Expect(err).To(Equal(targetErr))
|
||||||
@@ -366,8 +402,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
})
|
})
|
||||||
@@ -387,10 +426,20 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(2))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("abcdef123"))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Refresh lock mismatches but get lock doesn't return lockId", func() {
|
It("Refresh lock mismatches but get lock doesn't return lockId", func() {
|
||||||
@@ -404,8 +453,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -417,8 +469,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewNotFound(ctx, "file not found"),
|
Status: status.NewNotFound(ctx, "file not found"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(404))
|
Expect(response.Status).To(Equal(404))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -430,8 +485,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
response, err := fc.Lock(ctx, "abcdef123", "112233")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -441,7 +499,8 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Describe("RefreshLock", func() {
|
Describe("RefreshLock", func() {
|
||||||
It("No valid context", func() {
|
It("No valid context", func() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
response, err := fc.RefreshLock(ctx, "newLock")
|
|
||||||
|
response, err := fc.RefreshLock(ctx, "")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(response).To(BeNil())
|
Expect(response).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -450,7 +509,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "")
|
response, err := fc.RefreshLock(ctx, "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(400))
|
Expect(response.Status).To(Equal(400))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -476,10 +535,19 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Refresh lock file not found", func() {
|
It("Refresh lock file not found", func() {
|
||||||
@@ -489,8 +557,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewNotFound(ctx, "file not found"),
|
Status: status.NewNotFound(ctx, "file not found"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(404))
|
Expect(response.Status).To(Equal(404))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -507,6 +578,9 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewConflict(ctx, nil, "lock mismatch"),
|
Status: status.NewConflict(ctx, nil, "lock mismatch"),
|
||||||
}, targetErr)
|
}, targetErr)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err).To(Equal(targetErr))
|
Expect(err).To(Equal(targetErr))
|
||||||
@@ -524,8 +598,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInternal(ctx, "lock mismatch"),
|
Status: status.NewInternal(ctx, "lock mismatch"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -541,8 +618,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
||||||
})
|
})
|
||||||
@@ -562,8 +642,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
})
|
})
|
||||||
@@ -575,8 +658,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.RefreshLock(ctx, "abcdef123")
|
response, err := fc.RefreshLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -585,7 +671,8 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Describe("Unlock", func() {
|
Describe("Unlock", func() {
|
||||||
It("No valid context", func() {
|
It("No valid context", func() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
response, err := fc.UnLock(ctx, "newLock")
|
|
||||||
|
response, err := fc.UnLock(ctx, "")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(response).To(BeNil())
|
Expect(response).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -594,7 +681,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "")
|
response, err := fc.UnLock(ctx, "")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(400))
|
Expect(response.Status).To(Equal(400))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -620,10 +707,19 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(HaveLen(1))
|
||||||
|
Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Unlock file isn't locked", func() {
|
It("Unlock file isn't locked", func() {
|
||||||
@@ -633,8 +729,16 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewConflict(ctx, nil, "lock mismatch"),
|
Status: status.NewConflict(ctx, nil, "lock mismatch"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
Info: &providerv1beta1.ResourceInfo{
|
||||||
|
Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
||||||
})
|
})
|
||||||
@@ -651,6 +755,9 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInternal(ctx, "something failed"),
|
Status: status.NewInternal(ctx, "something failed"),
|
||||||
}, targetErr)
|
}, targetErr)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err).To(Equal(targetErr))
|
Expect(err).To(Equal(targetErr))
|
||||||
@@ -668,8 +775,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInternal(ctx, "something failed"),
|
Status: status.NewInternal(ctx, "something failed"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -685,8 +795,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal(""))
|
||||||
})
|
})
|
||||||
@@ -706,8 +819,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
})
|
})
|
||||||
@@ -719,8 +835,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
Status: status.NewInsufficientStorage(ctx, nil, "file too big"),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).
|
||||||
|
Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil)
|
||||||
|
|
||||||
response, err := fc.UnLock(ctx, "abcdef123")
|
response, err := fc.UnLock(ctx, "abcdef123")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -759,7 +878,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
|
|
||||||
stream := strings.NewReader("This is the content of a file")
|
stream := strings.NewReader("This is the content of a file")
|
||||||
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newFile.txt")
|
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newFile.txt")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -813,7 +932,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx")
|
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -869,7 +988,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -938,7 +1057,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -972,7 +1091,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(500), nil)
|
ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(500), nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1012,7 +1131,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
|
|
||||||
stream := strings.NewReader("This is the content of a file")
|
stream := strings.NewReader("This is the content of a file")
|
||||||
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newFile.txt")
|
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newFile.txt")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1065,7 +1184,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx")
|
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -1124,7 +1243,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf")
|
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
Expect(response.Headers[connector.HeaderWopiValidRT]).To(MatchRegexp(`[a-zA-Z0-9_-] convFile\.pdf`))
|
Expect(response.Headers[connector.HeaderWopiValidRT]).To(MatchRegexp(`[a-zA-Z0-9_-] convFile\.pdf`))
|
||||||
@@ -1159,7 +1278,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(500), nil)
|
ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(500), nil)
|
||||||
|
|
||||||
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf")
|
response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1223,7 +1342,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, targetErr)
|
}, targetErr)
|
||||||
|
|
||||||
response, err := fc.DeleteFile(ctx, "newlock")
|
response, err := fc.DeleteFile(ctx, "newlock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(404))
|
Expect(response.Status).To(Equal(404))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -1240,7 +1359,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.DeleteFile(ctx, "newlock")
|
response, err := fc.DeleteFile(ctx, "newlock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -1261,7 +1380,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.DeleteFile(ctx, "newlock")
|
response, err := fc.DeleteFile(ctx, "newlock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
})
|
})
|
||||||
@@ -1278,7 +1397,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.DeleteFile(ctx, "newlock")
|
response, err := fc.DeleteFile(ctx, "newlock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -1291,7 +1410,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.DeleteFile(ctx, "newlock")
|
response, err := fc.DeleteFile(ctx, "newlock")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
})
|
})
|
||||||
@@ -1327,7 +1446,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1375,7 +1494,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1399,7 +1518,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
response, err := fc.RenameFile(ctx, "lockid", "newFile.doc")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(409))
|
Expect(response.Status).To(Equal(409))
|
||||||
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999"))
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
@@ -1442,7 +1561,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil).Once()
|
}, nil).Once()
|
||||||
|
|
||||||
response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc")
|
response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -1474,7 +1593,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil).Once()
|
}, nil).Once()
|
||||||
|
|
||||||
response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc")
|
response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc")
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Headers).To(BeNil())
|
Expect(response.Headers).To(BeNil())
|
||||||
rBody := response.Body.(map[string]interface{})
|
rBody := response.Body.(map[string]interface{})
|
||||||
@@ -1512,13 +1631,21 @@ var _ = Describe("FileConnector", func() {
|
|||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
response, err := fc.CheckFileInfo(ctx)
|
response, err := fc.CheckFileInfo(ctx)
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(500))
|
Expect(response.Status).To(Equal(500))
|
||||||
Expect(response.Body).To(BeNil())
|
Expect(response.Body).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Stat success", func() {
|
It("Stat success", func() {
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
u := &userv1beta1.User{
|
||||||
|
Id: &userv1beta1.UserId{
|
||||||
|
Idp: "customIdp",
|
||||||
|
OpaqueId: "admin",
|
||||||
|
},
|
||||||
|
DisplayName: "Pet Shaft",
|
||||||
|
}
|
||||||
|
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||||
|
|
||||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
@@ -1533,17 +1660,25 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Seconds: uint64(16273849),
|
Seconds: uint64(16273849),
|
||||||
},
|
},
|
||||||
Path: "/path/to/test.txt",
|
Path: "/path/to/test.txt",
|
||||||
// Other properties aren't used for now.
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageid",
|
||||||
|
OpaqueId: "opaqueid",
|
||||||
|
SpaceId: "spaceid",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
expectedFileInfo := &fileinfo.Microsoft{
|
expectedFileInfo := &fileinfo.Microsoft{
|
||||||
OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp
|
OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp
|
||||||
Size: int64(998877),
|
Size: int64(998877),
|
||||||
Version: "16273849.0",
|
Version: "v162738490",
|
||||||
BaseFileName: "test.txt",
|
BaseFileName: "test.txt",
|
||||||
BreadcrumbDocName: "test.txt",
|
BreadcrumbDocName: "test.txt",
|
||||||
UserCanNotWriteRelative: false,
|
BreadcrumbFolderName: "/path/to",
|
||||||
|
BreadcrumbFolderURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid",
|
||||||
|
UserCanNotWriteRelative: false,
|
||||||
|
//HostViewURL: "http://test.ex.prv/view",
|
||||||
|
//HostEditURL: "http://test.ex.prv/edit",
|
||||||
SupportsExtendedLockLength: true,
|
SupportsExtendedLockLength: true,
|
||||||
SupportsGetLock: true,
|
SupportsGetLock: true,
|
||||||
SupportsLocks: true,
|
SupportsLocks: true,
|
||||||
@@ -1552,19 +1687,20 @@ var _ = Describe("FileConnector", func() {
|
|||||||
SupportsRename: true,
|
SupportsRename: true,
|
||||||
UserCanWrite: true,
|
UserCanWrite: true,
|
||||||
UserCanRename: true,
|
UserCanRename: true,
|
||||||
UserID: "6f7061717565496440696e6d656d6f7279", // hex of opaqueId@inmemory
|
UserID: "61646d696e40637573746f6d496470", // hex of admin@customIdp
|
||||||
UserFriendlyName: "Pet Shaft",
|
UserFriendlyName: "Pet Shaft",
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := fc.CheckFileInfo(ctx)
|
response, err := fc.CheckFileInfo(ctx)
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Body.(*fileinfo.Microsoft)).To(Equal(expectedFileInfo))
|
Expect(response.Body.(*fileinfo.Microsoft)).To(Equal(expectedFileInfo))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Stat success guests", func() {
|
It("Stat success guests", func() {
|
||||||
// add user's opaque to include public-share-role
|
// add user's opaque to include public-share-role
|
||||||
wopiCtx.User.Opaque = &typesv1beta1.Opaque{
|
u := &userv1beta1.User{}
|
||||||
|
u.Opaque = &typesv1beta1.Opaque{
|
||||||
Map: map[string]*typesv1beta1.OpaqueEntry{
|
Map: map[string]*typesv1beta1.OpaqueEntry{
|
||||||
"public-share-role": &typesv1beta1.OpaqueEntry{
|
"public-share-role": &typesv1beta1.OpaqueEntry{
|
||||||
Decoder: "plain",
|
Decoder: "plain",
|
||||||
@@ -1576,6 +1712,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY
|
wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY
|
||||||
|
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||||
|
|
||||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
@@ -1590,6 +1727,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Seconds: uint64(16273849),
|
Seconds: uint64(16273849),
|
||||||
},
|
},
|
||||||
Path: "/path/to/test.txt",
|
Path: "/path/to/test.txt",
|
||||||
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageid",
|
||||||
|
OpaqueId: "opaqueid",
|
||||||
|
SpaceId: "spaceid",
|
||||||
|
},
|
||||||
// Other properties aren't used for now.
|
// Other properties aren't used for now.
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
@@ -1625,7 +1767,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
response.Body.(*fileinfo.Collabora).UserID = "guest-zzz000"
|
response.Body.(*fileinfo.Collabora).UserID = "guest-zzz000"
|
||||||
response.Body.(*fileinfo.Collabora).UserFriendlyName = "guest zzz000"
|
response.Body.(*fileinfo.Collabora).UserFriendlyName = "guest zzz000"
|
||||||
|
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo))
|
Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo))
|
||||||
})
|
})
|
||||||
@@ -1635,6 +1777,16 @@ var _ = Describe("FileConnector", func() {
|
|||||||
wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY
|
wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY
|
||||||
|
|
||||||
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx)
|
||||||
|
u := &userv1beta1.User{
|
||||||
|
Id: &userv1beta1.UserId{
|
||||||
|
Idp: "example.com",
|
||||||
|
OpaqueId: "aabbcc",
|
||||||
|
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
|
||||||
|
},
|
||||||
|
DisplayName: "Pet Shaft",
|
||||||
|
Mail: "shaft@example.com",
|
||||||
|
}
|
||||||
|
ctx = ctxpkg.ContextSetUser(ctx, u)
|
||||||
|
|
||||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{
|
||||||
Status: status.NewOK(ctx),
|
Status: status.NewOK(ctx),
|
||||||
@@ -1649,7 +1801,11 @@ var _ = Describe("FileConnector", func() {
|
|||||||
Seconds: uint64(16273849),
|
Seconds: uint64(16273849),
|
||||||
},
|
},
|
||||||
Path: "/path/to/test.txt",
|
Path: "/path/to/test.txt",
|
||||||
// Other properties aren't used for now.
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageid",
|
||||||
|
OpaqueId: "opaqueid",
|
||||||
|
SpaceId: "spaceid",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
@@ -1664,7 +1820,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
DisableExport: true,
|
DisableExport: true,
|
||||||
DisableCopy: true,
|
DisableCopy: true,
|
||||||
DisablePrint: true,
|
DisablePrint: true,
|
||||||
UserID: hex.EncodeToString([]byte("opaqueId@inmemory")),
|
UserID: hex.EncodeToString([]byte("aabbcc@example.com")),
|
||||||
UserFriendlyName: "Pet Shaft",
|
UserFriendlyName: "Pet Shaft",
|
||||||
EnableOwnerTermination: true,
|
EnableOwnerTermination: true,
|
||||||
WatermarkText: "Pet Shaft shaft@example.com",
|
WatermarkText: "Pet Shaft shaft@example.com",
|
||||||
@@ -1677,7 +1833,7 @@ var _ = Describe("FileConnector", func() {
|
|||||||
|
|
||||||
response, err := fc.CheckFileInfo(ctx)
|
response, err := fc.CheckFileInfo(ctx)
|
||||||
|
|
||||||
Expect(err).To(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(response.Status).To(Equal(200))
|
Expect(response.Status).To(Equal(200))
|
||||||
Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo))
|
Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ type Microsoft struct {
|
|||||||
// A Boolean value indicating whether the user is an education user or not.
|
// A Boolean value indicating whether the user is an education user or not.
|
||||||
IsEduUser bool `json:"IsEduUser,omitempty"`
|
IsEduUser bool `json:"IsEduUser,omitempty"`
|
||||||
// A Boolean value indicating whether the user is a business user or not.
|
// A Boolean value indicating whether the user is a business user or not.
|
||||||
LicenseCheckForEditIsEnabled bool `json:"LicenseCheckForEditIsEnabled,omitempty"`
|
LicenseCheckForEditIsEnabled bool `json:"LicenseCheckForEditIsEnabled"`
|
||||||
// A string that is the name of the user, suitable for displaying in UI.
|
// A string that is the name of the user, suitable for displaying in UI.
|
||||||
UserFriendlyName string `json:"UserFriendlyName,omitempty"`
|
UserFriendlyName string `json:"UserFriendlyName,omitempty"`
|
||||||
// A string value containing information about the user. This string can be passed from a WOPI client to the host by means of a PutUserInfo operation. If the host has a UserInfo string for the user, they must include it in this property. See the PutUserInfo documentation for more details.
|
// A string value containing information about the user. This string can be passed from a WOPI client to the host by means of a PutUserInfo operation. If the host has a UserInfo string for the user, they must include it in this property. See the PutUserInfo documentation for more details.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
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/config"
|
||||||
@@ -15,16 +14,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HeaderWopiLock string = "X-WOPI-Lock"
|
HeaderWopiLock string = "X-WOPI-Lock"
|
||||||
HeaderWopiOldLock string = "X-WOPI-OldLock"
|
HeaderWopiOldLock string = "X-WOPI-OldLock"
|
||||||
HeaderWopiST string = "X-WOPI-SuggestedTarget"
|
HeaderWopiLockFailureReason string = "X-WOPI-LockFailureReason"
|
||||||
HeaderWopiRT string = "X-WOPI-RelativeTarget"
|
HeaderWopiST string = "X-WOPI-SuggestedTarget"
|
||||||
HeaderWopiOverwriteRT string = "X-WOPI-OverwriteRelativeTarget"
|
HeaderWopiRT string = "X-WOPI-RelativeTarget"
|
||||||
HeaderWopiSize string = "X-WOPI-Size"
|
HeaderWopiOverwriteRT string = "X-WOPI-OverwriteRelativeTarget"
|
||||||
HeaderWopiValidRT string = "X-WOPI-ValidRelativeTarget"
|
HeaderWopiSize string = "X-WOPI-Size"
|
||||||
HeaderWopiRequestedName string = "X-WOPI-RequestedName"
|
HeaderWopiValidRT string = "X-WOPI-ValidRelativeTarget"
|
||||||
HeaderContentLength string = "Content-Length"
|
HeaderWopiRequestedName string = "X-WOPI-RequestedName"
|
||||||
HeaderContentType string = "Content-Type"
|
HeaderContentLength string = "Content-Length"
|
||||||
|
HeaderContentType string = "Content-Type"
|
||||||
|
HeaderWopiVersion string = "X-WOPI-ItemVersion"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HttpAdapter will adapt the responses from the connector to HTTP.
|
// HttpAdapter will adapt the responses from the connector to HTTP.
|
||||||
@@ -51,9 +52,6 @@ func NewHttpAdapter(gwc gatewayv1beta1.GatewayAPIClient, cfg *config.Config) *Ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpAdapter.locks = &locks.NoopLockParser{}
|
httpAdapter.locks = &locks.NoopLockParser{}
|
||||||
if strings.ToLower(cfg.App.Name) == "microsoftofficeonline" {
|
|
||||||
httpAdapter.locks = &locks.LegacyLockParser{}
|
|
||||||
}
|
|
||||||
return httpAdapter
|
return httpAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/mocks"
|
"github.com/owncloud/ocis/v2/services/collaboration/mocks"
|
||||||
@@ -133,12 +134,13 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return(connector.NewResponseWithLock(409, "zzz111"), nil)
|
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return(connector.NewResponseLockConflict("zzz111", "Lock Conflict"), nil)
|
||||||
|
|
||||||
httpAdapter.Lock(w, req)
|
httpAdapter.Lock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(409))
|
Expect(resp.StatusCode).To(Equal(409))
|
||||||
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLockFailureReason)).To(Equal("Lock Conflict"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Success", func() {
|
It("Success", func() {
|
||||||
@@ -148,11 +150,18 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return(connector.NewResponse(200), nil)
|
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return(
|
||||||
|
connector.NewResponseWithVersionAndLock(
|
||||||
|
200,
|
||||||
|
&typesv1beta1.Timestamp{Seconds: uint64(1234), Nanos: uint32(567)},
|
||||||
|
"abc123",
|
||||||
|
), nil)
|
||||||
|
|
||||||
httpAdapter.Lock(w, req)
|
httpAdapter.Lock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
Expect(resp.StatusCode).To(Equal(200))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("abc123"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiVersion)).To(Equal("v1234567"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -195,12 +204,13 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return(connector.NewResponseWithLock(409, "zzz111"), nil)
|
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return(connector.NewResponseLockConflict("zzz111", "Lock Conflict"), nil)
|
||||||
|
|
||||||
httpAdapter.Lock(w, req)
|
httpAdapter.Lock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(409))
|
Expect(resp.StatusCode).To(Equal(409))
|
||||||
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLockFailureReason)).To(Equal("Lock Conflict"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Success", func() {
|
It("Success", func() {
|
||||||
@@ -211,11 +221,18 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return(connector.NewResponse(200), nil)
|
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return(
|
||||||
|
connector.NewResponseWithVersionAndLock(
|
||||||
|
200,
|
||||||
|
&typesv1beta1.Timestamp{Seconds: uint64(1234), Nanos: uint32(567)},
|
||||||
|
"abc123",
|
||||||
|
), nil)
|
||||||
|
|
||||||
httpAdapter.Lock(w, req)
|
httpAdapter.Lock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
Expect(resp.StatusCode).To(Equal(200))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("abc123"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiVersion)).To(Equal("v1234567"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -256,12 +273,13 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponseWithLock(409, "zzz111"), nil)
|
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponseLockConflict("zzz111", "Lock Conflict"), nil)
|
||||||
|
|
||||||
httpAdapter.RefreshLock(w, req)
|
httpAdapter.RefreshLock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(409))
|
Expect(resp.StatusCode).To(Equal(409))
|
||||||
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLockFailureReason)).To(Equal("Lock Conflict"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Success", func() {
|
It("Success", func() {
|
||||||
@@ -271,11 +289,18 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponse(200), nil)
|
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return(
|
||||||
|
connector.NewResponseWithVersionAndLock(
|
||||||
|
200,
|
||||||
|
&typesv1beta1.Timestamp{Seconds: uint64(1234), Nanos: uint32(5678)},
|
||||||
|
"abc123",
|
||||||
|
), nil)
|
||||||
|
|
||||||
httpAdapter.RefreshLock(w, req)
|
httpAdapter.RefreshLock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
Expect(resp.StatusCode).To(Equal(200))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("abc123"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiVersion)).To(Equal("v12345678"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -315,12 +340,13 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponseWithLock(409, "zzz111"), nil)
|
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponseLockConflict("zzz111", "Lock Conflict"), nil)
|
||||||
|
|
||||||
httpAdapter.UnLock(w, req)
|
httpAdapter.UnLock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(409))
|
Expect(resp.StatusCode).To(Equal(409))
|
||||||
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLockFailureReason)).To(Equal("Lock Conflict"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Success", func() {
|
It("Success", func() {
|
||||||
@@ -330,11 +356,15 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return(connector.NewResponse(200), nil)
|
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return(
|
||||||
|
connector.NewResponseWithVersion(200,
|
||||||
|
&typesv1beta1.Timestamp{Seconds: uint64(1234), Nanos: uint32(567)},
|
||||||
|
), nil)
|
||||||
|
|
||||||
httpAdapter.UnLock(w, req)
|
httpAdapter.UnLock(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
Expect(resp.StatusCode).To(Equal(200))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiVersion)).To(Equal("v1234567"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -458,12 +488,14 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return(connector.NewResponseWithLock(409, "zzz111"), nil)
|
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return(
|
||||||
|
connector.NewResponseLockConflict("zzz111", "Lock Conflict"), nil)
|
||||||
|
|
||||||
httpAdapter.PutFile(w, req)
|
httpAdapter.PutFile(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(409))
|
Expect(resp.StatusCode).To(Equal(409))
|
||||||
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLockFailureReason)).To(Equal("Lock Conflict"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Success", func() {
|
It("Success", func() {
|
||||||
@@ -473,11 +505,18 @@ var _ = Describe("HttpAdapter", func() {
|
|||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return(connector.NewResponse(200), nil)
|
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return(
|
||||||
|
connector.NewResponseWithVersionAndLock(
|
||||||
|
200,
|
||||||
|
&typesv1beta1.Timestamp{Seconds: uint64(1234), Nanos: uint32(567)},
|
||||||
|
"abc123",
|
||||||
|
), nil)
|
||||||
|
|
||||||
httpAdapter.PutFile(w, req)
|
httpAdapter.PutFile(w, req)
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
Expect(resp.StatusCode).To(Equal(200))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("abc123"))
|
||||||
|
Expect(resp.Header.Get(connector.HeaderWopiVersion)).To(Equal("v1234567"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseWopiFileID extracts the file id from a wopi path
|
||||||
|
//
|
||||||
|
// If the file id is a jwt, it will be decoded and the file id will be extracted from the jwt claims.
|
||||||
|
// If the file id is not a jwt, it will be returned as is.
|
||||||
|
func ParseWopiFileID(cfg *config.Config, path string) string {
|
||||||
|
s := strings.Split(path, "/")
|
||||||
|
if len(s) < 4 || (s[1] != "wopi" && s[2] != "files") {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// check if the fileid is a jwt
|
||||||
|
if strings.Contains(s[3], ".") {
|
||||||
|
token, err := jwt.Parse(s[3], func(_ *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(cfg.Wopi.ProxySecret), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return s[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return s[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
f, ok := claims["f"].(string)
|
||||||
|
if !ok {
|
||||||
|
return s[3]
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
// fileid is not a jwt
|
||||||
|
return s[3]
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetVersionHeader sets a WOPI version header on the response writer
|
||||||
|
func SetVersionHeader(w http.ResponseWriter, t *typesv1beta1.Timestamp) {
|
||||||
|
// non-canonical headers can only be set directly on the header map
|
||||||
|
w.Header().Set("X-WOPI-ItemVersion", GetVersion(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns a string representation of the timestamp
|
||||||
|
func GetVersion(timestamp *typesv1beta1.Timestamp) string {
|
||||||
|
return "v" + strconv.FormatUint(timestamp.GetSeconds(), 10) +
|
||||||
|
strconv.FormatUint(uint64(timestamp.GetNanos()), 10)
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ package locks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LockParser is the interface that wraps the ParseLock method
|
// LockParser is the interface that wraps the ParseLock method
|
||||||
@@ -54,7 +53,7 @@ func (*NoopLockParser) ParseLock(id string) string {
|
|||||||
// If the JSON string is not in the expected format, the original lockID will be returned.
|
// If the JSON string is not in the expected format, the original lockID will be returned.
|
||||||
func (*LegacyLockParser) ParseLock(id string) string {
|
func (*LegacyLockParser) ParseLock(id string) string {
|
||||||
var decodedValues map[string]interface{}
|
var decodedValues map[string]interface{}
|
||||||
err := json.NewDecoder(strings.NewReader(id)).Decode(&decodedValues)
|
err := json.Unmarshal([]byte(id), &decodedValues)
|
||||||
if err != nil || len(decodedValues) == 0 {
|
if err != nil || len(decodedValues) == 0 {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package middleware_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMiddleware(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Middleware Suite")
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
@@ -27,7 +28,6 @@ func CollaborationTracingMiddleware(next http.Handler) http.Handler {
|
|||||||
wopiMethod := r.Header.Get("X-WOPI-Override")
|
wopiMethod := r.Header.Get("X-WOPI-Override")
|
||||||
|
|
||||||
wopiFile := wopiContext.FileReference
|
wopiFile := wopiContext.FileReference
|
||||||
wopiUser := wopiContext.User.GetId()
|
|
||||||
|
|
||||||
attrs := []attribute.KeyValue{
|
attrs := []attribute.KeyValue{
|
||||||
attribute.String("ocis.wopi.sessionid", r.Header.Get("X-WOPI-SessionId")),
|
attribute.String("ocis.wopi.sessionid", r.Header.Get("X-WOPI-SessionId")),
|
||||||
@@ -36,9 +36,14 @@ func CollaborationTracingMiddleware(next http.Handler) http.Handler {
|
|||||||
attribute.String("ocis.wopi.resource.id.opaque", wopiFile.GetResourceId().GetOpaqueId()),
|
attribute.String("ocis.wopi.resource.id.opaque", wopiFile.GetResourceId().GetOpaqueId()),
|
||||||
attribute.String("ocis.wopi.resource.id.space", wopiFile.GetResourceId().GetSpaceId()),
|
attribute.String("ocis.wopi.resource.id.space", wopiFile.GetResourceId().GetSpaceId()),
|
||||||
attribute.String("ocis.wopi.resource.path", wopiFile.GetPath()),
|
attribute.String("ocis.wopi.resource.path", wopiFile.GetPath()),
|
||||||
attribute.String("ocis.wopi.user.idp", wopiUser.GetIdp()),
|
}
|
||||||
attribute.String("ocis.wopi.user.opaque", wopiUser.GetOpaqueId()),
|
|
||||||
attribute.String("ocis.wopi.user.type", wopiUser.GetType().String()),
|
if wopiUser, ok := ctxpkg.ContextGetUser(r.Context()); ok {
|
||||||
|
attrs = append(attrs, []attribute.KeyValue{
|
||||||
|
attribute.String("ocis.wopi.user.idp", wopiUser.GetId().GetIdp()),
|
||||||
|
attribute.String("ocis.wopi.user.opaque", wopiUser.GetId().GetOpaqueId()),
|
||||||
|
attribute.String("ocis.wopi.user.type", wopiUser.GetId().GetType().String()),
|
||||||
|
}...)
|
||||||
}
|
}
|
||||||
span.SetAttributes(attrs...)
|
span.SetAttributes(attrs...)
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||||
|
rjwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
||||||
@@ -29,7 +28,6 @@ type WopiContext struct {
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
ViewOnlyToken string
|
ViewOnlyToken string
|
||||||
FileReference *providerv1beta1.Reference
|
FileReference *providerv1beta1.Reference
|
||||||
User *userv1beta1.User
|
|
||||||
ViewMode appproviderv1beta1.ViewMode
|
ViewMode appproviderv1beta1.ViewMode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +43,6 @@ type WopiContext struct {
|
|||||||
// * A contextual zerologger containing information about the request
|
// * A contextual zerologger containing information about the request
|
||||||
// and the WopiContext
|
// and the WopiContext
|
||||||
func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handler {
|
func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handler {
|
||||||
// compile a regexp here to extract the fileid from the URL
|
|
||||||
fileIDregexp := regexp.MustCompile(`^/wopi/files/([0-9a-f]{64})(/.*)?$`)
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
accessToken := r.URL.Query().Get("access_token")
|
accessToken := r.URL.Query().Get("access_token")
|
||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
@@ -76,11 +72,25 @@ func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handl
|
|||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tokenManager, err := rjwt.New(map[string]interface{}{
|
||||||
|
"secret": cfg.TokenManager.JWTSecret,
|
||||||
|
"expires": int64(24 * 60 * 60),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, _, err := tokenManager.DismantleToken(ctx, wopiContextAccessToken)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
claims.WopiContext.AccessToken = wopiContextAccessToken
|
claims.WopiContext.AccessToken = wopiContextAccessToken
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, wopiContextKey, claims.WopiContext)
|
ctx = context.WithValue(ctx, wopiContextKey, claims.WopiContext)
|
||||||
// authentication for the CS3 api
|
// authentication for the CS3 api
|
||||||
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, claims.WopiContext.AccessToken)
|
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, claims.WopiContext.AccessToken)
|
||||||
|
ctx = ctxpkg.ContextSetUser(ctx, user)
|
||||||
|
|
||||||
// include additional info in the context's logger
|
// include additional info in the context's logger
|
||||||
// we might need to check https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/common-headers
|
// we might need to check https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/common-headers
|
||||||
@@ -94,13 +104,13 @@ func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handl
|
|||||||
Str("WopiStamp", r.Header.Get("X-WOPI-TimeStamp")).
|
Str("WopiStamp", r.Header.Get("X-WOPI-TimeStamp")).
|
||||||
Str("FileReference", claims.WopiContext.FileReference.String()).
|
Str("FileReference", claims.WopiContext.FileReference.String()).
|
||||||
Str("ViewMode", claims.WopiContext.ViewMode.String()).
|
Str("ViewMode", claims.WopiContext.ViewMode.String()).
|
||||||
Str("Requester", claims.WopiContext.User.GetId().String()).
|
Str("Requester", user.GetId().String()).
|
||||||
Logger()
|
Logger()
|
||||||
ctx = wopiLogger.WithContext(ctx)
|
ctx = wopiLogger.WithContext(ctx)
|
||||||
|
|
||||||
hashedRef := helpers.HashResourceId(claims.WopiContext.FileReference.GetResourceId())
|
hashedRef := helpers.HashResourceId(claims.WopiContext.FileReference.GetResourceId())
|
||||||
matches := fileIDregexp.FindStringSubmatch(r.URL.Path)
|
fileID := helpers.ParseWopiFileID(cfg, r.URL.Path)
|
||||||
if len(matches) < 2 || matches[1] != hashedRef {
|
if fileID != hashedRef {
|
||||||
wopiLogger.Error().Msg("file reference in the URL doesn't match the one inside the access token")
|
wopiLogger.Error().Msg("file reference in the URL doesn't match the one inside the access token")
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package middleware_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||||
|
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||||
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
|
"github.com/cs3org/reva/v2/pkg/token"
|
||||||
|
rjwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/wopisrc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Wopi Context Middleware", func() {
|
||||||
|
var (
|
||||||
|
cfg *config.Config
|
||||||
|
ctx context.Context
|
||||||
|
mw http.Handler
|
||||||
|
rid *providerv1beta1.ResourceId
|
||||||
|
tknMngr token.Manager
|
||||||
|
user *userv1beta1.User
|
||||||
|
src *url.URL
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
cfg = &config.Config{
|
||||||
|
TokenManager: &config.TokenManager{JWTSecret: "jwtSecret"},
|
||||||
|
Wopi: config.Wopi{
|
||||||
|
Secret: "wopiSecret",
|
||||||
|
WopiSrc: "https://localhost:9300",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.Background()
|
||||||
|
|
||||||
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
mw = middleware.WopiContextAuthMiddleware(cfg, next)
|
||||||
|
|
||||||
|
tknMngr, err = rjwt.New(map[string]interface{}{
|
||||||
|
"secret": cfg.TokenManager.JWTSecret,
|
||||||
|
"expires": int64(24 * 60 * 60),
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
user = &userv1beta1.User{
|
||||||
|
Id: &userv1beta1.UserId{
|
||||||
|
Idp: "example.com",
|
||||||
|
OpaqueId: "12345",
|
||||||
|
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
|
||||||
|
},
|
||||||
|
Username: "admin",
|
||||||
|
Mail: "admin@example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
rid = &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageID",
|
||||||
|
OpaqueId: "opaqueID",
|
||||||
|
SpaceId: "spaceID",
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err = url.Parse(cfg.Wopi.WopiSrc)
|
||||||
|
src.Path = path.Join("wopi", "files", helpers.HashResourceId(rid))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("Should not authorize with empty access token", func() {
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusUnauthorized))
|
||||||
|
})
|
||||||
|
It("Should not authorize with malformed access token", func() {
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", "token")
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusUnauthorized))
|
||||||
|
})
|
||||||
|
It("Should not authorize when fileID mismatches", func() {
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
// create request with different fileID in the wopi context
|
||||||
|
token, err := tknMngr.MintToken(ctx, user, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
wopiContext := middleware.WopiContext{
|
||||||
|
AccessToken: token,
|
||||||
|
ViewMode: appprovider.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
|
FileReference: &providerv1beta1.Reference{
|
||||||
|
ResourceId: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageID",
|
||||||
|
OpaqueId: "opaqueID2",
|
||||||
|
SpaceId: "spaceID",
|
||||||
|
},
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, cfg)
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", wopiToken)
|
||||||
|
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusUnauthorized))
|
||||||
|
})
|
||||||
|
It("Should not authorize with wrong wopi secret", func() {
|
||||||
|
src.Path = path.Join("wopi", "files", helpers.HashResourceId(rid))
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
token, err := tknMngr.MintToken(ctx, user, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
wopiContext := middleware.WopiContext{
|
||||||
|
AccessToken: token,
|
||||||
|
}
|
||||||
|
// use wrong wopi secret when generating the wopi token
|
||||||
|
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, &config.Config{Wopi: config.Wopi{
|
||||||
|
Secret: "wrongSecret",
|
||||||
|
}})
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", wopiToken)
|
||||||
|
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusUnauthorized))
|
||||||
|
})
|
||||||
|
It("Should authorize successful", func() {
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
token, err := tknMngr.MintToken(ctx, user, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
wopiContext := middleware.WopiContext{
|
||||||
|
AccessToken: token,
|
||||||
|
ViewMode: appprovider.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
|
FileReference: &providerv1beta1.Reference{
|
||||||
|
ResourceId: rid,
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, cfg)
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", wopiToken)
|
||||||
|
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusOK))
|
||||||
|
})
|
||||||
|
It("Should not authorize with proxy when fileID mismatches", func() {
|
||||||
|
cfg.Wopi.ProxySecret = "proxySecret"
|
||||||
|
cfg.Wopi.ProxyURL = "https://proxy"
|
||||||
|
src, err := wopisrc.GenerateWopiSrc(helpers.HashResourceId(rid), cfg)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
token, err := tknMngr.MintToken(ctx, user, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
wopiContext := middleware.WopiContext{
|
||||||
|
AccessToken: token,
|
||||||
|
ViewMode: appprovider.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
|
FileReference: &providerv1beta1.Reference{
|
||||||
|
ResourceId: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "storageID",
|
||||||
|
OpaqueId: "opaqueID3",
|
||||||
|
SpaceId: "spaceID",
|
||||||
|
},
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, cfg)
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", wopiToken)
|
||||||
|
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusUnauthorized))
|
||||||
|
})
|
||||||
|
It("Should authorize successful with proxy", func() {
|
||||||
|
cfg.Wopi.ProxySecret = "proxySecret"
|
||||||
|
cfg.Wopi.ProxyURL = "https://proxy"
|
||||||
|
src, err := wopisrc.GenerateWopiSrc(helpers.HashResourceId(rid), cfg)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", src.String(), nil).WithContext(ctx)
|
||||||
|
token, err := tknMngr.MintToken(ctx, user, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
wopiContext := middleware.WopiContext{
|
||||||
|
AccessToken: token,
|
||||||
|
ViewMode: appprovider.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
|
FileReference: &providerv1beta1.Reference{
|
||||||
|
ResourceId: rid,
|
||||||
|
Path: ".",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, cfg)
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add("access_token", wopiToken)
|
||||||
|
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
mw.ServeHTTP(resp, req)
|
||||||
|
Expect(resp.Code).To(Equal(http.StatusOK))
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -121,6 +121,7 @@ func prepareRoutes(r *chi.Mux, options Options) {
|
|||||||
// authentication and wopi context
|
// authentication and wopi context
|
||||||
return colabmiddleware.WopiContextAuthMiddleware(options.Config, h)
|
return colabmiddleware.WopiContextAuthMiddleware(options.Config, h)
|
||||||
},
|
},
|
||||||
|
|
||||||
colabmiddleware.CollaborationTracingMiddleware,
|
colabmiddleware.CollaborationTracingMiddleware,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||||
"github.com/cs3org/reva/v2/pkg/utils"
|
"github.com/cs3org/reva/v2/pkg/utils"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/wopisrc"
|
||||||
|
|
||||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||||
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
@@ -109,7 +110,6 @@ func (s *Service) OpenInApp(
|
|||||||
AccessToken: req.GetAccessToken(), // it will be encrypted
|
AccessToken: req.GetAccessToken(), // it will be encrypted
|
||||||
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
|
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
|
||||||
FileReference: &providerFileRef,
|
FileReference: &providerFileRef,
|
||||||
User: user,
|
|
||||||
ViewMode: req.GetViewMode(),
|
ViewMode: req.GetViewMode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,11 +201,10 @@ func (s *Service) addQueryToURL(baseURL string, req *appproviderv1beta1.OpenInAp
|
|||||||
// so that all sessions on one file end on the same office server
|
// so that all sessions on one file end on the same office server
|
||||||
fileRef := helpers.HashResourceId(req.GetResourceInfo().GetId())
|
fileRef := helpers.HashResourceId(req.GetResourceInfo().GetId())
|
||||||
|
|
||||||
wopiSrcURL, err := url.Parse(s.config.Wopi.WopiSrc)
|
wopiSrcURL, err := wopisrc.GenerateWopiSrc(fileRef, s.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
wopiSrcURL.Path = path.Join("wopi", "files", fileRef)
|
|
||||||
|
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Add("WOPISrc", wopiSrcURL.String())
|
q.Add("WOPISrc", wopiSrcURL.String())
|
||||||
@@ -216,6 +215,38 @@ func (s *Service) addQueryToURL(baseURL string, req *appproviderv1beta1.OpenInAp
|
|||||||
|
|
||||||
lang := utils.ReadPlainFromOpaque(req.GetOpaque(), "lang")
|
lang := utils.ReadPlainFromOpaque(req.GetOpaque(), "lang")
|
||||||
|
|
||||||
|
// @TODO: this is a temporary solution until we figure out how to send these from oc web
|
||||||
|
switch lang {
|
||||||
|
case "bg":
|
||||||
|
lang = "bg-BG"
|
||||||
|
case "cs":
|
||||||
|
lang = "cs-CZ"
|
||||||
|
case "de":
|
||||||
|
lang = "de-DE"
|
||||||
|
case "en":
|
||||||
|
lang = "en-US"
|
||||||
|
case "es":
|
||||||
|
lang = "es-ES"
|
||||||
|
case "fr":
|
||||||
|
lang = "fr-FR"
|
||||||
|
case "gl":
|
||||||
|
lang = "gl-ES"
|
||||||
|
case "it":
|
||||||
|
lang = "it-IT"
|
||||||
|
case "nl":
|
||||||
|
lang = "nl-NL"
|
||||||
|
case "ko":
|
||||||
|
lang = "ko-KR"
|
||||||
|
case "sq":
|
||||||
|
lang = "sq-AL"
|
||||||
|
case "sv":
|
||||||
|
lang = "sv-SE"
|
||||||
|
case "tr":
|
||||||
|
lang = "tr-TR"
|
||||||
|
case "zh":
|
||||||
|
lang = "zh-CN"
|
||||||
|
}
|
||||||
|
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
switch strings.ToLower(s.config.App.Name) {
|
switch strings.ToLower(s.config.App.Name) {
|
||||||
case "collabora":
|
case "collabora":
|
||||||
|
|||||||
@@ -187,15 +187,60 @@ var _ = Describe("Discovery", func() {
|
|||||||
Entry("Microsoft chat no lang", "Microsoft", "", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
Entry("Microsoft chat no lang", "Microsoft", "", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||||
Entry("Collabora chat no lang", "Collabora", "", false, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
Entry("Collabora chat no lang", "Collabora", "", false, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||||
Entry("OnlyOffice chat no lang", "OnlyOffice", "", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
Entry("OnlyOffice chat no lang", "OnlyOffice", "", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||||
Entry("Microsoft chat lang", "Microsoft", "de", false, "https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=de&WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
Entry("Microsoft chat lang", "Microsoft", "de", false, "https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=de-DE&WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||||
Entry("Collabora chat lang", "Collabora", "de", false, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&lang=de"),
|
Entry("Collabora chat lang", "Collabora", "de", false, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&lang=de-DE"),
|
||||||
Entry("OnlyOffice chat lang", "OnlyOffice", "de", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&ui=de"),
|
Entry("OnlyOffice chat lang", "OnlyOffice", "de", false, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&ui=de-DE"),
|
||||||
Entry("Microsoft no chat no lang", "Microsoft", "", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
Entry("Microsoft no chat no lang", "Microsoft", "", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||||
Entry("Collabora no chat no lang", "Collabora", "", true, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
Entry("Collabora no chat no lang", "Collabora", "", true, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||||
Entry("OnlyOffice no chat no lang", "OnlyOffice", "", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
Entry("OnlyOffice no chat no lang", "OnlyOffice", "", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||||
Entry("Microsoft no chat lang", "Microsoft", "de", true, "https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=de&WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
Entry("Microsoft no chat lang", "Microsoft", "de", true, "https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=de-DE&WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||||
Entry("Collabora no chat lang", "Collabora", "de", true, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&lang=de"),
|
Entry("Collabora no chat lang", "Collabora", "de", true, "https://test.server.prv/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&lang=de-DE"),
|
||||||
Entry("OnlyOffice no chat lang", "OnlyOffice", "de", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&ui=de"),
|
Entry("OnlyOffice no chat lang", "OnlyOffice", "de", true, "https://test.server.prv/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&ui=de-DE"),
|
||||||
)
|
)
|
||||||
|
It("Success with Wopi Proxy", func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
nowTime := time.Now()
|
||||||
|
|
||||||
|
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
|
||||||
|
cfg.Wopi.Secret = "my_supa_secret"
|
||||||
|
cfg.Wopi.ProxyURL = "https://office.proxy.test.prv"
|
||||||
|
cfg.Wopi.ProxySecret = "your_supa_secret"
|
||||||
|
cfg.App.Name = "Microsoft"
|
||||||
|
|
||||||
|
myself := &userv1beta1.User{
|
||||||
|
Id: &userv1beta1.UserId{
|
||||||
|
Idp: "myIdp",
|
||||||
|
OpaqueId: "opaque001",
|
||||||
|
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
|
||||||
|
},
|
||||||
|
Username: "username",
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &appproviderv1beta1.OpenInAppRequest{
|
||||||
|
ResourceInfo: &providerv1beta1.ResourceInfo{
|
||||||
|
Id: &providerv1beta1.ResourceId{
|
||||||
|
StorageId: "myStorage",
|
||||||
|
OpaqueId: "storageOpaque001",
|
||||||
|
SpaceId: "SpaceA",
|
||||||
|
},
|
||||||
|
Path: "/path/to/file.docx",
|
||||||
|
},
|
||||||
|
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
||||||
|
AccessToken: MintToken(myself, cfg.Wopi.Secret, nowTime),
|
||||||
|
}
|
||||||
|
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "lang", "en")
|
||||||
|
|
||||||
|
gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Times(1).Return(&gatewayv1beta1.WhoAmIResponse{
|
||||||
|
Status: status.NewOK(ctx),
|
||||||
|
User: myself,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
resp, err := srv.OpenInApp(ctx, req)
|
||||||
|
Expect(err).To(Succeed())
|
||||||
|
Expect(resp.GetStatus().GetCode()).To(Equal(rpcv1beta1.Code_CODE_OK))
|
||||||
|
Expect(resp.GetAppUrl().GetMethod()).To(Equal("POST"))
|
||||||
|
Expect(resp.GetAppUrl().GetAppUrl()).To(Equal("https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=en-US&WOPISrc=https%3A%2F%2Foffice.proxy.test.prv%2Fwopi%2Ffiles%2FeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1IjoiaHR0cHM6Ly93b3Bpc2VydmVyLnRlc3QucHJ2L3dvcGkvZmlsZXMvIiwiZiI6IjJmNmVjMTg2OTZkZDEwMDgxMDY3NDliZDk0MTA2ZTVjZmFkNWMwOWUxNWRlN2I3NzA4OGQwMzg0M2U3MWI0M2UifQ.yfyLHZ18Z1MFOa6u7AP0LqfIiQ9X5AMkYauEZGhbCNs"))
|
||||||
|
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package wopisrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateWopiSrc generates a WOPI src URL for the given file reference.
|
||||||
|
// If a proxy URL and proxy secret are configured, the URL will be generated
|
||||||
|
// as a jwt token that is signed with the proxy secret and contains the file reference
|
||||||
|
// and the WOPI src URL.
|
||||||
|
// Example:
|
||||||
|
// https://cloud.proxy.com/wopi/files/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1IjoiaHR0cHM6Ly9vY2lzLnRlYW0vd29waS9maWxlcy8iLCJmIjoiMTIzNDU2In0.6ol9PQXGKktKfAri8tsJ4X_a9rIeosJ7id6KTQW6Ui0
|
||||||
|
//
|
||||||
|
// If no proxy URL and proxy secret are configured, the URL will be generated
|
||||||
|
// as a direct URL that contains the file reference.
|
||||||
|
// Example:
|
||||||
|
// https:/ocis.team/wopi/files/12312678470610632091729803710923
|
||||||
|
func GenerateWopiSrc(fileRef string, cfg *config.Config) (*url.URL, error) {
|
||||||
|
wopiSrcURL, err := url.Parse(cfg.Wopi.WopiSrc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if wopiSrcURL.Host == "" {
|
||||||
|
return nil, errors.New("invalid WopiSrc URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Wopi.ProxyURL != "" && cfg.Wopi.ProxySecret != "" {
|
||||||
|
return generateProxySrc(fileRef, cfg.Wopi.ProxyURL, cfg.Wopi.ProxySecret, wopiSrcURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateDirectSrc(fileRef, wopiSrcURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDirectSrc(fileRef string, wopiSrcURL *url.URL) (*url.URL, error) {
|
||||||
|
wopiSrcURL.Path = path.Join("wopi", "files", fileRef)
|
||||||
|
return wopiSrcURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateProxySrc(fileRef string, proxyUrl string, proxySecret string, wopiSrcURL *url.URL) (*url.URL, error) {
|
||||||
|
proxyURL, err := url.Parse(proxyUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if proxyURL.Host == "" {
|
||||||
|
return nil, errors.New("invalid proxy URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
wopiSrcURL.Path = path.Join("wopi", "files")
|
||||||
|
|
||||||
|
type tokenClaims struct {
|
||||||
|
URL string `json:"u"`
|
||||||
|
FileID string `json:"f"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenClaims{
|
||||||
|
FileID: fileRef,
|
||||||
|
// the string value from the URL package always ends with a slash
|
||||||
|
// the office365 proxy assumes that we have a trailing slash
|
||||||
|
URL: wopiSrcURL.String() + "/",
|
||||||
|
})
|
||||||
|
tokenString, err := token.SignedString([]byte(proxySecret))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
proxyURL.Path = path.Join("wopi", "files", tokenString)
|
||||||
|
return proxyURL, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package wopisrc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWopisrc(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Wopisrc Suite")
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package wopisrc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
|
||||||
|
"github.com/owncloud/ocis/v2/services/collaboration/pkg/wopisrc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Wopisrc Test", func() {
|
||||||
|
var (
|
||||||
|
c *config.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
Context("GenerateWopiSrc", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
c = &config.Config{
|
||||||
|
Wopi: config.Wopi{
|
||||||
|
WopiSrc: "https://ocis.team/wopi/files",
|
||||||
|
ProxyURL: "https://cloud.proxy.com",
|
||||||
|
ProxySecret: "secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
When("WopiSrc URL is incorrect", func() {
|
||||||
|
c = &config.Config{
|
||||||
|
Wopi: config.Wopi{
|
||||||
|
WopiSrc: "https:&//ocis.team/wopi/files",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url, err := wopisrc.GenerateWopiSrc("123456", c)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(url).To(BeNil())
|
||||||
|
})
|
||||||
|
When("proxy URL is incorrect", func() {
|
||||||
|
c = &config.Config{
|
||||||
|
Wopi: config.Wopi{
|
||||||
|
WopiSrc: "https://ocis.team/wopi/files",
|
||||||
|
ProxyURL: "cloud",
|
||||||
|
ProxySecret: "secret",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url, err := wopisrc.GenerateWopiSrc("123456", c)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(url).To(BeNil())
|
||||||
|
})
|
||||||
|
When("proxy URL and proxy secret are configured", func() {
|
||||||
|
It("should generate a WOPI src URL as a jwt token", func() {
|
||||||
|
url, err := wopisrc.GenerateWopiSrc("123456", c)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url.String()).To(Equal("https://cloud.proxy.com/wopi/files/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1IjoiaHR0cHM6Ly9vY2lzLnRlYW0vd29waS9maWxlcy8iLCJmIjoiMTIzNDU2In0.6ol9PQXGKktKfAri8tsJ4X_a9rIeosJ7id6KTQW6Ui0"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
When("proxy URL and proxy secret are not configured", func() {
|
||||||
|
It("should generate a WOPI src URL as a direct URL", func() {
|
||||||
|
c.Wopi.ProxyURL = ""
|
||||||
|
c.Wopi.ProxySecret = ""
|
||||||
|
url, err := wopisrc.GenerateWopiSrc("123456", c)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(url.String()).To(Equal("https://ocis.team/wopi/files/123456"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user