diff --git a/internal/grpc/services/ocminvitemanager/ocminvitemanager.go b/internal/grpc/services/ocminvitemanager/ocminvitemanager.go index ac7956f6ff9..92e798f77c6 100644 --- a/internal/grpc/services/ocminvitemanager/ocminvitemanager.go +++ b/internal/grpc/services/ocminvitemanager/ocminvitemanager.go @@ -26,9 +26,9 @@ import ( invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmd" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/ocm/client" "github.com/cs3org/reva/pkg/ocm/invite" "github.com/cs3org/reva/pkg/ocm/invite/repository/registry" "github.com/cs3org/reva/pkg/plugin" @@ -66,7 +66,7 @@ type config struct { type service struct { conf *config repo invite.Repository - ocmClient *client.OCMClient + ocmClient *ocmd.OCMClient } func (c *config) ApplyDefaults() { @@ -110,12 +110,9 @@ func New(ctx context.Context, m map[string]interface{}) (rgrpc.Service, error) { } service := &service{ - conf: &c, - repo: repo, - ocmClient: client.New(&client.Config{ - Timeout: time.Duration(c.OCMClientTimeout) * time.Second, - Insecure: c.OCMClientInsecure, - }), + conf: &c, + repo: repo, + ocmClient: ocmd.NewOCMClient(time.Duration(c.OCMClientTimeout)*time.Second, c.OCMClientInsecure), } return service, nil } @@ -166,7 +163,7 @@ func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInvite return nil, err } - remoteUser, err := s.ocmClient.InviteAccepted(ctx, ocmEndpoint, &client.InviteAcceptedRequest{ + remoteUser, err := s.ocmClient.InviteAccepted(ctx, ocmEndpoint, &ocmd.InviteAcceptedRequest{ Token: req.InviteToken.GetToken(), RecipientProvider: s.conf.ProviderDomain, UserID: user.GetId().GetOpaqueId(), @@ -175,19 +172,19 @@ func (s *service) ForwardInvite(ctx context.Context, req *invitepb.ForwardInvite }) if err != nil { switch { - case errors.Is(err, client.ErrTokenInvalid): + case errors.Is(err, ocmd.ErrTokenInvalid): return &invitepb.ForwardInviteResponse{ Status: status.NewInvalid(ctx, "token not valid"), }, nil - case errors.Is(err, client.ErrTokenNotFound): + case errors.Is(err, ocmd.ErrTokenNotFound): return &invitepb.ForwardInviteResponse{ Status: status.NewNotFound(ctx, "token not found"), }, nil - case errors.Is(err, client.ErrUserAlreadyAccepted): + case errors.Is(err, ocmd.ErrUserAlreadyAccepted): return &invitepb.ForwardInviteResponse{ Status: status.NewAlreadyExists(ctx, err, err.Error()), }, nil - case errors.Is(err, client.ErrServiceNotTrusted): + case errors.Is(err, ocmd.ErrServiceNotTrusted): return &invitepb.ForwardInviteResponse{ Status: status.NewPermissionDenied(ctx, err, err.Error()), }, nil diff --git a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index 2617bf87f76..7cdb28ab905 100644 --- a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -41,7 +41,6 @@ import ( "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/ocm/client" "github.com/cs3org/reva/pkg/ocm/share" "github.com/cs3org/reva/pkg/ocm/share/repository/registry" "github.com/cs3org/reva/pkg/plugin" @@ -79,7 +78,7 @@ type config struct { type service struct { conf *config repo share.Repository - client *client.OCMClient + client *ocmd.OCMClient gateway gateway.GatewayAPIClient webappTmpl *template.Template walker walker.Walker @@ -119,11 +118,6 @@ func New(ctx context.Context, m map[string]interface{}) (rgrpc.Service, error) { return nil, err } - client := client.New(&client.Config{ - Timeout: time.Duration(c.ClientTimeout) * time.Second, - Insecure: c.ClientInsecure, - }) - gateway, err := pool.GetGatewayServiceClient(pool.Endpoint(c.GatewaySVC)) if err != nil { return nil, err @@ -135,10 +129,11 @@ func New(ctx context.Context, m map[string]interface{}) (rgrpc.Service, error) { } walker := walker.NewWalker(gateway) + ocmcl := ocmd.NewOCMClient(time.Duration(c.ClientTimeout)*time.Second, c.ClientInsecure) service := &service{ conf: &c, repo: repo, - client: client, + client: ocmcl, gateway: gateway, webappTmpl: tpl, walker: walker, @@ -324,7 +319,7 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq }, nil } - newShareReq := &client.NewShareRequest{ + newShareReq := &ocmd.NewShareRequest{ ShareWith: formatOCMUser(req.Grantee.GetUserId()), Name: ocmshare.Name, ProviderID: ocmshare.Id.OpaqueId, @@ -349,11 +344,11 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq newShareRes, err := s.client.NewShare(ctx, ocmEndpoint, newShareReq) if err != nil { switch { - case errors.Is(err, client.ErrInvalidParameters): + case errors.Is(err, ocmd.ErrInvalidParameters): return &ocm.CreateOCMShareResponse{ Status: status.NewInvalidArg(ctx, err.Error()), }, nil - case errors.Is(err, client.ErrServiceNotTrusted): + case errors.Is(err, ocmd.ErrServiceNotTrusted): return &ocm.CreateOCMShareResponse{ Status: status.NewInvalidArg(ctx, err.Error()), }, nil diff --git a/pkg/ocm/client/client.go b/internal/http/services/opencloudmesh/ocmd/client.go similarity index 78% rename from pkg/ocm/client/client.go rename to internal/http/services/opencloudmesh/ocmd/client.go index d43f8b73794..e44be91627e 100644 --- a/pkg/ocm/client/client.go +++ b/internal/http/services/opencloudmesh/ocmd/client.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package client +package ocmd import ( "bytes" @@ -28,7 +28,7 @@ import ( "net/url" "time" - "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmd" + "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmprovider" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/pkg/errors" @@ -59,62 +59,28 @@ type OCMClient struct { client *http.Client } -// Config is the configuration to be used for the OCMClient. -type Config struct { - Timeout time.Duration - Insecure bool -} - -// New returns a new OCMClient. -func New(c *Config) *OCMClient { +// NewOCMClient returns a new OCMClient. +func NewOCMClient(timeout time.Duration, insecure bool) *OCMClient { tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.Insecure}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, } return &OCMClient{ - client: &http.Client{Transport: tr}, - } -} - -// InviteAcceptedRequest contains the parameters for accepting -// an invitation. -type InviteAcceptedRequest struct { - UserID string `json:"userID"` - Email string `json:"email"` - Name string `json:"name"` - RecipientProvider string `json:"recipientProvider"` - Token string `json:"token"` -} - -// User contains the remote user's information when accepting -// an invitation. -type User struct { - UserID string `json:"userID"` - Email string `json:"email"` - Name string `json:"name"` -} - -func (r *InviteAcceptedRequest) toJSON() (io.Reader, error) { - var b bytes.Buffer - if err := json.NewEncoder(&b).Encode(r); err != nil { - return nil, err + client: &http.Client{ + Transport: tr, + Timeout: timeout, + }, } - return &b, nil } -// InviteAccepted informs the sender that the invitation was accepted to start sharing -// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post -func (c *OCMClient) InviteAccepted(ctx context.Context, endpoint string, r *InviteAcceptedRequest) (*User, error) { - url, err := url.JoinPath(endpoint, "invite-accepted") - if err != nil { - return nil, err - } - - body, err := r.toJSON() +// 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) (*ocmprovider.DiscoveryData, error) { + url, err := url.JoinPath(endpoint, "/ocm-provider") if err != nil { return nil, err } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, errors.Wrap(err, "error creating request") } @@ -126,48 +92,36 @@ func (c *OCMClient) InviteAccepted(ctx context.Context, endpoint string, r *Invi } defer resp.Body.Close() - return c.parseInviteAcceptedResponse(resp) -} - -func (c *OCMClient) parseInviteAcceptedResponse(r *http.Response) (*User, error) { - switch r.StatusCode { - case http.StatusOK: - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, errors.Wrap(err, "error decoding response body") - } - return &u, nil - case http.StatusBadRequest: - return nil, ErrTokenInvalid - case http.StatusNotFound: - return nil, ErrTokenNotFound - case http.StatusConflict: - return nil, ErrUserAlreadyAccepted - case http.StatusForbidden: - return nil, ErrServiceNotTrusted + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err } - body, err := io.ReadAll(r.Body) + var disco ocmprovider.DiscoveryData + err = json.Unmarshal(body, &disco) if err != nil { - return nil, errors.Wrap(err, "error decoding response body") + 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, errtypes.InternalError(string(body)) + + return &disco, nil } // NewShareRequest contains the parameters for creating a new OCM share. type NewShareRequest struct { - ShareWith string `json:"shareWith"` - Name string `json:"name"` - Description string `json:"description"` - ProviderID string `json:"providerId"` - Owner string `json:"owner"` - Sender string `json:"sender"` - OwnerDisplayName string `json:"ownerDisplayName"` - SenderDisplayName string `json:"senderDisplayName"` - ShareType string `json:"shareType"` - Expiration uint64 `json:"expiration"` - ResourceType string `json:"resourceType"` - Protocols ocmd.Protocols `json:"protocol"` + ShareWith string `json:"shareWith"` + Name string `json:"name"` + Description string `json:"description"` + ProviderID string `json:"providerId"` + Owner string `json:"owner"` + Sender string `json:"sender"` + OwnerDisplayName string `json:"ownerDisplayName"` + SenderDisplayName string `json:"senderDisplayName"` + ShareType string `json:"shareType"` + Expiration uint64 `json:"expiration"` + ResourceType string `json:"resourceType"` + Protocols Protocols `json:"protocol"` } func (r *NewShareRequest) toJSON() (io.Reader, error) { @@ -232,34 +186,46 @@ func (c *OCMClient) parseNewShareResponse(r *http.Response) (*NewShareResponse, return nil, errtypes.InternalError(string(body)) } -// Capabilities contains a set of properties exposed by -// a remote cloud storage. -type Capabilities struct { - Enabled bool `json:"enabled"` - APIVersion string `json:"apiVersion"` - EndPoint string `json:"endPoint"` - Provider string `json:"provider"` - ResourceTypes []struct { - Name string `json:"name"` - ShareTypes []string `json:"shareTypes"` - Protocols struct { - Webdav *string `json:"webdav"` - Webapp *string `json:"webapp"` - Datatx *string `json:"datatx"` - } `json:"protocols"` - } `json:"resourceTypes"` - Capabilities []string `json:"capabilities"` +// InviteAcceptedRequest contains the parameters for accepting +// an invitation. +type InviteAcceptedRequest struct { + UserID string `json:"userID"` + Email string `json:"email"` + Name string `json:"name"` + RecipientProvider string `json:"recipientProvider"` + Token string `json:"token"` } -// Discovery 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) Discovery(ctx context.Context, endpoint string) (*Capabilities, error) { - url, err := url.JoinPath(endpoint, "shares") +// User contains the remote user's information when accepting +// an invitation. +type User struct { + UserID string `json:"userID"` + Email string `json:"email"` + Name string `json:"name"` +} + +func (r *InviteAcceptedRequest) toJSON() (io.Reader, error) { + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(r); err != nil { + return nil, err + } + return &b, nil +} + +// InviteAccepted informs the sender that the invitation was accepted to start sharing +// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1invite-accepted/post +func (c *OCMClient) InviteAccepted(ctx context.Context, endpoint string, r *InviteAcceptedRequest) (*User, error) { + url, err := url.JoinPath(endpoint, "invite-accepted") if err != nil { return nil, err } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + body, err := r.toJSON() + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) if err != nil { return nil, errors.Wrap(err, "error creating request") } @@ -271,10 +237,30 @@ func (c *OCMClient) Discovery(ctx context.Context, endpoint string) (*Capabiliti } defer resp.Body.Close() - var cap Capabilities - if err := json.NewDecoder(resp.Body).Decode(&c); err != nil { - return nil, err + return c.parseInviteAcceptedResponse(resp) +} + +func (c *OCMClient) parseInviteAcceptedResponse(r *http.Response) (*User, error) { + switch r.StatusCode { + case http.StatusOK: + var u User + if err := json.NewDecoder(r.Body).Decode(&u); err != nil { + return nil, errors.Wrap(err, "error decoding response body") + } + return &u, nil + case http.StatusBadRequest: + return nil, ErrTokenInvalid + case http.StatusNotFound: + return nil, ErrTokenNotFound + case http.StatusConflict: + return nil, ErrUserAlreadyAccepted + case http.StatusForbidden: + return nil, ErrServiceNotTrusted } - return &cap, nil + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, errors.Wrap(err, "error decoding response body") + } + return nil, errtypes.InternalError(string(body)) } diff --git a/internal/http/services/opencloudmesh/ocmd/shares.go b/internal/http/services/opencloudmesh/ocmd/shares.go index a48f040f7ef..f6f8331efc3 100644 --- a/internal/http/services/opencloudmesh/ocmd/shares.go +++ b/internal/http/services/opencloudmesh/ocmd/shares.go @@ -21,7 +21,6 @@ package ocmd import ( "encoding/json" "fmt" - "io" "mime" "net/http" "path/filepath" @@ -37,13 +36,11 @@ import ( 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/opencloudmesh/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/httpclient" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/utils" "github.com/go-playground/validator/v10" @@ -282,43 +279,21 @@ func discoverOcmWebdavRoot(r *http.Request) (string, error) { log := appctx.GetLogger(ctx) log.Debug().Str("sender", r.Host).Msg("received OCM 1.0 share, attempting to discover sender endpoint") - httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, r.Host+"/ocm-provider", nil) + ocmClient := NewOCMClient(time.Duration(10)*time.Second, true) + ocmCaps, err := ocmClient.Discover(ctx, r.Host) if err != nil { + log.Warn().Str("sender", r.Host).Err(err).Msg("failed to discover OCM sender") return "", err } - httpClient := httpclient.New( - httpclient.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 { + for _, t := range ocmCaps.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 + return filepath.Join(ocmCaps.Endpoint, webdavRoot), nil } } - log.Warn().Str("sender", r.Host).Str("response", string(body)).Msg("missing webdav root") + log.Warn().Str("sender", r.Host).Interface("response", ocmCaps).Msg("missing webdav root") return "", errtypes.NotFound("WebDAV root not found on OCM discovery") } diff --git a/pkg/ocm/provider/authorizer/open/open.go b/pkg/ocm/provider/authorizer/open/open.go index 92dcc58e6a3..c31593a6a7d 100644 --- a/pkg/ocm/provider/authorizer/open/open.go +++ b/pkg/ocm/provider/authorizer/open/open.go @@ -20,12 +20,16 @@ package open import ( "context" + "path/filepath" "strings" + "time" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + client "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmd" "github.com/cs3org/reva/pkg/ocm/provider" "github.com/cs3org/reva/pkg/ocm/provider/authorizer/registry" "github.com/cs3org/reva/pkg/utils/cfg" + "github.com/pkg/errors" ) func init() { @@ -61,25 +65,49 @@ func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmpr return p, nil } } + // not yet known: try to discover the remote OCM endpoint - //TODO - // return a fake provider info record for this domain, including the OCM service + ocmClient := client.NewOCMClient(time.Duration(10)*time.Second, true) + ocmCaps, err := ocmClient.Discover(ctx, domain) + if err != nil { + return nil, errors.Wrap(err, "error probing OCM services at remote server") + } + var path string + for _, t := range ocmCaps.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. + path = filepath.Join(ocmCaps.Endpoint, webdavRoot) + } + } + + // return a provider info record for this domain, including the OCM service return &ocmprovider.ProviderInfo{ Name: "ocm_" + domain, - FullName: "", + FullName: ocmCaps.Provider, Description: "OCM service at " + domain, Organization: domain, Domain: domain, - Homepage: "https://" + domain, + Homepage: "", Email: "", Properties: map[string]string{}, - Services: []*ocmprovider.Service{{ - Endpoint: &ocmprovider.ServiceEndpoint{ - Type: &ocmprovider.ServiceType{Name: "OCM"}, - Path: "", + Services: []*ocmprovider.Service{ + { + Endpoint: &ocmprovider.ServiceEndpoint{ + Type: &ocmprovider.ServiceType{Name: "OCM"}, + Path: ocmCaps.Endpoint, + }, + Host: ocmCaps.Endpoint, + }, + { + Endpoint: &ocmprovider.ServiceEndpoint{ + Type: &ocmprovider.ServiceType{Name: "Webdav"}, + Path: path, + }, + Host: ocmCaps.Endpoint, }, - Host: "", - }}, + }, }, nil }