Skip to content

Commit

Permalink
Support incoming OCM 1.0 shares
Browse files Browse the repository at this point in the history
  • Loading branch information
glpatcern committed Sep 19, 2023
1 parent a9dc6d3 commit 354a46b
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 22 deletions.
2 changes: 1 addition & 1 deletion internal/http/services/ocmd/ocm.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func init() {

type config struct {
Prefix string `mapstructure:"prefix"`
GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"`
GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"`
ExposeRecipientDisplayName bool `mapstructure:"expose_recipient_display_name"`
}

Expand Down
30 changes: 24 additions & 6 deletions internal/http/services/ocmd/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,30 @@ func (p *Protocols) UnmarshalJSON(data []byte) error {
for name, d := range prot {
var res Protocol

// we do not support the OCM v1.0 properties for now, therefore just skip or bail out
if name == "name" {
continue
}
if name == "options" {
var opt map[string]any
if err := json.Unmarshal(d, &opt); err != nil || len(opt) > 0 {
return fmt.Errorf("protocol options not supported: %s", string(d))
if err := json.Unmarshal(d, &opt); err != nil {
return fmt.Errorf("malformed protocol options %s", d)
}
if len(opt) > 0 {
// This is an OCM 1.0 payload: parse the secret and assume max
// permissions, as in the OCM 1.0 model the remote server would check
// (and would not tell!) to the sharee which permissions are enabled
// on the share. Also, in this case the URL has to be resolved via
// discovery, see shares.go.
ss, ok := opt["sharedSecret"].(string)
if !ok {
return fmt.Errorf("missing sharedSecret from options %s", d)
}
res = &WebDAV{
SharedSecret: ss,
Permissions: []string{"read", "write", "share"},
URL: "",
}
*p = append(*p, res)
}
continue
}
Expand All @@ -145,15 +161,17 @@ func (p Protocols) MarshalJSON() ([]byte, error) {
}
d := make(map[string]any)
for _, prot := range p {
d[getProtocolName(prot)] = prot
d[GetProtocolName(prot)] = prot
}
// fill in the OCM v1.0 properties
// fill in the OCM v1.0 properties: for now we only create OCM 1.1 payloads,
// irrespective from the capabilities of the remote server.
d["name"] = "multi"
d["options"] = map[string]any{}
return json.Marshal(d)
}

func getProtocolName(p Protocol) string {
// GetProtocolName returns the name of the protocol by reflection.
func GetProtocolName(p Protocol) string {
n := reflect.TypeOf(p).String()
s := strings.Split(n, ".")
return strings.ToLower(s[len(s)-1])
Expand Down
16 changes: 13 additions & 3 deletions internal/http/services/ocmd/protocols_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,23 @@ func TestUnmarshalProtocol(t *testing.T) {
raw: `{"name":"foo","options":{ }}`,
expected: []Protocol{},
},
{
raw: `{"unsupported":{}}`,
err: "protocol unsupported not recognised",
},
{
raw: `{"name":"foo","options":{"unsupported":"value"}}`,
err: `protocol options not supported: {"unsupported":"value"}`,
err: `missing sharedSecret from options {"unsupported":"value"}`,
},
{
raw: `{"unsupported":{}}`,
err: "protocol unsupported not recognised",
raw: `{"name":"ocm10format","options":{"sharedSecret":"secret"}}`,
expected: []Protocol{
&WebDAV{
SharedSecret: "secret",
Permissions: []string{"read", "write", "share"},
URL: "",
},
},
},
{
raw: `{"name":"multi","options":{},"webdav":{"sharedSecret":"secret","permissions":["read","write"],"url":"http://example.org"}}`,
Expand Down
81 changes: 75 additions & 6 deletions internal/http/services/ocmd/shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,31 @@ package ocmd

import (
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net/http"
"path/filepath"
"strings"
"time"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/pkg/errors"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
ocmproviderhttp "github.com/cs3org/reva/internal/http/services/ocmprovider"

rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/cs3org/reva/internal/http/services/reqres"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/rhttp"
"github.com/cs3org/reva/pkg/utils"
"github.com/go-playground/validator/v10"
)
Expand Down Expand Up @@ -149,6 +155,12 @@ func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
return
}

protocols, err := getAndResolveProtocols(req.Protocols, r)
if err != nil {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
return
}

createShareReq := &ocmcore.CreateOCMCoreShareRequest{
Description: req.Description,
Name: req.Name,
Expand All @@ -158,7 +170,7 @@ func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
ShareWith: userRes.User.Id,
ResourceType: getResourceTypeFromOCMRequest(req.ResourceType),
ShareType: getOCMShareType(req.ShareType),
Protocols: getProtocols(req.Protocols),
Protocols: protocols,
}

if req.Expiration != 0 {
Expand Down Expand Up @@ -246,10 +258,67 @@ func getOCMShareType(t string) ocm.ShareType {
return ocm.ShareType_SHARE_TYPE_GROUP
}

func getProtocols(p Protocols) []*ocm.Protocol {
prot := make([]*ocm.Protocol, 0, len(p))
func getAndResolveProtocols(p Protocols, r *http.Request) ([]*ocm.Protocol, error) {
protos := make([]*ocm.Protocol, 0, len(p))
for _, data := range p {
prot = append(prot, data.ToOCMProtocol())
ocmProto := data.ToOCMProtocol()
if GetProtocolName(data) == "webdav" && ocmProto.GetWebdavOptions().Uri == "" {
// This is an OCM 1.0 payload with only webdav: we need to resolve the remote URL
remoteRoot, err := discoverOcmWebdavRoot(r)
if err != nil {
return nil, err
}
ocmProto.GetWebdavOptions().Uri = filepath.Join(remoteRoot, ocmProto.GetWebdavOptions().SharedSecret)
}
protos = append(protos, ocmProto)
}
return protos, nil
}

func discoverOcmWebdavRoot(r *http.Request) (string, error) {
// implements the OCM discovery logic and returns the OCM WebDAV root, see
// https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1ocm-provider/get
ctx := r.Context()
log := appctx.GetLogger(ctx)
log.Debug().Str("remoteHost", r.Host).Msg("received OCM 1.0 share payload, attempting to discover remote endpoint")

httpReq, err := rhttp.NewRequest(ctx, http.MethodGet, r.Host+"/ocm-provider", nil)
if err != nil {
return "", err
}
return prot
httpClient := rhttp.GetHTTPClient(
rhttp.Timeout(time.Duration(10 * int64(time.Second))),
)
httpRes, err := httpClient.Do(httpReq)
if err != nil {
return "", errors.Wrap(err, "failed to contact remote server")
}
defer httpRes.Body.Close()

if httpRes.StatusCode != http.StatusOK {
return "", errtypes.InternalError("Invalid HTTP response on OCM discovery")
}
body, err := io.ReadAll(httpRes.Body)
if err != nil {
return "", err
}

var result ocmproviderhttp.DiscoveryData
err = json.Unmarshal(body, &result)
if err != nil {
log.Warn().Str("remoteHost", r.Host).Str("response", string(body)).Msg("malformed response")
return "", errtypes.InternalError("Invalid payload on OCM discovery")
}

for _, t := range result.ResourceTypes {
webdavRoot, ok := t.Protocols["webdav"]
if ok {
// assume the first resourceType that exposes a webdav root is OK to use. As a matter of fact,
// no implementation exists yet that exposes multiple resource types with different roots.
return filepath.Join(result.Endpoint, webdavRoot), nil
}
}

log.Warn().Str("remoteHost", r.Host).Str("response", string(body)).Msg("Missing webdav root")
return "", errtypes.NotFound("WebDAV root not found on OCM discovery")
}
8 changes: 4 additions & 4 deletions internal/http/services/ocmprovider/ocmprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type config struct {
EnableDatatx bool `mapstructure:"enable_datatx" docs:"false;Whether data transfers are enabled in OCM shares."`
}

type discoveryData struct {
type DiscoveryData struct {
Enabled bool `json:"enabled" xml:"enabled"`
APIVersion string `json:"apiVersion" xml:"apiVersion"`
Endpoint string `json:"endPoint" xml:"endPoint"`
Expand All @@ -61,7 +61,7 @@ type resourceTypes struct {
}

type svc struct {
data *discoveryData
data *DiscoveryData
}

func (c *config) ApplyDefaults() {
Expand All @@ -85,9 +85,9 @@ func (c *config) ApplyDefaults() {
}
}

func (c *config) prepare() *discoveryData {
func (c *config) prepare() *DiscoveryData {
// generates the (static) data structure to be exposed by /ocm-provider
d := &discoveryData{}
d := &DiscoveryData{}
if c.Endpoint == "" {
d.Enabled = false
d.Endpoint = ""
Expand Down
4 changes: 2 additions & 2 deletions pkg/ocm/share/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import (
)

// NewWebDAVProtocol is an abstraction for creating a WebDAV protocol.
func NewWebDAVProtocol(uri, shareSecred string, perms *ocm.SharePermissions) *ocm.Protocol {
func NewWebDAVProtocol(uri, sharedSecret string, perms *ocm.SharePermissions) *ocm.Protocol {
return &ocm.Protocol{
Term: &ocm.Protocol_WebdavOptions{
WebdavOptions: &ocm.WebDAVProtocol{
Uri: uri,
SharedSecret: shareSecred,
SharedSecret: sharedSecret,
Permissions: perms,
},
},
Expand Down

0 comments on commit 354a46b

Please sign in to comment.