Skip to content

Commit

Permalink
ocm: fixed discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
glpatcern committed Feb 15, 2025
1 parent 35b14f4 commit 8cf76ad
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 29 deletions.
49 changes: 34 additions & 15 deletions internal/http/services/opencloudmesh/ocmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,37 +70,56 @@ func NewClient(timeout time.Duration, insecure bool) *OCMClient {
// Discover returns a number of properties used to discover the capabilities offered by a remote cloud storage.
// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1ocm-provider/get
func (c *OCMClient) Discover(ctx context.Context, endpoint string) (*wellknown.OcmDiscoveryData, error) {
url, err := url.JoinPath(endpoint, "/ocm-provider")
log := appctx.GetLogger(ctx)

remoteurl, _ := url.JoinPath(endpoint, "/.well-known/ocm")
body, err := c.discover(ctx, remoteurl)
if err != nil || len(body) == 0 {
log.Debug().Err(err).Str("sender", remoteurl).Str("response", string(body)).Msg("invalid or empty response, falling back to legacy discovery")
remoteurl, _ := url.JoinPath(endpoint, "/ocm-provider") // legacy discovery endpoint

body, err = c.discover(ctx, remoteurl)
if err != nil || len(body) == 0 {
log.Warn().Err(err).Str("sender", remoteurl).Str("response", string(body)).Msg("invalid or empty response")
return nil, errtypes.BadRequest("Invalid response on OCM discovery")
}
}

var disco wellknown.OcmDiscoveryData
err = json.Unmarshal(body, &disco)
if err != nil {
return nil, err
log.Warn().Err(err).Str("sender", remoteurl).Str("response", string(body)).Msg("malformed response")
return nil, errtypes.BadRequest("Invalid payload on OCM discovery")
}

log.Debug().Str("sender", remoteurl).Any("response", disco).Msg("discovery response")
return &disco, nil
}

func (c *OCMClient) discover(ctx context.Context, url string) ([]byte, error) {
log := appctx.GetLogger(ctx)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, errors.Wrap(err, "error creating request")
return nil, errors.Wrap(err, "error creating OCM discovery request")
}
req.Header.Set("Content-Type", "application/json")

resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "error doing request")
return nil, errors.Wrap(err, "error doing OCM discovery request")
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
if resp.StatusCode != http.StatusOK {
log.Warn().Str("sender", url).Any("response", resp).Int("status", resp.StatusCode).Msg("discovery returned")
return nil, errtypes.BadRequest("Remote does not offer a valid OCM discovery endpoint")
}

var disco wellknown.OcmDiscoveryData
err = json.Unmarshal(body, &disco)
body, err := io.ReadAll(resp.Body)
if err != nil {
log := appctx.GetLogger(ctx)
log.Warn().Str("sender", endpoint).Str("response", string(body)).Msg("malformed response")
return nil, errtypes.InternalError("Invalid payload on OCM discovery")
return nil, errors.Wrap(err, "malformed remote OCM discovery")
}

return &disco, nil
return body, nil
}

// NewShare sends a new OCM share to the remote system.
Expand Down
1 change: 1 addition & 0 deletions internal/http/services/opencloudmesh/ocmd/invites.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (h *invitesHandler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "missing parameters in request", err)
return
}
log.Info().Any("req", req).Msg("OCM /invite-accepted request received")

if req.Token == "" || req.UserID == "" || req.RecipientProvider == "" {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "token, userID and recipiendProvider must not be null", nil)
Expand Down
66 changes: 52 additions & 14 deletions internal/http/services/opencloudmesh/ocmd/shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"fmt"
"mime"
"net/http"
"path/filepath"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -63,8 +63,7 @@ func (h *sharesHandler) init(c *config) error {
return nil
}

// CreateShare sends all the informations to the consumer needed to start
// synchronization between the two services.
// CreateShare implements the OCM /shares call.
func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
Expand All @@ -73,6 +72,7 @@ func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
return
}
log.Info().Any("req", req).Msg("OCM /shares request received")

_, meshProvider, err := getIDAndMeshProvider(req.Sender)
log.Debug().Msgf("Determined Mesh Provider '%s' from req.Sender '%s'", meshProvider, req.Sender)
Expand Down Expand Up @@ -161,6 +161,7 @@ func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
}
}

log.Info().Any("req", createShareReq).Msg("CreateOCMCoreShare payload")
createShareResp, err := h.gatewayClient.CreateOCMCoreShare(ctx, createShareReq)
if err != nil {
reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", err)
Expand Down Expand Up @@ -210,10 +211,10 @@ func getCreateShareRequest(r *http.Request) (*NewShareRequest, error) {
contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err == nil && contentType == "application/json" {
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
return nil, errors.Wrap(err, "malformed OCM /shares request")
}
} else {
return nil, errors.New("OCM /share request payload not recognised")
return nil, errors.New("malformed OCM /shares request payload")
}
// validate the request
if err := validate.Struct(req); err != nil {
Expand Down Expand Up @@ -249,40 +250,77 @@ func getAndResolveProtocols(p Protocols, r *http.Request) ([]*ocm.Protocol, erro
protos := make([]*ocm.Protocol, 0, len(p))
for _, data := range p {
ocmProto := data.ToOCMProtocol()
protocolName := GetProtocolName(data)
var uri string
var isLocalhost bool

switch protocolName {
case "webdav":
uri = ocmProto.GetWebdavOptions().Uri
isLocalhost = strings.Contains(uri, "localhost")
case "webapp":
uri = ocmProto.GetWebappOptions().UriTemplate
isLocalhost = strings.Contains(uri, "localhost")
}

// Irrespective from the presence of a full `uri` in the payload (deprecated), resolve the remote root
remoteRoot, err := discoverOcmRoot(r, GetProtocolName(data))
// yet skip this if the remote is localhost (for integration tests)
if isLocalhost {
protos = append(protos, ocmProto)
continue
}
remoteRoot, err := discoverOcmRoot(r, protocolName)
if err != nil {
return nil, err
}
if GetProtocolName(data) == "webdav" {
ocmProto.GetWebdavOptions().Uri = filepath.Join(remoteRoot, ocmProto.GetWebdavOptions().SharedSecret)
} else if GetProtocolName(data) == "webapp" {
// ocmProto.GetWebappOptions().Uri = filepath.Join(remoteRoot, ocmProto.GetWebappOptions().SharedSecret) -> this is OCM 1.2
uri, _ = url.JoinPath(remoteRoot, uri[strings.LastIndex(uri, "/")+1:])

switch protocolName {
case "webdav":
ocmProto.GetWebdavOptions().Uri = uri
case "webapp":
ocmProto.GetWebappOptions().UriTemplate = uri
}
protos = append(protos, ocmProto)
}

return protos, nil
}


func discoverOcmRoot(r *http.Request, proto string) (string, error) {
// implements the OCM discovery logic to fetch the root at the remote host that sent the share for the given proto, 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 share, attempting to discover sender endpoint")

// assume the sender host is either given in the usual reverse proxy headers or as RemoteAddr, and that the
// remote end listens on https regardless if the incoming connection got its TLS terminated upstream of us
senderURL := r.Header.Get("X-Real-Ip")
if senderURL == "" {
senderURL = r.Header.Get("X-Forwarded-For")
}
if senderURL == "" {
senderURL = r.RemoteAddr
}
senderURL = "https://" + senderURL[:strings.LastIndex(senderURL, ":")]
log.Debug().Str("sender", senderURL).Msg("received OCM share, attempting to discover sender endpoint")

ocmClient := NewClient(time.Duration(10)*time.Second, true)
ocmCaps, err := ocmClient.Discover(ctx, r.Host)
ocmCaps, err := ocmClient.Discover(ctx, senderURL)
if err != nil {
log.Warn().Str("sender", r.Host).Err(err).Msg("failed to discover OCM sender")
log.Warn().Str("sender", senderURL).Err(err).Msg("failed to discover OCM sender")
return "", err
}
for _, t := range ocmCaps.ResourceTypes {
protoRoot, ok := t.Protocols[proto]
if ok {
// assume the first resourceType that exposes a 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(ocmCaps.Endpoint, protoRoot), nil
u, _ := url.Parse(ocmCaps.Endpoint)
u.Path = protoRoot
u.RawQuery = ""
return u.String(), nil
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler {

c, err := pool.GetGatewayServiceClient(pool.Endpoint(s.c.GatewaySvc))
if err != nil {
log.Error().Err(err).Msg("error getting gateway during OCM authentication")
w.WriteHeader(http.StatusNotFound)
return
}
Expand Down

0 comments on commit 8cf76ad

Please sign in to comment.