diff --git a/changelog/unreleased/appprovider-url-object.md b/changelog/unreleased/appprovider-url-object.md
new file mode 100644
index 0000000000..aac3d82f3c
--- /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 7a6712dd39..feaa676664 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 9d69b7ccc7..4902f18fbe 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 a477512a5b..10e58e90bd 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 aeefd821df..5843edee1d 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e
github.com/cs3org/go-cs3apis v0.0.0-20210802070913-970eec344e59
+ github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 // indirect
github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59
github.com/gdexlab/go-render v1.0.1
github.com/go-ldap/ldap/v3 v3.3.0
@@ -39,6 +40,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 +75,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 cf760b4ea4..47f173abb0 100644
--- a/go.sum
+++ b/go.sum
@@ -108,8 +108,8 @@ 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/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI=
+github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY=
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 +279,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 +309,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 +382,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 abbfdc1fac..035f240029 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 5ae6b1a681..60f24bb977 100644
--- a/internal/grpc/services/storageprovider/storageprovider.go
+++ b/internal/grpc/services/storageprovider/storageprovider.go
@@ -20,19 +20,15 @@ package storageprovider
import (
"context"
- "sort"
-
- // "encoding/json"
"fmt"
"net/url"
"os"
"path"
+ "sort"
"strconv"
"strings"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
-
- // link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
@@ -94,6 +90,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 bae42ca2c1..9b131e64d0 100644
--- a/internal/http/services/appprovider/appprovider.go
+++ b/internal/http/services/appprovider/appprovider.go
@@ -16,18 +16,17 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
-package ocmd
+package appprovider
import (
"context"
"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 92e535d876..73ac5e2719 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 0e6c1f65a0..bb1319dc52 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 4805f2b1b9..254dd868ae 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("", p.iframeUIProvider, resource.Id.StorageId+":"+resource.Id.OpaqueId, viewMode.String(), token)
- return msg, nil
+func (p *demoProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (*appprovider.OpenInAppURL, error) {
+ url := fmt.Sprintf("", p.iframeUIProvider, resource.Id.StorageId+":"+resource.Id.OpaqueId, viewMode.String(), token)
+ return &appprovider.OpenInAppURL{
+ AppUrl: url,
+ Method: "GET",
+ }, nil
}
func (p *demoProvider) GetAppProviderInfo(ctx context.Context) (*appregistry.ProviderInfo, error) {
diff --git a/pkg/app/provider/wopi/wopi.go b/pkg/app/provider/wopi/wopi.go
index 2562c9902a..96c0387c52 100644
--- a/pkg/app/provider/wopi/wopi.go
+++ b/pkg/app/provider/wopi/wopi.go
@@ -21,12 +21,15 @@ package demo
import (
"bytes"
"context"
+ "encoding/json"
"fmt"
"io"
+ "io/ioutil"
"net/http"
"net/url"
"os"
"path"
+ "strconv"
"strings"
"time"
@@ -37,9 +40,13 @@ import (
"github.com/cs3org/reva/pkg/app"
"github.com/cs3org/reva/pkg/app/provider/registry"
"github.com/cs3org/reva/pkg/appctx"
+ "github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/mime"
"github.com/cs3org/reva/pkg/rhttp"
+ "github.com/cs3org/reva/pkg/sharedconf"
+ "github.com/cs3org/reva/pkg/token"
"github.com/cs3org/reva/pkg/user"
+ "github.com/golang-jwt/jwt"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
@@ -52,9 +59,12 @@ type config struct {
IOPSecret string `mapstructure:"iop_secret" docs:";The IOP secret used to connect to the wopiserver."`
WopiURL string `mapstructure:"wopi_url" docs:";The wopiserver's URL."`
AppName string `mapstructure:"app_name" docs:";The App user-friendly name."`
+ AppIconURI string `mapstructure:"app_icon_uri" docs:";A URI to a static asset which represents the app icon."`
AppURL string `mapstructure:"app_url" docs:";The App URL."`
AppIntURL string `mapstructure:"app_int_url" docs:";The internal app URL in case of dockerized deployments. Defaults to AppURL"`
AppAPIKey string `mapstructure:"app_api_key" docs:";The API key used by the app, if applicable."`
+ JWTSecret string `mapstructure:"jwt_secret" docs:";The JWT secret to be used to retrieve the token TTL."`
+ AppDesktopOnly bool `mapstructure:"app_desktop_only" docs:";Whether the app can be opened only on desktop."`
InsecureConnections bool `mapstructure:"insecure_connections"`
}
@@ -86,6 +96,7 @@ func New(m map[string]interface{}) (app.Provider, error) {
if c.IOPSecret == "" {
c.IOPSecret = os.Getenv("REVA_APPPROVIDER_IOPSECRET")
}
+ c.JWTSecret = sharedconf.GetJWTSecret(c.JWTSecret)
appURLs, err := getAppURLs(c)
if err != nil {
@@ -107,19 +118,19 @@ func New(m map[string]interface{}) (app.Provider, error) {
}, nil
}
-func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (string, error) {
+func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, token string) (*appprovider.OpenInAppURL, error) {
log := appctx.GetLogger(ctx)
ext := path.Ext(resource.Path)
wopiurl, err := url.Parse(p.conf.WopiURL)
if err != nil {
- return "", err
+ return nil, err
}
wopiurl.Path = path.Join(wopiurl.Path, "/wopi/iop/openinapp")
httpReq, err := rhttp.NewRequest(ctx, "GET", wopiurl.String(), nil)
if err != nil {
- return "", err
+ return nil, err
}
q := httpReq.URL.Query()
@@ -156,17 +167,52 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc
openRes, err := p.wopiClient.Do(httpReq)
if err != nil {
- return "", errors.Wrap(err, "wopi: error performing open request to WOPI server")
+ return nil, errors.Wrap(err, "wopi: error performing open request to WOPI server")
}
defer openRes.Body.Close()
- if openRes.StatusCode != http.StatusFound {
- return "", errors.Wrap(err, "wopi: unexpected status from WOPI server: "+openRes.Status)
+ if openRes.StatusCode != http.StatusOK {
+ return nil, errtypes.InternalError("wopi: unexpected status from WOPI server: " + openRes.Status)
+ }
+
+ body, err := ioutil.ReadAll(openRes.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ var result map[string]interface{}
+ err = json.Unmarshal(body, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ tokenTTL, err := p.getAccessTokenTTL(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ appFullURL := result["app-url"].(string)
+
+ // Depending on whether wopi server returned any form parameters or not,
+ // we decide whether the request method is POST or GET
+ var formParams map[string]string
+ method := "GET"
+ if form, ok := result["form-parameters"].(map[string]interface{}); ok {
+ if tkn, ok := form["access_token"].(string); ok {
+ formParams = map[string]string{
+ "access_token": tkn,
+ "access_token_ttl": tokenTTL,
+ }
+ method = "POST"
+ }
}
- appFullURL := openRes.Header.Get("Location")
log.Info().Msg(fmt.Sprintf("wopi: returning app URL %s", appFullURL))
- return appFullURL, nil
+ return &appprovider.OpenInAppURL{
+ AppUrl: appFullURL,
+ Method: method,
+ FormParameters: formParams,
+ }, nil
}
func (p *wopiProvider) GetAppProviderInfo(ctx context.Context) (*appregistry.ProviderInfo, error) {
@@ -185,8 +231,10 @@ func (p *wopiProvider) GetAppProviderInfo(ctx context.Context) (*appregistry.Pro
}
return &appregistry.ProviderInfo{
- Name: p.conf.AppName,
- MimeTypes: mimeTypes,
+ Name: p.conf.AppName,
+ Icon: p.conf.AppIconURI,
+ DesktopOnly: p.conf.AppDesktopOnly,
+ MimeTypes: mimeTypes,
}, nil
}
@@ -255,6 +303,22 @@ func getAppURLs(c *config) (map[string]map[string]string, error) {
return appURLs, nil
}
+func (p *wopiProvider) getAccessTokenTTL(ctx context.Context) (string, error) {
+ tkn := token.ContextMustGetToken(ctx)
+ token, err := jwt.ParseWithClaims(tkn, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
+ return []byte(p.conf.JWTSecret), nil
+ })
+ if err != nil {
+ return "", err
+ }
+
+ if claims, ok := token.Claims.(*jwt.StandardClaims); ok && token.Valid {
+ return strconv.FormatInt(claims.ExpiresAt, 10), nil
+ }
+
+ return "", errtypes.InvalidCredentials("wopi: invalid token present in ctx")
+}
+
func parseWopiDiscovery(body io.Reader) (map[string]map[string]string, error) {
appURLs := make(map[string]map[string]string)
@@ -291,6 +355,9 @@ func parseWopiDiscovery(body io.Reader) (map[string]map[string]string, error) {
}
func getCodimdExtensions(appURL string) map[string]map[string]string {
+ // Register custom mime types
+ mime.RegisterMime(".zmd", "application/compressed-markdown")
+
appURLs := make(map[string]map[string]string)
appURLs["edit"] = map[string]string{
".txt": appURL,
diff --git a/pkg/app/registry/static/static.go b/pkg/app/registry/static/static.go
index 548452d4a0..0d975ba226 100644
--- a/pkg/app/registry/static/static.go
+++ b/pkg/app/registry/static/static.go
@@ -21,6 +21,7 @@ package static
import (
"context"
"strings"
+ "sync"
registrypb "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
"github.com/cs3org/reva/pkg/app"
@@ -65,6 +66,7 @@ type mimeTypeIndex struct {
type reg struct {
providers map[string]*registrypb.ProviderInfo
mimetypes map[string]*mimeTypeIndex // map the mime type to the addresses of the corresponding providers
+ sync.RWMutex
}
// New returns an implementation of the app.Registry interface.
@@ -99,6 +101,9 @@ func (b *reg) FindProviders(ctx context.Context, mimeType string) ([]*registrypb
// find longest match
var match string
+ b.RLock()
+ defer b.RUnlock()
+
for prefix := range b.mimetypes {
if strings.HasPrefix(mimeType, prefix) && len(prefix) > len(match) {
match = prefix
@@ -117,6 +122,9 @@ func (b *reg) FindProviders(ctx context.Context, mimeType string) ([]*registrypb
}
func (b *reg) AddProvider(ctx context.Context, p *registrypb.ProviderInfo) error {
+ b.Lock()
+ defer b.Unlock()
+
b.providers[p.Address] = p
for _, m := range p.MimeTypes {
@@ -130,6 +138,9 @@ func (b *reg) AddProvider(ctx context.Context, p *registrypb.ProviderInfo) error
}
func (b *reg) ListProviders(ctx context.Context) ([]*registrypb.ProviderInfo, error) {
+ b.RLock()
+ defer b.RUnlock()
+
providers := make([]*registrypb.ProviderInfo, 0, len(b.providers))
for _, p := range b.providers {
providers = append(providers, p)
@@ -137,14 +148,19 @@ func (b *reg) ListProviders(ctx context.Context) ([]*registrypb.ProviderInfo, er
return providers, nil
}
-func (b *reg) ListSupportedMimeTypes(ctx context.Context) (map[string]*registrypb.AppProviderNameList, error) {
- mimeTypes := make(map[string]*registrypb.AppProviderNameList)
+func (b *reg) ListSupportedMimeTypes(ctx context.Context) (map[string]*registrypb.AppProviderList, error) {
+ b.RLock()
+ defer b.RUnlock()
+
+ mimeTypes := make(map[string]*registrypb.AppProviderList)
for _, p := range b.providers {
+ t := *p
+ t.MimeTypes = nil
for _, m := range p.MimeTypes {
if _, ok := mimeTypes[m]; ok {
- mimeTypes[m].AppProviderName = append(mimeTypes[m].AppProviderName, p.Name)
+ mimeTypes[m].AppProviders = append(mimeTypes[m].AppProviders, &t)
} else {
- mimeTypes[m] = ®istrypb.AppProviderNameList{AppProviderName: []string{p.Name}}
+ mimeTypes[m] = ®istrypb.AppProviderList{AppProviders: []*registrypb.ProviderInfo{&t}}
}
}
}
@@ -152,6 +168,9 @@ func (b *reg) ListSupportedMimeTypes(ctx context.Context) (map[string]*registryp
}
func (b *reg) SetDefaultProviderForMimeType(ctx context.Context, mimeType string, p *registrypb.ProviderInfo) error {
+ b.Lock()
+ defer b.Unlock()
+
_, ok := b.mimetypes[mimeType]
if ok {
b.mimetypes[mimeType].defaultApp = p.Address
@@ -173,6 +192,9 @@ func (b *reg) SetDefaultProviderForMimeType(ctx context.Context, mimeType string
}
func (b *reg) GetDefaultProviderForMimeType(ctx context.Context, mimeType string) (*registrypb.ProviderInfo, error) {
+ b.RLock()
+ defer b.RUnlock()
+
_, ok := b.mimetypes[mimeType]
if ok {
p, ok := b.providers[b.mimetypes[mimeType].defaultApp]
diff --git a/pkg/mime/mime.go b/pkg/mime/mime.go
index b29d3552ff..3e8a084443 100644
--- a/pkg/mime/mime.go
+++ b/pkg/mime/mime.go
@@ -19,8 +19,9 @@
package mime
import (
- gomime "mime"
"path"
+
+ gomime "github.com/cubewise-code/go-mime"
)
const defaultMimeDir = "httpd/unix-directory"
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 13c28007dc..9d1405e6b4 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -29,6 +29,7 @@ import (
"strings"
"time"
+ gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
@@ -271,3 +272,17 @@ func UserTypeToString(accountType userpb.UserType) string {
}
return t
}
+
+// GetViewMode converts a human-readable string to a view mode for opening a resource in an app.
+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
+ }
+}