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

Support incoming OCM 1.0 shares #4195

Merged
merged 2 commits into from
Sep 21, 2023
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
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("sender", r.Host).Msg("received OCM 1.0 share, attempting to discover sender 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 OCM sender 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("sender", 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("sender", 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
3 changes: 0 additions & 3 deletions pkg/ocm/provider/authorizer/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"sync"

ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/ocm/provider"
"github.com/cs3org/reva/pkg/ocm/provider/authorizer/registry"
Expand Down Expand Up @@ -113,7 +112,6 @@ func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmpr
}

func (a *authorizer) IsProviderAllowed(ctx context.Context, pi *ocmprovider.ProviderInfo) error {
log := appctx.GetLogger(ctx)
var err error
normalizedDomain, err := normalizeDomain(pi.Domain)
if err != nil {
Expand Down Expand Up @@ -142,7 +140,6 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, pi *ocmprovider.Prov

var ocmHost string
for _, p := range a.providers {
log.Debug().Msgf("Comparing '%s' to '%s'", p.Domain, normalizedDomain)
if p.Domain == normalizedDomain {
ocmHost, err = a.getOCMHost(p)
if err != nil {
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