mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-16 00:59:37 -06:00
Merge pull request #9580 from dragonchaser/disable_collabora_chat
Add more wopi features, language, disable chat
This commit is contained in:
5
changelog/unreleased/missing-wopi-keys.md
Normal file
5
changelog/unreleased/missing-wopi-keys.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Add missing WOPI features
|
||||
|
||||
We added the feature to disable the chat for onlyoffice and added the missing language parameters to the wopi app url.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/9580
|
||||
@@ -62,7 +62,7 @@ type WOPIDriver struct {
|
||||
AppInternalURL string `yaml:"app_internal_url" env:"APP_PROVIDER_WOPI_APP_INTERNAL_URL" desc:"Internal URL to the app, like in your DMZ." introductionVersion:"pre5.0"`
|
||||
AppName string `yaml:"app_name" env:"APP_PROVIDER_WOPI_APP_NAME" desc:"Human readable app name." introductionVersion:"pre5.0"`
|
||||
AppURL string `yaml:"app_url" env:"APP_PROVIDER_WOPI_APP_URL" desc:"URL for end users to access the app." introductionVersion:"pre5.0"`
|
||||
AppDisableChat bool `yaml:"app_disable_chat" env:"APP_PROVIDER_WOPI_DISABLE_CHAT" desc:"Disable the chat functionality of the office app." introductionVersion:"pre5.0"`
|
||||
AppDisableChat bool `yaml:"app_disable_chat" env:"APP_PROVIDER_WOPI_DISABLE_CHAT;OCIS_WOPI_DISABLE_CHAT" desc:"Disable the chat functionality of the office app." introductionVersion:"pre5.0"`
|
||||
Insecure bool `yaml:"insecure" env:"APP_PROVIDER_WOPI_INSECURE" desc:"Disable TLS certificate validation for requests to the WOPI server and the web office application. Do not set this in production environments." introductionVersion:"pre5.0"`
|
||||
IopSecret string `yaml:"wopi_server_iop_secret" env:"APP_PROVIDER_WOPI_WOPI_SERVER_IOP_SECRET" desc:"Shared secret of the CS3org WOPI server." introductionVersion:"pre5.0"`
|
||||
WopiURL string `yaml:"wopi_server_external_url" env:"APP_PROVIDER_WOPI_WOPI_SERVER_EXTERNAL_URL" desc:"External url of the CS3org WOPI server." introductionVersion:"pre5.0"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
// Wopi defines the available configuration for the WOPI endpoint.
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
DisableChat bool `yaml:"disable_chat" env:"COLLABORATION_WOPI_DISABLE_CHAT;OCIS_WOPI_DISABLE_CHAT" desc:"Disable chat in the frontend." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (c *ContentConnector) GetFile(ctx context.Context, writer io.Writer) error
|
||||
|
||||
// Initiate download request
|
||||
req := &providerv1beta1.InitiateFileDownloadRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
}
|
||||
|
||||
if wopiContext.ViewMode == appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY && wopiContext.ViewOnlyToken != "" {
|
||||
@@ -206,7 +206,7 @@ func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, stream
|
||||
// We need a stat call on the target file in order to get both the lock
|
||||
// (if any) and the current size of the file
|
||||
statRes, err := c.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("PutFile: stat failed")
|
||||
@@ -254,7 +254,7 @@ func (c *ContentConnector) PutFile(ctx context.Context, stream io.Reader, stream
|
||||
|
||||
req := &providerv1beta1.InitiateFileUploadRequest{
|
||||
Opaque: opaque,
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
LockId: lockID,
|
||||
Options: &providerv1beta1.InitiateFileUploadRequest_IfMatch{
|
||||
IfMatch: statRes.GetInfo().GetEtag(),
|
||||
|
||||
@@ -44,7 +44,7 @@ var _ = Describe("ContentConnector", func() {
|
||||
|
||||
wopiCtx = middleware.WopiContext{
|
||||
AccessToken: "abcdef123456",
|
||||
FileReference: providerv1beta1.Reference{
|
||||
FileReference: &providerv1beta1.Reference{
|
||||
ResourceId: &providerv1beta1.ResourceId{
|
||||
StorageId: "abc",
|
||||
OpaqueId: "12345",
|
||||
@@ -178,7 +178,7 @@ var _ = Describe("ContentConnector", func() {
|
||||
wopiCtx = middleware.WopiContext{
|
||||
AccessToken: "abcdef123456",
|
||||
ViewOnlyToken: "view.only.123456",
|
||||
FileReference: providerv1beta1.Reference{
|
||||
FileReference: &providerv1beta1.Reference{
|
||||
ResourceId: &providerv1beta1.ResourceId{
|
||||
StorageId: "abc",
|
||||
OpaqueId: "12345",
|
||||
|
||||
@@ -89,7 +89,7 @@ func (f *FileConnector) GetLock(ctx context.Context) (string, error) {
|
||||
logger := zerolog.Ctx(ctx)
|
||||
|
||||
req := &providerv1beta1.GetLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
}
|
||||
|
||||
resp, err := f.gwc.GetLock(ctx, req)
|
||||
@@ -158,7 +158,7 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (str
|
||||
if oldLockID == "" {
|
||||
// If the oldLockID is empty, this is a "LOCK" request
|
||||
req := &providerv1beta1.SetLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
Lock: &providerv1beta1.Lock{
|
||||
LockId: lockID,
|
||||
AppName: f.cfg.App.LockName + "." + f.cfg.App.Name,
|
||||
@@ -179,7 +179,7 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (str
|
||||
// If the oldLockID isn't empty, this is a "UnlockAndRelock" request. We'll
|
||||
// do a "RefreshLock" in reva and provide the old lock
|
||||
req := &providerv1beta1.RefreshLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
Lock: &providerv1beta1.Lock{
|
||||
LockId: lockID,
|
||||
AppName: f.cfg.App.LockName + "." + f.cfg.App.Name,
|
||||
@@ -211,7 +211,7 @@ func (f *FileConnector) Lock(ctx context.Context, lockID, oldLockID string) (str
|
||||
// In both cases, we need to get the current lock to return it in a
|
||||
// 409 response if needed
|
||||
req := &providerv1beta1.GetLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
}
|
||||
|
||||
resp, err := f.gwc.GetLock(ctx, req)
|
||||
@@ -292,7 +292,7 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (string,
|
||||
}
|
||||
|
||||
req := &providerv1beta1.RefreshLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
Lock: &providerv1beta1.Lock{
|
||||
LockId: lockID,
|
||||
AppName: f.cfg.App.LockName + "." + f.cfg.App.Name,
|
||||
@@ -330,7 +330,7 @@ func (f *FileConnector) RefreshLock(ctx context.Context, lockID string) (string,
|
||||
// Either the file is unlocked or there is no lock
|
||||
// We need to return 409 with the current lock
|
||||
req := &providerv1beta1.GetLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
}
|
||||
|
||||
resp, err := f.gwc.GetLock(ctx, req)
|
||||
@@ -400,7 +400,7 @@ func (f *FileConnector) UnLock(ctx context.Context, lockID string) (string, erro
|
||||
}
|
||||
|
||||
req := &providerv1beta1.UnlockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
Lock: &providerv1beta1.Lock{
|
||||
LockId: lockID,
|
||||
AppName: f.cfg.App.LockName + "." + f.cfg.App.Name,
|
||||
@@ -424,7 +424,7 @@ func (f *FileConnector) UnLock(ctx context.Context, lockID string) (string, erro
|
||||
case rpcv1beta1.Code_CODE_LOCKED:
|
||||
// We need to return 409 with the current lock
|
||||
req := &providerv1beta1.GetLockRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
}
|
||||
|
||||
resp, err := f.gwc.GetLock(ctx, req)
|
||||
@@ -485,7 +485,7 @@ func (f *FileConnector) CheckFileInfo(ctx context.Context) (fileinfo.FileInfo, e
|
||||
logger := zerolog.Ctx(ctx)
|
||||
|
||||
statRes, err := f.gwc.Stat(ctx, &providerv1beta1.StatRequest{
|
||||
Ref: &wopiContext.FileReference,
|
||||
Ref: wopiContext.FileReference,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("CheckFileInfo: stat failed")
|
||||
|
||||
@@ -39,7 +39,7 @@ var _ = Describe("FileConnector", func() {
|
||||
|
||||
wopiCtx = middleware.WopiContext{
|
||||
AccessToken: "abcdef123456",
|
||||
FileReference: providerv1beta1.Reference{
|
||||
FileReference: &providerv1beta1.Reference{
|
||||
ResourceId: &providerv1beta1.ResourceId{
|
||||
StorageId: "abc",
|
||||
OpaqueId: "12345",
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
type WopiContext struct {
|
||||
AccessToken string
|
||||
ViewOnlyToken string
|
||||
FileReference providerv1beta1.Reference
|
||||
FileReference *providerv1beta1.Reference
|
||||
User *userv1beta1.User
|
||||
ViewMode appproviderv1beta1.ViewMode
|
||||
EditAppUrl string
|
||||
|
||||
@@ -25,7 +25,10 @@ import (
|
||||
|
||||
// NewHandler creates a new grpc service implementing the OpenInApp interface
|
||||
func NewHandler(opts ...Option) (*Service, func(), error) {
|
||||
teardown := func() {}
|
||||
teardown := func() {
|
||||
/* this is required as a argument for the return value to satisfy the interface */
|
||||
/* in case you are wondering about the necessity of this comment, sonarcloud is asking for it */
|
||||
}
|
||||
options := newOptions(opts...)
|
||||
|
||||
gwc := options.Gwc
|
||||
@@ -93,18 +96,18 @@ func (s *Service) OpenInApp(
|
||||
var viewAppURL string
|
||||
var editAppURL string
|
||||
if viewCommentAppURLs, ok := s.appURLs["view_comment"]; ok {
|
||||
if url := viewCommentAppURLs[fileExt]; ok {
|
||||
viewCommentAppURL = url
|
||||
if u, ok := viewCommentAppURLs[fileExt]; ok {
|
||||
viewCommentAppURL = u
|
||||
}
|
||||
}
|
||||
if viewAppURLs, ok := s.appURLs["view"]; ok {
|
||||
if url := viewAppURLs[fileExt]; ok {
|
||||
viewAppURL = url
|
||||
if u, ok := viewAppURLs[fileExt]; ok {
|
||||
viewAppURL = u
|
||||
}
|
||||
}
|
||||
if editAppURLs, ok := s.appURLs["edit"]; ok {
|
||||
if url, ok := editAppURLs[fileExt]; ok {
|
||||
editAppURL = url
|
||||
if u, ok := editAppURLs[fileExt]; ok {
|
||||
editAppURL = u
|
||||
}
|
||||
}
|
||||
if editAppURL == "" && viewAppURL == "" && viewCommentAppURL == "" {
|
||||
@@ -151,6 +154,18 @@ func (s *Service) OpenInApp(
|
||||
|
||||
q := u.Query()
|
||||
q.Add("WOPISrc", wopiSrcURL.String())
|
||||
|
||||
if s.config.Wopi.DisableChat {
|
||||
q.Add("dchat", "1")
|
||||
}
|
||||
|
||||
lang := utils.ReadPlainFromOpaque(req.GetOpaque(), "lang")
|
||||
|
||||
if lang != "" {
|
||||
q.Add("ui", lang) // OnlyOffice
|
||||
q.Add("lang", lang) // Collabora, Impact on the default document language of OnlyOffice
|
||||
q.Add("UI_LLCC", lang) // Office365
|
||||
}
|
||||
qs := q.Encode()
|
||||
u.RawQuery = qs
|
||||
|
||||
@@ -199,7 +214,7 @@ func (s *Service) OpenInApp(
|
||||
wopiContext := middleware.WopiContext{
|
||||
AccessToken: cryptedReqAccessToken,
|
||||
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
|
||||
FileReference: providerFileRef,
|
||||
FileReference: &providerFileRef,
|
||||
User: user,
|
||||
ViewMode: req.GetViewMode(),
|
||||
EditAppUrl: editAppURL,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -163,6 +164,7 @@ var _ = Describe("Discovery", func() {
|
||||
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
||||
AccessToken: MintToken(myself, cfg.Wopi.Secret, nowTime),
|
||||
}
|
||||
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "lang", "de")
|
||||
|
||||
gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Times(1).Return(&gatewayv1beta1.WhoAmIResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
@@ -173,7 +175,93 @@ var _ = Describe("Discovery", func() {
|
||||
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?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"))
|
||||
Expect(resp.GetAppUrl().GetAppUrl()).To(Equal("https://test.server.prv/hosting/wopi/word/edit?UI_LLCC=de&WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&lang=de&ui=de"))
|
||||
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||
})
|
||||
|
||||
It("Success", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
|
||||
cfg.Wopi.Secret = "my_supa_secret"
|
||||
cfg.Wopi.DisableChat = true
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"))
|
||||
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||
})
|
||||
|
||||
It("Success", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
|
||||
cfg.Wopi.Secret = "my_supa_secret"
|
||||
cfg.Wopi.DisableChat = true
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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?WOPISrc=https%3A%2F%2Fwopiserver.test.prv%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"))
|
||||
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user