From be665a209447580e5b734e01e7a3408e5f6f531d Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Tue, 3 Aug 2021 16:02:37 +0200 Subject: [PATCH] Use a URL object in OpenInAppResponse --- .../unreleased/appprovider-url-object.md | 3 + cmd/reva/open-in-app.go | 18 +-- examples/ocmd/ocmd-server-1.toml | 2 + examples/storage-references/gateway.toml | 1 + go.mod | 2 + go.sum | 9 +- .../grpc/services/appregistry/appregistry.go | 2 +- .../storageprovider/storageprovider.go | 3 + .../http/services/appprovider/appprovider.go | 127 +++++++++++------- internal/http/services/loader/loader.go | 1 + pkg/app/app.go | 4 +- pkg/app/provider/demo/demo.go | 9 +- pkg/app/provider/wopi/wopi.go | 84 ++++++++++-- pkg/app/registry/static/static.go | 30 ++++- pkg/utils/utils.go | 15 +++ 15 files changed, 219 insertions(+), 91 deletions(-) create mode 100644 changelog/unreleased/appprovider-url-object.md diff --git a/changelog/unreleased/appprovider-url-object.md b/changelog/unreleased/appprovider-url-object.md new file mode 100644 index 00000000000..aac3d82f3cb --- /dev/null +++ b/changelog/unreleased/appprovider-url-object.md @@ -0,0 +1,3 @@ +Enhancement: Use a URL object in OpenInAppResponse + +https://github.com/cs3org/reva/pull/1968 diff --git a/cmd/reva/open-in-app.go b/cmd/reva/open-in-app.go index 7a6712dd395..feaa676664d 100644 --- a/cmd/reva/open-in-app.go +++ b/cmd/reva/open-in-app.go @@ -26,6 +26,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" ) @@ -54,7 +55,7 @@ func openInAppCommand() *command { } path := cmd.Args()[0] - vm := getViewMode(*viewMode) + vm := utils.GetViewMode(*viewMode) client, err := getClient() if err != nil { @@ -86,22 +87,9 @@ func openInAppCommand() *command { return formatError(openRes.Status) } - fmt.Println("App URL: " + openRes.AppUrl) + fmt.Printf("App URL: %+v\n", openRes.AppUrl) return nil } return cmd } - -func getViewMode(viewMode string) gateway.OpenInAppRequest_ViewMode { - switch viewMode { - case "view": - return gateway.OpenInAppRequest_VIEW_MODE_VIEW_ONLY - case "read": - return gateway.OpenInAppRequest_VIEW_MODE_READ_ONLY - case "write": - return gateway.OpenInAppRequest_VIEW_MODE_READ_WRITE - default: - return gateway.OpenInAppRequest_VIEW_MODE_INVALID - } -} diff --git a/examples/ocmd/ocmd-server-1.toml b/examples/ocmd/ocmd-server-1.toml index 9d69b7ccc7d..4902f18fbec 100644 --- a/examples/ocmd/ocmd-server-1.toml +++ b/examples/ocmd/ocmd-server-1.toml @@ -143,4 +143,6 @@ prefix = "ocs" [http.services.ocdav] +[http.services.appprovider] + [http.middlewares.cors] diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index a477512a5b6..10e58e90bd3 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -35,3 +35,4 @@ appauth = "localhost:15000" [http.services.ocmd] [http.services.ocdav] [http.services.ocs] +[http.services.appprovider] diff --git a/go.mod b/go.mod index aeefd821df3..bf7356f29c3 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/imdario/mergo v0.3.8 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/mattn/go-sqlite3 v1.14.8 + github.com/mileusna/useragent v1.0.2 github.com/minio/minio-go/v7 v7.0.12 github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/mapstructure v1.4.1 @@ -73,6 +74,7 @@ require ( go 1.16 replace ( + github.com/cs3org/go-cs3apis => github.com/ishank011/go-cs3apis v0.0.0-20210806135412-33c0570675bf github.com/eventials/go-tus => github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 google.golang.org/grpc => google.golang.org/grpc v1.26.0 // temporary downgrade diff --git a/go.sum b/go.sum index cf760b4ea4c..c730a09aa00 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20210802070913-970eec344e59 h1:cj9HxIbmbGn+HPpFP8nZ8oaNUsoFa0+cheCO8FUNoMc= -github.com/cs3org/go-cs3apis v0.0.0-20210802070913-970eec344e59/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -279,11 +277,8 @@ github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -312,6 +307,8 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ishank011/go-cs3apis v0.0.0-20210806135412-33c0570675bf h1:wn+wPv/i6zy20sf9PqOhLjfaRyj987uObXSRqeJfdDI= +github.com/ishank011/go-cs3apis v0.0.0-20210806135412-33c0570675bf/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= @@ -383,6 +380,8 @@ github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvr github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= +github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/minio-go/v7 v7.0.12 h1:/4pxUdwn9w0QEryNkrrWaodIESPRX+NxpO0Q6hVdaAA= diff --git a/internal/grpc/services/appregistry/appregistry.go b/internal/grpc/services/appregistry/appregistry.go index abbfdc1fac0..035f2400291 100644 --- a/internal/grpc/services/appregistry/appregistry.go +++ b/internal/grpc/services/appregistry/appregistry.go @@ -45,7 +45,7 @@ func (s *svc) Close() error { } func (s *svc) UnprotectedEndpoints() []string { - return []string{"/cs3.app.registry.v1beta1.RegistryAPI/AddAppProvider"} + return []string{"/cs3.app.registry.v1beta1.RegistryAPI/AddAppProvider", "/cs3.app.registry.v1beta1.RegistryAPI/ListSupportedMimeTypes"} } func (s *svc) Register(ss *grpc.Server) { diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 5ae6b1a6810..1ec7efe6492 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -94,6 +94,9 @@ func (c *config) init() { if len(c.AvailableXS) == 0 { c.AvailableXS = map[string]uint32{"md5": 100, "unset": 1000} } + if c.MimeTypes == nil || len(c.MimeTypes) == 0 { + c.MimeTypes = map[string]string{".zmd": "application/compressed-markdown"} + } } diff --git a/internal/http/services/appprovider/appprovider.go b/internal/http/services/appprovider/appprovider.go index bae42ca2c12..dda3419001f 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -23,11 +23,10 @@ import ( "encoding/base64" "encoding/json" "net/http" - "net/url" "strings" - "time" "unicode/utf8" + appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -35,7 +34,10 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/utils" + ua "github.com/mileusna/useragent" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -47,17 +49,13 @@ func init() { // Config holds the config options that need to be passed down to all ocdav handlers type Config struct { - Prefix string `mapstructure:"prefix"` - GatewaySvc string `mapstructure:"gatewaysvc"` - AccessTokenTTL int `mapstructure:"access_token_ttl"` + Prefix string `mapstructure:"prefix"` + GatewaySvc string `mapstructure:"gatewaysvc"` } func (c *Config) init() { if c.Prefix == "" { - c.Prefix = "api/v0/wopi/open" - } - if c.AccessTokenTTL == 0 { - c.AccessTokenTTL = 86400 + c.Prefix = "app" } c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) } @@ -91,28 +89,58 @@ func (s *svc) Prefix() string { } func (s *svc) Unprotected() []string { - return []string{} + return []string{"/list"} } func (s *svc) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - ocmd.WriteError(w, r, ocmd.APIErrorUnimplemented, "only GET requests are supported", errors.New("only GET requests are supported")) - return + var head string + head, r.URL.Path = router.ShiftPath(r.URL.Path) + + switch head { + case "list": + s.handleList(w, r) + case "open": + s.handleOpen(w, r) } - - s.handleWopiOpen(w, r) }) } -// WopiResponse holds the various fields to be returned for a wopi open call -type WopiResponse struct { - WopiClientURL string `json:"wopiclienturl"` - AccessToken string `json:"accesstoken"` - AccessTokenTTL int64 `json:"accesstokenttl"` +func (s *svc) handleList(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error getting grpc gateway client", err) + return + } + + listRes, err := client.ListSupportedMimeTypes(ctx, &appregistry.ListSupportedMimeTypesRequest{}) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error listing supported mime types", err) + return + } + if listRes.Status.Code != rpc.Code_CODE_OK { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error listing supported mime types", status.NewErrorFromCode(listRes.Status.Code, "appprovider")) + return + } + + mimeTypes := listRes.MimeTypes + filterAppsByUserAgent(mimeTypes, r.UserAgent()) + + js, err := json.Marshal(map[string]interface{}{"mime-types": mimeTypes}) + if err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error marshalling JSON response", err) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err = w.Write(js); err != nil { + ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error writing JSON response", err) + return + } } -func (s *svc) handleWopiOpen(w http.ResponseWriter, r *http.Request) { +func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) { ctx := r.Context() client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) @@ -121,15 +149,16 @@ func (s *svc) handleWopiOpen(w http.ResponseWriter, r *http.Request) { return } - info, errCode, err := s.getStatInfo(ctx, r.URL.Query().Get("fileId"), client) + info, errCode, err := s.getStatInfo(ctx, r.URL.Query().Get("file_id"), client) if err != nil { ocmd.WriteError(w, r, errCode, "error statting file", err) + return } openReq := gateway.OpenInAppRequest{ Ref: &provider.Reference{ResourceId: info.Id}, - ViewMode: getViewMode(info), - App: r.URL.Query().Get("app"), + ViewMode: getViewMode(info, r.URL.Query().Get("view_mode")), + App: r.URL.Query().Get("app_name"), } openRes, err := client.OpenInApp(ctx, &openReq) if err != nil { @@ -141,32 +170,7 @@ func (s *svc) handleWopiOpen(w http.ResponseWriter, r *http.Request) { return } - u, err := url.Parse(openRes.AppUrl) - if err != nil { - ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error parsing app URL", err) - return - } - q := u.Query() - - // remove access token from query parameters - accessToken := q.Get("access_token") - q.Del("access_token") - - // more options used by oC 10: - // &lang=en-GB - // &closebutton=1 - // &revisionhistory=1 - // &title=Hello.odt - u.RawQuery = q.Encode() - - js, err := json.Marshal( - WopiResponse{ - WopiClientURL: u.String(), - AccessToken: accessToken, - // https://wopi.readthedocs.io/projects/wopirest/en/latest/concepts.html#term-access-token-ttl - AccessTokenTTL: time.Now().Add(time.Second*time.Duration(s.conf.AccessTokenTTL)).UnixNano() / 1e6, - }, - ) + js, err := json.Marshal(openRes.AppUrl) if err != nil { ocmd.WriteError(w, r, ocmd.APIErrorServerError, "error marshalling JSON response", err) return @@ -179,6 +183,23 @@ func (s *svc) handleWopiOpen(w http.ResponseWriter, r *http.Request) { } } +func filterAppsByUserAgent(mimeTypes map[string]*appregistry.AppProviderList, userAgent string) { + ua := ua.Parse(userAgent) + if ua.Desktop { + return + } + + for m, providers := range mimeTypes { + apps := []*appregistry.ProviderInfo{} + for _, p := range providers.AppProviders { + if !p.DesktopOnly { + apps = append(apps, p) + } + } + mimeTypes[m] = &appregistry.AppProviderList{AppProviders: apps} + } +} + func (s *svc) getStatInfo(ctx context.Context, fileID string, client gateway.GatewayAPIClient) (*provider.ResourceInfo, ocmd.APIErrorCode, error) { if fileID == "" { return nil, ocmd.APIErrorInvalidParameter, errors.New("fileID parameter missing in request") @@ -215,7 +236,11 @@ func (s *svc) getStatInfo(ctx context.Context, fileID string, client gateway.Gat return statRes.Info, ocmd.APIErrorCode(""), nil } -func getViewMode(res *provider.ResourceInfo) gateway.OpenInAppRequest_ViewMode { +func getViewMode(res *provider.ResourceInfo, vm string) gateway.OpenInAppRequest_ViewMode { + if vm != "" { + return utils.GetViewMode(vm) + } + var viewMode gateway.OpenInAppRequest_ViewMode canEdit := res.PermissionSet.InitiateFileUpload canView := res.PermissionSet.InitiateFileDownload diff --git a/internal/http/services/loader/loader.go b/internal/http/services/loader/loader.go index 92e535d8763..73ac5e27197 100644 --- a/internal/http/services/loader/loader.go +++ b/internal/http/services/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load core HTTP services + _ "github.com/cs3org/reva/internal/http/services/appprovider" _ "github.com/cs3org/reva/internal/http/services/datagateway" _ "github.com/cs3org/reva/internal/http/services/dataprovider" _ "github.com/cs3org/reva/internal/http/services/helloworld" diff --git a/pkg/app/app.go b/pkg/app/app.go index 0e6c1f65a06..bb1319dc524 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -31,7 +31,7 @@ import ( type Registry interface { FindProviders(ctx context.Context, mimeType string) ([]*registry.ProviderInfo, error) ListProviders(ctx context.Context) ([]*registry.ProviderInfo, error) - ListSupportedMimeTypes(ctx context.Context) (map[string]*registry.AppProviderNameList, error) + ListSupportedMimeTypes(ctx context.Context) (map[string]*registry.AppProviderList, error) AddProvider(ctx context.Context, p *registry.ProviderInfo) error GetDefaultProviderForMimeType(ctx context.Context, mimeType string) (*registry.ProviderInfo, error) SetDefaultProviderForMimeType(ctx context.Context, mimeType string, p *registry.ProviderInfo) error @@ -40,6 +40,6 @@ type Registry interface { // Provider is the interface that application providers implement // for providing the URL of the app which will serve the requested resource. type Provider interface { - GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (string, error) + GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (*appprovider.OpenInAppURL, error) GetAppProviderInfo(ctx context.Context) (*registry.ProviderInfo, error) } diff --git a/pkg/app/provider/demo/demo.go b/pkg/app/provider/demo/demo.go index 4805f2b1b94..254dd868ae9 100644 --- a/pkg/app/provider/demo/demo.go +++ b/pkg/app/provider/demo/demo.go @@ -39,9 +39,12 @@ type demoProvider struct { iframeUIProvider string } -func (p *demoProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (string, error) { - msg := fmt.Sprintf("