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 9e79d94 commit 2143dc2
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 22 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/ocm-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Support incoming OCM 1.0 shares

OCM 1.0 payloads are now supported as incoming shares, and
converted to the OCM 1.1 format for persistency and further processing.
Outgoing shares are still only OCM 1.1.

https://github.com/cs3org/reva/pull/4195
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 to fetch the WebDAV root at the remote host that sent the share, 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 2143dc2

Please sign in to comment.