Skip to content

Commit

Permalink
Remote open in app (#3683)
Browse files Browse the repository at this point in the history
* implement open in app in sciencemesh

* add webapp template

* fix url creatioN

* improved error message when share does not exist

* add changelog
  • Loading branch information
gmgigi96 authored Mar 6, 2023
1 parent f60f2f4 commit a83cc99
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 11 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/ocm-remote-apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Remote open in app in OCM

https://github.com/cs3org/reva/pull/3683
45 changes: 34 additions & 11 deletions internal/grpc/services/ocmshareprovider/ocmshareprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"fmt"
"net/url"
"path/filepath"
"strings"
"text/template"
"time"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
Expand Down Expand Up @@ -60,13 +62,15 @@ type config struct {
GatewaySVC string `mapstructure:"gatewaysvc"`
WebDAVPrefix string `mapstructure:"webdav_prefix"`
ProviderDomain string `mapstructure:"provider_domain" docs:"The same domain registered in the provider authorizer"`
WebappTemplate string `mapstructure:"webapp_template"`
}

type service struct {
conf *config
repo share.Repository
client *client.OCMClient
gateway gateway.GatewayAPIClient
conf *config
repo share.Repository
client *client.OCMClient
gateway gateway.GatewayAPIClient
webappTmpl *template.Template
}

func (c *config) init() {
Expand All @@ -76,6 +80,9 @@ func (c *config) init() {
if c.ClientTimeout == 0 {
c.ClientTimeout = 10
}
if c.WebappTemplate == "" {
c.WebappTemplate = "https://cernbox.cern.ch/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}"
}

c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC)
}
Expand Down Expand Up @@ -123,11 +130,17 @@ func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
return nil, err
}

tpl, err := template.New("webapp_template").Parse(c.WebappTemplate)
if err != nil {
return nil, err
}

service := &service{
conf: c,
repo: repo,
client: client,
gateway: gateway,
conf: c,
repo: repo,
client: client,
gateway: gateway,
webappTmpl: tpl,
}

return service, nil
Expand Down Expand Up @@ -190,14 +203,24 @@ func (s *service) getWebdavProtocol(ctx context.Context, info *providerpb.Resour
}
}

func (s *service) getProtocols(ctx context.Context, info *providerpb.ResourceInfo, methods []*ocm.AccessMethod) ocmd.Protocols {
func (s *service) getWebappProtocol(share *ocm.Share) *ocmd.Webapp {
var b strings.Builder
if err := s.webappTmpl.Execute(&b, share); err != nil {
panic(err)
}
return &ocmd.Webapp{
URITemplate: b.String(),
}
}

func (s *service) getProtocols(ctx context.Context, share *ocm.Share, info *providerpb.ResourceInfo, methods []*ocm.AccessMethod) ocmd.Protocols {
var p ocmd.Protocols
for _, m := range methods {
switch t := m.Term.(type) {
case *ocm.AccessMethod_WebdavOptions:
p = append(p, s.getWebdavProtocol(ctx, info, t))
case *ocm.AccessMethod_WebappOptions:
// TODO
p = append(p, s.getWebappProtocol(share))
case *ocm.AccessMethod_TransferOptions:
// TODO
}
Expand Down Expand Up @@ -285,7 +308,7 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq
SenderDisplayName: user.DisplayName,
ShareType: "user",
ResourceType: getResourceType(info),
Protocols: s.getProtocols(ctx, info, req.AccessMethods),
Protocols: s.getProtocols(ctx, ocmshare, info, req.AccessMethods),
}

if req.Expiration != nil {
Expand Down
139 changes: 139 additions & 0 deletions internal/http/services/sciencemesh/apps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2018-2023 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package sciencemesh

import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
ocmpb "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
"github.com/cs3org/reva/internal/http/services/reqres"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/rhttp/router"
)

type appsHandler struct {
gatewayClient gateway.GatewayAPIClient
ocmMountPoint string
}

func (h *appsHandler) init(c *config) error {
var err error
h.gatewayClient, err = pool.GetGatewayServiceClient(pool.Endpoint(c.GatewaySvc))
if err != nil {
return err
}
h.ocmMountPoint = c.OCMMountPoint

return nil
}

func (h *appsHandler) shareInfo(p string) (*ocmpb.ShareId, string) {
p = strings.TrimPrefix(p, h.ocmMountPoint)
shareID, rel := router.ShiftPath(p)
if len(rel) > 0 {
rel = rel[1:]
}
return &ocmpb.ShareId{OpaqueId: shareID}, rel
}

func (h *appsHandler) OpenInApp(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

if err := r.ParseForm(); err != nil {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "parameters could not be parsed", nil)
return
}

path := r.Form.Get("file")
if path == "" {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing file", nil)
return
}

shareID, rel := h.shareInfo(path)

template, err := h.webappTemplate(ctx, shareID)
if err != nil {
var e errtypes.NotFound
if errors.As(err, &e) {
reqres.WriteError(w, r, reqres.APIErrorNotFound, e.Error(), nil)
}
reqres.WriteError(w, r, reqres.APIErrorServerError, err.Error(), err)
return
}

url := resolveTemplate(template, rel)

if err := json.NewEncoder(w).Encode(map[string]any{
"app_url": url,
}); err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error marshalling JSON response", err)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}

func (h *appsHandler) webappTemplate(ctx context.Context, id *ocmpb.ShareId) (string, error) {
res, err := h.gatewayClient.GetReceivedOCMShare(ctx, &ocmpb.GetReceivedOCMShareRequest{
Ref: &ocmpb.ShareReference{
Spec: &ocmpb.ShareReference_Id{
Id: id,
},
},
})
if err != nil {
return "", err
}
if res.Status.Code != rpcv1beta1.Code_CODE_OK {
if res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND {
return "", errtypes.NotFound(res.Status.Message)
}
return "", errtypes.InternalError(res.Status.Message)
}

webapp, ok := getWebappProtocol(res.Share.Protocols)
if !ok {
return "", errtypes.BadRequest("share does not contain webapp protocol")
}

return webapp.UriTemplate, nil
}

func getWebappProtocol(protocols []*ocmpb.Protocol) (*ocmpb.WebappProtocol, bool) {
for _, p := range protocols {
if t, ok := p.Term.(*ocmpb.Protocol_WebappOptions); ok {
return t.WebappOptions, true
}
}
return nil, false
}

func resolveTemplate(template string, rel string) string {
// the template is of type "https://open-cloud-mesh.org/s/share-hash/{relative-path-to-shared-resource}"
return strings.Replace(template, "{relative-path-to-shared-resource}", rel, 1)
}
7 changes: 7 additions & 0 deletions internal/http/services/sciencemesh/sciencemesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type config struct {
ProviderDomain string `mapstructure:"provider_domain"`
SubjectTemplate string `mapstructure:"subject_template"`
BodyTemplatePath string `mapstructure:"body_template_path"`
OCMMountPoint string `mapstructure:"ocm_mount_point"`
}

func (c *config) init() {
Expand All @@ -94,11 +95,17 @@ func (s *svc) routerInit() error {
return err
}

appsHandler := new(appsHandler)
if err := appsHandler.init(s.conf); err != nil {
return err
}

s.router.Get("/generate-invite", tokenHandler.Generate)
s.router.Get("/list-invite", tokenHandler.ListInvite)
s.router.Post("/accept-invite", tokenHandler.AcceptInvite)
s.router.Get("/find-accepted-users", tokenHandler.FindAccepted)
s.router.Get("/list-providers", providersHandler.ListProviders)
s.router.Post("/open-in-app", appsHandler.OpenInApp)

return nil
}
Expand Down

0 comments on commit a83cc99

Please sign in to comment.