Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a URL object in OpenInAppResponse #1968

Merged
merged 10 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/unreleased/appprovider-url-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Use a URL object in OpenInAppResponse

https://github.com/cs3org/reva/pull/1968
18 changes: 3 additions & 15 deletions cmd/reva/open-in-app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -54,7 +55,7 @@ func openInAppCommand() *command {
}
path := cmd.Args()[0]

vm := getViewMode(*viewMode)
vm := utils.GetViewMode(*viewMode)

client, err := getClient()
if err != nil {
Expand Down Expand Up @@ -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
}
}
2 changes: 2 additions & 0 deletions examples/ocmd/ocmd-server-1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,6 @@ prefix = "ocs"

[http.services.ocdav]

[http.services.appprovider]

[http.middlewares.cors]
1 change: 1 addition & 0 deletions examples/storage-references/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ appauth = "localhost:15000"
[http.services.ocmd]
[http.services.ocdav]
[http.services.ocs]
[http.services.appprovider]
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ require (
github.com/cheggaaa/pb v1.0.29
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/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8
github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8
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
Expand All @@ -37,6 +38,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/go-testing-interface v1.0.0 // indirect
Expand Down
9 changes: 7 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
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/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8 h1:bqUkE0l5wD62TKH6HkbSVYwyYzZ0PIeak/hnW7ggUdU=
github.com/cs3org/go-cs3apis v0.0.0-20210812121411-f18cf19614e8/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=
Expand Down Expand Up @@ -322,6 +324,9 @@ github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
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=
Expand Down
2 changes: 1 addition & 1 deletion internal/grpc/services/appregistry/appregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,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"}
}

}

Expand Down
129 changes: 77 additions & 52 deletions internal/http/services/appprovider/appprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,28 @@
// 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"
"github.com/cs3org/reva/internal/http/services/ocmd"
"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"
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions internal/http/services/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
9 changes: 6 additions & 3 deletions pkg/app/provider/demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("<iframe src=%s/open/%s?view-mode=%s&access-token=%s />", 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("<iframe src=%s/open/%s?view-mode=%s&access-token=%s />", 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) {
Expand Down
Loading