Initial QSfera import
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/config"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/helpers"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
AppURLs *helpers.AppURLs
|
||||
GatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient]
|
||||
Store microstore.Store
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the Logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the Config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// AppURLs provides a function to set the AppURLs option.
|
||||
func AppURLs(val *helpers.AppURLs) Option {
|
||||
return func(o *Options) {
|
||||
o.AppURLs = val
|
||||
}
|
||||
}
|
||||
|
||||
// GatewaySelector provides a function to set the GatewaySelector option.
|
||||
func GatewaySelector(val pool.Selectable[gatewayv1beta1.GatewayAPIClient]) Option {
|
||||
return func(o *Options) {
|
||||
o.GatewaySelector = val
|
||||
}
|
||||
}
|
||||
|
||||
// Store proivdes a function to set the store
|
||||
func Store(val microstore.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Store = val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/config"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/helpers"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/middleware"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/wopisrc"
|
||||
)
|
||||
|
||||
// NewHandler creates a new grpc service implementing the OpenInApp interface
|
||||
func NewHandler(opts ...Option) (*Service, func(), error) {
|
||||
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...)
|
||||
|
||||
gatewaySelector := options.GatewaySelector
|
||||
var err error
|
||||
if gatewaySelector == nil {
|
||||
gatewaySelector, err = pool.GatewaySelector(options.Config.CS3Api.Gateway.Name)
|
||||
if err != nil {
|
||||
return nil, teardown, err
|
||||
}
|
||||
}
|
||||
|
||||
if options.AppURLs == nil {
|
||||
return nil, teardown, errors.New("AppURLs option is required")
|
||||
}
|
||||
|
||||
return &Service{
|
||||
id: options.Config.GRPC.Namespace + "." + options.Config.Service.Name,
|
||||
appURLs: options.AppURLs,
|
||||
logger: options.Logger,
|
||||
config: options.Config,
|
||||
gatewaySelector: gatewaySelector,
|
||||
store: options.Store,
|
||||
}, teardown, nil
|
||||
}
|
||||
|
||||
// Service implements the OpenInApp interface
|
||||
type Service struct {
|
||||
id string
|
||||
appURLs *helpers.AppURLs
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
gatewaySelector pool.Selectable[gatewayv1beta1.GatewayAPIClient]
|
||||
store microstore.Store
|
||||
}
|
||||
|
||||
// OpenInApp will implement the OpenInApp interface of the app provider
|
||||
func (s *Service) OpenInApp(
|
||||
ctx context.Context,
|
||||
req *appproviderv1beta1.OpenInAppRequest,
|
||||
) (*appproviderv1beta1.OpenInAppResponse, error) {
|
||||
|
||||
// get the current user
|
||||
var user *userv1beta1.User = nil
|
||||
meReq := &gatewayv1beta1.WhoAmIRequest{
|
||||
Token: req.GetAccessToken(),
|
||||
}
|
||||
gwc, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
s.logger.Error().Err(err).Msg("OpenInApp: could not select a gateway client")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meResp, err := gwc.WhoAmI(ctx, meReq)
|
||||
if err == nil {
|
||||
if meResp.GetStatus().GetCode() == rpcv1beta1.Code_CODE_OK {
|
||||
user = meResp.GetUser()
|
||||
}
|
||||
}
|
||||
|
||||
// required for the response, it will be used also for logs
|
||||
providerFileRef := providerv1beta1.Reference{
|
||||
ResourceId: req.GetResourceInfo().GetId(),
|
||||
Path: ".",
|
||||
}
|
||||
|
||||
logger := s.logger.With().
|
||||
Str("FileReference", providerFileRef.String()).
|
||||
Str("ViewMode", req.GetViewMode().String()).
|
||||
Str("Requester", user.GetId().String()).
|
||||
Logger()
|
||||
|
||||
// get the file extension to use the right wopi app url
|
||||
fileExt := path.Ext(req.GetResourceInfo().GetPath())
|
||||
|
||||
// get the appURL we need to use
|
||||
appURL := s.getAppUrl(fileExt, req.GetViewMode())
|
||||
if appURL == "" {
|
||||
logger.Error().Msg("OpenInApp: neither edit nor view app URL found")
|
||||
return nil, errors.New("neither edit nor view app URL found")
|
||||
}
|
||||
|
||||
// append the parameters we need
|
||||
appURL, err = s.addQueryToURL(appURL, req)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("OpenInApp: error parsing appUrl")
|
||||
return &appproviderv1beta1.OpenInAppResponse{
|
||||
Status: &rpcv1beta1.Status{
|
||||
Code: rpcv1beta1.Code_CODE_INVALID_ARGUMENT,
|
||||
Message: "OpenInApp: error parsing appUrl",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// create the wopiContext and generate the token
|
||||
wopiContext := middleware.WopiContext{
|
||||
AccessToken: req.GetAccessToken(), // it will be encrypted
|
||||
ViewOnlyToken: utils.ReadPlainFromOpaque(req.GetOpaque(), "viewOnlyToken"),
|
||||
FileReference: &providerFileRef,
|
||||
ViewMode: req.GetViewMode(),
|
||||
}
|
||||
|
||||
if templateID := utils.ReadPlainFromOpaque(req.GetOpaque(), "template"); templateID != "" {
|
||||
// we can ignore the error here, as we are sure that the templateID is not empty
|
||||
templateRes, _ := storagespace.ParseID(templateID)
|
||||
// we need to have at least both opaqueID and spaceID set
|
||||
if templateRes.GetOpaqueId() == "" || templateRes.GetSpaceId() == "" {
|
||||
logger.Error().Err(err).Msg("OpenInApp: error parsing templateID")
|
||||
return &appproviderv1beta1.OpenInAppResponse{
|
||||
Status: &rpcv1beta1.Status{
|
||||
Code: rpcv1beta1.Code_CODE_INVALID_ARGUMENT,
|
||||
Message: "OpenInApp: error parsing templateID",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
wopiContext.TemplateReference = &providerv1beta1.Reference{
|
||||
ResourceId: &templateRes,
|
||||
Path: ".",
|
||||
}
|
||||
}
|
||||
|
||||
accessToken, accessExpiration, err := middleware.GenerateWopiToken(wopiContext, s.config, s.store)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("OpenInApp: error generating the token")
|
||||
return &appproviderv1beta1.OpenInAppResponse{
|
||||
Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_INTERNAL},
|
||||
}, err
|
||||
}
|
||||
|
||||
logger.Debug().Msg("OpenInApp: success")
|
||||
|
||||
return &appproviderv1beta1.OpenInAppResponse{
|
||||
Status: &rpcv1beta1.Status{Code: rpcv1beta1.Code_CODE_OK},
|
||||
AppUrl: &appproviderv1beta1.OpenInAppURL{
|
||||
AppUrl: appURL,
|
||||
Method: "POST",
|
||||
FormParameters: map[string]string{
|
||||
// these parameters will be passed to the web server by the app provider application
|
||||
"access_token": accessToken,
|
||||
// milliseconds since Jan 1, 1970 UTC as required in https://docs.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/concepts#access_token_ttl
|
||||
//"access_token_ttl": strconv.FormatInt(claims.ExpiresAt.UnixMilli(), 10),
|
||||
"access_token_ttl": strconv.FormatInt(accessExpiration, 10),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getAppUrl will get the appURL that should be used based on the extension
|
||||
// and the provided view mode.
|
||||
// "view" urls will be chosen first, then if the view mode is "read/write",
|
||||
// "edit" urls will be prioritized. Note that "view" url might be returned for
|
||||
// "read/write" view mode if no "edit" url is found.
|
||||
func (s *Service) getAppUrl(fileExt string, viewMode appproviderv1beta1.ViewMode) string {
|
||||
// prioritize view action if possible
|
||||
appURL := s.appURLs.GetAppURLFor("view", fileExt)
|
||||
|
||||
if strings.ToLower(s.config.App.Product) == "collabora" {
|
||||
// collabora provides only one action per extension. usual options
|
||||
// are "view" (checked above), "edit" or "view_comment" (this last one
|
||||
// is exclusive of collabora)
|
||||
if appURL == "" {
|
||||
if editURL := s.appURLs.GetAppURLFor("edit", fileExt); editURL != "" {
|
||||
return editURL
|
||||
}
|
||||
if commentURL := s.appURLs.GetAppURLFor("view_comment", fileExt); commentURL != "" {
|
||||
return commentURL
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If not collabora, there might be an edit action for the extension.
|
||||
// If read/write mode has been requested, prioritize edit action.
|
||||
if viewMode == appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE {
|
||||
if editAppURL := s.appURLs.GetAppURLFor("edit", fileExt); editAppURL != "" {
|
||||
appURL = editAppURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return appURL
|
||||
}
|
||||
|
||||
// addQueryToURL will add specific query parameters to the baseURL. These
|
||||
// parameters are:
|
||||
// * "WOPISrc" pointing to the requested resource in the OpenInAppRequest
|
||||
// * "dchat" to disable the chat, based on configuration
|
||||
// * "lang" (WOPI app dependent) with the language in the request. "lang"
|
||||
// for collabora, "ui" for onlyoffice and "UI_LLCC" for the rest
|
||||
func (s *Service) addQueryToURL(baseURL string, req *appproviderv1beta1.OpenInAppRequest) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// build a urlsafe and stable file reference that can be used for proxy routing,
|
||||
// so that all sessions on one file end on the same office server
|
||||
fileRef := helpers.HashResourceId(req.GetResourceInfo().GetId())
|
||||
|
||||
wopiSrcURL, err := wopisrc.GenerateWopiSrc(fileRef, s.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Add("WOPISrc", wopiSrcURL.String())
|
||||
|
||||
if s.config.Wopi.DisableChat {
|
||||
q.Add("dchat", "1")
|
||||
}
|
||||
|
||||
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-GB"
|
||||
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 != "" {
|
||||
switch strings.ToLower(s.config.App.Product) {
|
||||
case "collabora":
|
||||
q.Add("lang", lang)
|
||||
case "onlyoffice":
|
||||
q.Add("ui", lang)
|
||||
default:
|
||||
q.Add("UI_LLCC", lang)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ToLower(s.config.App.Product) == "collabora" {
|
||||
q.Add("closebutton", "false")
|
||||
}
|
||||
|
||||
qs := q.Encode()
|
||||
u.RawQuery = qs
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Service Suite")
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/collaboration/mocks"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/config"
|
||||
"github.com/qsfera/server/services/collaboration/pkg/helpers"
|
||||
service "github.com/qsfera/server/services/collaboration/pkg/service/grpc/v0"
|
||||
)
|
||||
|
||||
// Based on https://github.com/cs3org/reva/blob/b99ad4865401144a981d4cfd1ae28b5a018ea51d/pkg/token/manager/jwt/jwt.go#L82
|
||||
func MintToken(u *userv1beta1.User, secret string, nowTime time.Time) string {
|
||||
scopes := make(map[string]*authpb.Scope)
|
||||
scopes["user"] = &authpb.Scope{
|
||||
Resource: &types.OpaqueEntry{
|
||||
Decoder: "json",
|
||||
Value: []byte("{\"Path\":\"/\"}"),
|
||||
},
|
||||
Role: authpb.Role_ROLE_OWNER,
|
||||
}
|
||||
|
||||
claims := jwt.MapClaims{
|
||||
"exp": nowTime.Add(5 * time.Hour).Unix(),
|
||||
"iss": "myself",
|
||||
"aud": "reva",
|
||||
"iat": nowTime.Unix(),
|
||||
"user": u,
|
||||
"scope": scopes,
|
||||
}
|
||||
/*
|
||||
claims := claims{
|
||||
StandardClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: time.Now().Add(5 * time.Hour),
|
||||
Issuer: "myself",
|
||||
Audience: "reva",
|
||||
IssuedAt: time.Now(),
|
||||
},
|
||||
User: u,
|
||||
Scope: scopes,
|
||||
}
|
||||
*/
|
||||
|
||||
t := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
|
||||
|
||||
tkn, _ := t.SignedString([]byte(secret))
|
||||
|
||||
return tkn
|
||||
}
|
||||
|
||||
var _ = Describe("Discovery", func() {
|
||||
var (
|
||||
cfg *config.Config
|
||||
gatewayClient *cs3mocks.GatewayAPIClient
|
||||
srv *service.Service
|
||||
srvTear func()
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
cfg = &config.Config{}
|
||||
gatewayClient = &cs3mocks.GatewayAPIClient{}
|
||||
|
||||
gatewaySelector := mocks.NewSelectable[gatewayv1beta1.GatewayAPIClient](GinkgoT())
|
||||
gatewaySelector.On("Next").Return(gatewayClient, nil)
|
||||
|
||||
appURLs := helpers.NewAppURLs()
|
||||
appURLs.Store(map[string]map[string]string{
|
||||
"view": {
|
||||
".pdf": "https://cloud.qsfera.test/hosting/wopi/word/view",
|
||||
".djvu": "https://cloud.qsfera.test/hosting/wopi/word/view",
|
||||
".docx": "https://cloud.qsfera.test/hosting/wopi/word/view",
|
||||
".xls": "https://cloud.qsfera.test/hosting/wopi/cell/view",
|
||||
".xlsb": "https://cloud.qsfera.test/hosting/wopi/cell/view",
|
||||
},
|
||||
"edit": {
|
||||
".docx": "https://cloud.qsfera.test/hosting/wopi/word/edit",
|
||||
".invalid": "://cloud.qsfera.test/hosting/wopi/cell/edit",
|
||||
},
|
||||
})
|
||||
|
||||
srv, srvTear, _ = service.NewHandler(
|
||||
service.Logger(log.NopLogger()),
|
||||
service.Config(cfg),
|
||||
service.AppURLs(appURLs),
|
||||
service.GatewaySelector(gatewaySelector),
|
||||
)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
srvTear()
|
||||
})
|
||||
|
||||
Describe("OpenInApp", func() {
|
||||
It("Invalid access token", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
cfg.Wopi.WopiSrc = "https://wopi.qsfera.test"
|
||||
|
||||
req := &appproviderv1beta1.OpenInAppRequest{
|
||||
ResourceInfo: &providerv1beta1.ResourceInfo{
|
||||
Id: &providerv1beta1.ResourceId{
|
||||
StorageId: "myStorage",
|
||||
OpaqueId: "storageOpaque001",
|
||||
SpaceId: "SpaceA",
|
||||
},
|
||||
Path: "/path/to/file",
|
||||
},
|
||||
ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE,
|
||||
AccessToken: "goodAccessToken",
|
||||
}
|
||||
|
||||
gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Times(1).Return(&gatewayv1beta1.WhoAmIResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
User: &userv1beta1.User{
|
||||
Id: &userv1beta1.UserId{
|
||||
Idp: "myIdp",
|
||||
OpaqueId: "opaque001",
|
||||
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
|
||||
},
|
||||
Username: "username",
|
||||
},
|
||||
}, nil)
|
||||
|
||||
resp, err := srv.OpenInApp(ctx, req)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(resp).To(BeNil())
|
||||
})
|
||||
|
||||
DescribeTable(
|
||||
"Success",
|
||||
func(appName, lang string, disableChat bool, expectedAppUrl string) {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "https://wopi.qsfera.test"
|
||||
cfg.Wopi.Secret = "my_supa_secret"
|
||||
cfg.Wopi.DisableChat = disableChat
|
||||
cfg.App.Name = appName
|
||||
cfg.App.Product = appName
|
||||
|
||||
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),
|
||||
}
|
||||
if lang != "" {
|
||||
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "lang", lang)
|
||||
}
|
||||
|
||||
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(expectedAppUrl))
|
||||
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||
},
|
||||
Entry("Microsoft chat no lang", "Microsoft", "", false, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||
Entry("Collabora chat no lang", "Collabora", "", false, "https://cloud.qsfera.test/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&closebutton=false"),
|
||||
Entry("OnlyOffice chat no lang", "OnlyOffice", "", false, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||
Entry("Microsoft chat lang", "Microsoft", "de", false, "https://cloud.qsfera.test/hosting/wopi/word/edit?UI_LLCC=de-DE&WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e"),
|
||||
Entry("Collabora chat lang", "Collabora", "de", false, "https://cloud.qsfera.test/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&closebutton=false&lang=de-DE"),
|
||||
Entry("OnlyOffice chat lang", "OnlyOffice", "de", false, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&ui=de-DE"),
|
||||
Entry("Microsoft no chat no lang", "Microsoft", "", true, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||
Entry("Collabora no chat no lang", "Collabora", "", true, "https://cloud.qsfera.test/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&closebutton=false&dchat=1"),
|
||||
Entry("OnlyOffice no chat no lang", "OnlyOffice", "", true, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||
Entry("Microsoft no chat lang", "Microsoft", "de", true, "https://cloud.qsfera.test/hosting/wopi/word/edit?UI_LLCC=de-DE&WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1"),
|
||||
Entry("Collabora no chat lang", "Collabora", "de", true, "https://cloud.qsfera.test/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&closebutton=false&dchat=1&lang=de-DE"),
|
||||
Entry("OnlyOffice no chat lang", "OnlyOffice", "de", true, "https://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&ui=de-DE"),
|
||||
)
|
||||
It("Success with Wopi Proxy", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "https://wopi.qsfera.test"
|
||||
cfg.Wopi.Secret = "my_supa_secret"
|
||||
cfg.Wopi.ProxyURL = "https://office.proxy.qsfera.test"
|
||||
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://cloud.qsfera.test/hosting/wopi/word/edit?UI_LLCC=en-GB&WOPISrc=https%3A%2F%2Foffice.proxy.qsfera.test%2Fwopi%2Ffiles%2FeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1IjoiaHR0cHM6Ly93b3BpLm9wZW5jbG91ZC50ZXN0L3dvcGkvZmlsZXMvIiwiZiI6IjJmNmVjMTg2OTZkZDEwMDgxMDY3NDliZDk0MTA2ZTVjZmFkNWMwOWUxNWRlN2I3NzA4OGQwMzg0M2U3MWI0M2UifQ.j873xu7TkqtIokSIQXW5y7-BRRrHgIURqAx4WY_zxTA"))
|
||||
Expect(resp.GetAppUrl().GetFormParameters()["access_token_ttl"]).To(Equal(strconv.FormatInt(nowTime.Add(5*time.Hour).Unix()*1000, 10)))
|
||||
})
|
||||
It("Fail with invalid app url", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "htttps://wopi.qsfera.test"
|
||||
cfg.Wopi.Secret = "my_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.invalid",
|
||||
},
|
||||
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_INVALID_ARGUMENT))
|
||||
Expect(resp.GetStatus().GetMessage()).To(Equal("OpenInApp: error parsing appUrl"))
|
||||
})
|
||||
It("Fail with invalid template id", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "htttps://wopi.qsfera.test"
|
||||
cfg.Wopi.Secret = "my_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")
|
||||
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "template", "&file_id")
|
||||
|
||||
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_INVALID_ARGUMENT))
|
||||
Expect(resp.GetStatus().GetMessage()).To(Equal("OpenInApp: error parsing templateID"))
|
||||
})
|
||||
It("Success with valid template id", func() {
|
||||
ctx := context.Background()
|
||||
nowTime := time.Now()
|
||||
|
||||
cfg.Wopi.WopiSrc = "htttps://wopi.qsfera.test"
|
||||
cfg.Wopi.Secret = "my_supa_secret"
|
||||
cfg.App.Name = "OnlyOffice"
|
||||
cfg.App.Product = "OnlyOffice"
|
||||
|
||||
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")
|
||||
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "template", "prodiderID$spaceID!opaqueID")
|
||||
|
||||
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://cloud.qsfera.test/hosting/wopi/word/edit?WOPISrc=htttps%3A%2F%2Fwopi.qsfera.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&ui=en-GB"))
|
||||
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