From 1f4fdb297f56d10353e06f0089d3790dbf7cb73a Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 30 Apr 2024 18:59:08 +0200 Subject: [PATCH 01/13] Removed dead code --- pkg/ocm/share/sender/sender.go | 97 ---------------------------------- 1 file changed, 97 deletions(-) delete mode 100644 pkg/ocm/share/sender/sender.go diff --git a/pkg/ocm/share/sender/sender.go b/pkg/ocm/share/sender/sender.go deleted file mode 100644 index 05d6890bde..0000000000 --- a/pkg/ocm/share/sender/sender.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018-2024 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package sender - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "path" - "strings" - "time" - - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/httpclient" - "github.com/pkg/errors" -) - -const createOCMCoreShareEndpoint = "shares" - -func getOCMEndpoint(originProvider *ocmprovider.ProviderInfo) (string, error) { - for _, s := range originProvider.Services { - if s.Endpoint.Type.Name == "OCM" { - return s.Endpoint.Path, nil - } - } - return "", errors.New("json: ocm endpoint not specified for mesh provider") -} - -// Send executes the POST to the OCM shares endpoint to create the share at the -// remote site. -func Send(ctx context.Context, requestBodyMap map[string]interface{}, pi *ocmprovider.ProviderInfo) error { - requestBody, err := json.Marshal(requestBodyMap) - if err != nil { - err = errors.Wrap(err, "error marshalling request body") - return err - } - ocmEndpoint, err := getOCMEndpoint(pi) - if err != nil { - return err - } - u, err := url.Parse(ocmEndpoint) - if err != nil { - return err - } - u.Path = path.Join(u.Path, createOCMCoreShareEndpoint) - recipientURL := u.String() - - log := appctx.GetLogger(ctx) - log.Info().Msgf("in OCM Send! %s %s", recipientURL, requestBody) - - req, err := http.NewRequest(http.MethodPost, recipientURL, strings.NewReader(string(requestBody))) - if err != nil { - return errors.Wrap(err, "sender: error framing post request") - } - req.Header.Set("Content-Type", "application/json") - client := httpclient.New( - httpclient.Timeout(5 * time.Second), - ) - - resp, err := client.Do(req) - if err != nil { - err = errors.Wrap(err, "sender: error sending post request") - return err - } - - defer resp.Body.Close() - if (resp.StatusCode != http.StatusCreated) && (resp.StatusCode != http.StatusOK) { - respBody, e := io.ReadAll(resp.Body) - if e != nil { - e = errors.Wrap(e, "sender: error reading request body") - return e - } - err = errors.Wrap(fmt.Errorf("%s: %s", resp.Status, string(respBody)), "sender: error from "+ocmEndpoint) - return err - } - return nil -} From 7923d74a6552f363a422227398dbe70f204cfc53 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Fri, 3 May 2024 10:22:04 +0200 Subject: [PATCH 02/13] changelog --- changelog/unreleased/ocm-access.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/unreleased/ocm-access.md diff --git a/changelog/unreleased/ocm-access.md b/changelog/unreleased/ocm-access.md new file mode 100644 index 0000000000..bc4ec77e18 --- /dev/null +++ b/changelog/unreleased/ocm-access.md @@ -0,0 +1,6 @@ +Enhancement: ocm: support bearer token access + +This PR adds support for accessing remote OCM 1.1 shares via bearer token, +as opposed to having the shared secret in the URL only. + +https://github.com/cs3org/reva/pull/4670 From f2050a3999b7f85403885e126e2e5f50381061fd Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 30 Apr 2024 18:29:20 +0200 Subject: [PATCH 03/13] ocm: support bearer token access --- internal/http/services/owncloud/ocdav/dav.go | 43 ++++++++++++------- .../http/services/owncloud/ocdav/ocdav.go | 1 - .../http/services/owncloud/ocdav/propfind.go | 16 ++----- pkg/auth/manager/ocmshares/ocmshares.go | 10 ++++- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index ed0ea135bd..3e48fa804e 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -188,21 +188,33 @@ func (h *DavHandler) Handler(s *svc) http.Handler { return } - // OC10 and Nextcloud (OCM 1.0) are using basic auth for carrying the - // shared token. - var token string - username, _, ok := r.BasicAuth() - if ok { - // OCM 1.0 - token = username - r.URL.Path = filepath.Join("/", token, r.URL.Path) - ctx = context.WithValue(ctx, ctxOCM10, true) + var token, ocmshare string + // OCM v1.1 (OCIS et al.). + bearer := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + if bearer != "" { + // Bearer token is the shared secret, path is /{shareId}/path/to/resource. + // Here we're keeping the simpler public-share model, where the internal routing is done via the token, + // therefore we strip the shareId and reinject the token. + // TODO(lopresti) We should instead perform a lookup via shareId and leave the token just for auth. + var relPath string + token = bearer + ocmshare, relPath = router.ShiftPath(r.URL.Path) + r.URL.Path = filepath.Join("/", token, relPath) } else { - token, _ = router.ShiftPath(r.URL.Path) - ctx = context.WithValue(ctx, ctxOCM10, false) + username, _, ok := r.BasicAuth() + if ok { + // OCM v1.0 (OC10 and Nextcloud) uses basic auth for carrying the shared secret, + // and does not pass the shareId. + token = username + r.URL.Path = filepath.Join("/", token, r.URL.Path) + } else { + // compatibility for ScienceMesh: no auth, shared secret is the first element + // of the path, the shareId is not given. Leave the URL as is. + token = strings.Split(r.URL.Path, "/")[1] + } } - authRes, err := handleOCMAuth(ctx, c, token) + authRes, err := handleOCMAuth(ctx, c, ocmshare, token) switch { case err != nil: log.Error().Err(err).Msg("error during ocm authentication") @@ -347,9 +359,10 @@ func handleSignatureAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, return c.Authenticate(ctx, &authenticateRequest) } -func handleOCMAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, token string) (*gatewayv1beta1.AuthenticateResponse, error) { +func handleOCMAuth(ctx context.Context, c gatewayv1beta1.GatewayAPIClient, ocmshare, token string) (*gatewayv1beta1.AuthenticateResponse, error) { return c.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ - Type: "ocmshares", - ClientId: token, + Type: "ocmshares", + ClientId: ocmshare, + ClientSecret: token, }) } diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 0a6b49bbce..6c4105a6c4 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -51,7 +51,6 @@ type ctxKey int const ( ctxKeyBaseURI ctxKey = iota - ctxOCM10 ) var ( diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 5f532a3fd8..fbbee90382 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -504,16 +504,6 @@ func (s *svc) newPropRaw(key, val string) *propertyXML { } } -func supportLegacyOCMAccess(ctx context.Context, md *provider.ResourceInfo) { - ocm10, _ := ctx.Value(ctxOCM10).(bool) - if ocm10 { - // the path is something like //... - // we need to strip the token part as this - // is passed as username in the basic auth - _, md.Path = router.ShiftPath(md.Path) - } -} - func appendSlash(path string) string { if path == "" { return "/" @@ -542,9 +532,11 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sublog := appctx.GetLogger(ctx).With().Str("ns", ns).Logger() md.Path = strings.TrimPrefix(md.Path, ns) - baseURI := ctx.Value(ctxKeyBaseURI).(string) + // see internal/http/services/owncloud/ocdav/dav.go#L191-212: + // // was injected in front of the path for the routing to work, we now remove it + _, md.Path = router.ShiftPath(md.Path) - supportLegacyOCMAccess(ctx, md) + baseURI := ctx.Value(ctxKeyBaseURI).(string) ref := path.Join(baseURI, md.Path) if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" diff --git a/pkg/auth/manager/ocmshares/ocmshares.go b/pkg/auth/manager/ocmshares/ocmshares.go index c53b00a085..b347cbe817 100644 --- a/pkg/auth/manager/ocmshares/ocmshares.go +++ b/pkg/auth/manager/ocmshares/ocmshares.go @@ -82,8 +82,8 @@ func (m *manager) Configure(ml map[string]interface{}) error { return nil } -func (m *manager) Authenticate(ctx context.Context, token, _ string) (*userpb.User, map[string]*authpb.Scope, error) { - log := appctx.GetLogger(ctx).With().Str("token", token).Logger() +func (m *manager) Authenticate(ctx context.Context, ocmshare, token string) (*userpb.User, map[string]*authpb.Scope, error) { + log := appctx.GetLogger(ctx).With().Str("token", token).Str("ocmshare", ocmshare).Logger() shareRes, err := m.gw.GetOCMShareByToken(ctx, &ocm.GetOCMShareByTokenRequest{ Token: token, }) @@ -103,6 +103,12 @@ func (m *manager) Authenticate(ctx context.Context, token, _ string) (*userpb.Us return nil, nil, errtypes.InternalError(shareRes.Status.Message) } + // validate OCM share id if given (OCM v1.1) + if ocmshare != "" && shareRes.GetShare().GetId().GetOpaqueId() != ocmshare { + log.Error().Str("requested_share", ocmshare).Str("share_from_provider", shareRes.GetShare().GetId().GetOpaqueId()).Msg("mismatching ocm share id for existing secret") + return nil, nil, errtypes.InvalidCredentials("invalid shared secret") + } + // the user authenticated using the ocmshares authentication method // is the recipient of the share u := shareRes.Share.Grantee.GetUserId() From 7f2f63b179da72f5ded8dad13c269c241a23b668 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Fri, 3 May 2024 11:33:12 +0200 Subject: [PATCH 04/13] ocm: adapted handling of shares --- .../services/ocmshareprovider/ocmshareprovider.go | 14 +++++++------- internal/http/services/owncloud/ocdav/propfind.go | 4 ++-- pkg/ocm/client/client.go | 2 +- pkg/ocm/storage/outcoming/ocm.go | 4 ++++ pkg/ocm/storage/received/ocm.go | 7 +++++-- tests/integration/grpc/ocm_share_test.go | 5 ++++- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index 1de9c8427c..93ede0175a 100644 --- a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -178,13 +178,13 @@ func getResourceType(info *providerpb.ResourceInfo) string { return "unknown" } -func (s *service) webdavURL(ctx context.Context, share *ocm.Share) string { - // the url is in the form of https://cernbox.cern.ch/remote.php/dav/ocm/token - p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/remote.php/dav/ocm", share.Token) +func (s *service) webdavURL(share *ocm.Share) string { + // the url is expected to be in the form https://ourserver/remote.php/dav/ocm/{ShareId}, see c.WebdavRoot in ocmprovider.go + p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/remote.php/dav/ocm", share.Id.OpaqueId) return p } -func (s *service) getWebdavProtocol(ctx context.Context, share *ocm.Share, m *ocm.AccessMethod_WebdavOptions) *ocmd.WebDAV { +func (s *service) getWebdavProtocol(share *ocm.Share, m *ocm.AccessMethod_WebdavOptions) *ocmd.WebDAV { var perms []string if m.WebdavOptions.Permissions.InitiateFileDownload { perms = append(perms, "read") @@ -195,7 +195,7 @@ func (s *service) getWebdavProtocol(ctx context.Context, share *ocm.Share, m *oc return &ocmd.WebDAV{ Permissions: perms, - URL: s.webdavURL(ctx, share), + URL: s.webdavURL(share), SharedSecret: share.Token, } } @@ -233,7 +233,7 @@ func (s *service) getDataTransferProtocol(ctx context.Context, share *ocm.Share) panic(err) } return &ocmd.Datatx{ - SourceURI: s.webdavURL(ctx, share), + SourceURI: s.webdavURL(share), Size: size, } } @@ -248,7 +248,7 @@ func (s *service) getProtocols(ctx context.Context, share *ocm.Share) ocmd.Proto for _, m := range share.AccessMethods { switch t := m.Term.(type) { case *ocm.AccessMethod_WebdavOptions: - p = append(p, s.getWebdavProtocol(ctx, share, t)) + p = append(p, s.getWebdavProtocol(share, t)) case *ocm.AccessMethod_WebappOptions: p = append(p, s.getWebappProtocol(share)) case *ocm.AccessMethod_TransferOptions: diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index fbbee90382..35247fbbd4 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -532,8 +532,8 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sublog := appctx.GetLogger(ctx).With().Str("ns", ns).Logger() md.Path = strings.TrimPrefix(md.Path, ns) - // see internal/http/services/owncloud/ocdav/dav.go#L191-212: - // // was injected in front of the path for the routing to work, we now remove it + // see internal/http/services/owncloud/ocdav/dav.go: + // // was injected in front of the public-link or ocm path for the routing to work, we now remove it _, md.Path = router.ShiftPath(md.Path) baseURI := ctx.Value(ctxKeyBaseURI).(string) diff --git a/pkg/ocm/client/client.go b/pkg/ocm/client/client.go index faeb28d183..d43f8b7379 100644 --- a/pkg/ocm/client/client.go +++ b/pkg/ocm/client/client.go @@ -197,7 +197,7 @@ func (c *OCMClient) NewShare(ctx context.Context, endpoint string, r *NewShareRe } log := appctx.GetLogger(ctx) - log.Debug().Msgf("Sending OCM /shares POST to %s: %s", url, body) + log.Info().Str("url", url).Msgf("Sending OCM share: %s", body) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) if err != nil { return nil, errors.Wrap(err, "error creating request") diff --git a/pkg/ocm/storage/outcoming/ocm.go b/pkg/ocm/storage/outcoming/ocm.go index ed843a8b20..ac80120f7c 100644 --- a/pkg/ocm/storage/outcoming/ocm.go +++ b/pkg/ocm/storage/outcoming/ocm.go @@ -68,6 +68,7 @@ func (c *config) ApplyDefaults() { } // New creates an OCM storage driver. +// This driver exposes local resources to remote OCM users. func New(ctx context.Context, m map[string]interface{}) (storage.FS, error) { var c config if err := cfg.Decode(m, &c); err != nil { @@ -148,6 +149,9 @@ func (d *driver) shareAndRelativePathFromRef(ctx context.Context, ref *provider. } path = makeRelative(path) + log := appctx.GetLogger(ctx) + log.Info().Interface("ref", ref).Str("path", path).Str("token", token).Msg("Accessing OCM share") + share, err := d.resolveToken(ctx, token) if err != nil { return nil, "", err diff --git a/pkg/ocm/storage/received/ocm.go b/pkg/ocm/storage/received/ocm.go index bcb684b9a8..89e73ef29b 100644 --- a/pkg/ocm/storage/received/ocm.go +++ b/pkg/ocm/storage/received/ocm.go @@ -33,6 +33,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typepb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" + "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/mime" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -62,6 +63,7 @@ func (c *config) ApplyDefaults() { } // New creates an OCM storage driver. +// This driver exposes remote OCM resources to local users. func New(ctx context.Context, m map[string]interface{}) (storage.FS, error) { var c config if err := cfg.Decode(m, &c); err != nil { @@ -153,11 +155,12 @@ func (d *driver) webdavClient(ctx context.Context, ref *provider.Reference) (*go return nil, nil, "", err } - // FIXME: it's still not clear from the OCM APIs how to use the shared secret - // will use as a token in the bearer authentication as this is the reva implementation + // use the secret as bearer authentication according to OCM v1.1+ c := gowebdav.NewClient(endpoint, "", "") c.SetHeader("Authorization", "Bearer "+secret) + log := appctx.GetLogger(ctx) + log.Info().Str("endpoint", endpoint).Interface("share", share).Str("rel", rel).Str("secret", secret).Msg("Accessing OCM share") return c, share, rel, nil } diff --git a/tests/integration/grpc/ocm_share_test.go b/tests/integration/grpc/ocm_share_test.go index fabcb593ee..a560528179 100644 --- a/tests/integration/grpc/ocm_share_test.go +++ b/tests/integration/grpc/ocm_share_test.go @@ -219,6 +219,7 @@ var _ = Describe("ocm share", func() { Expect(ok).To(BeTrue()) webdavClient := gowebdav.NewClient(webdav.WebdavOptions.Uri, "", "") + webdavClient.SetHeader("Authorization", "Bearer "+webdav.WebdavOptions.SharedSecret) d, err := webdavClient.Read(".") Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("test"))) @@ -299,6 +300,7 @@ var _ = Describe("ocm share", func() { Expect(ok).To(BeTrue()) webdavClient := gowebdav.NewClient(webdav.WebdavOptions.Uri, "", "") + webdavClient.SetHeader("Authorization", "Bearer "+webdav.WebdavOptions.SharedSecret) data := []byte("new-content") webdavClient.SetHeader(ocdav.HeaderUploadLength, strconv.Itoa(len(data))) err = webdavClient.Write(".", data, 0) @@ -394,7 +396,7 @@ var _ = Describe("ocm share", func() { Expect(ok).To(BeTrue()) webdavClient := gowebdav.NewClient(webdav.WebdavOptions.Uri, "", "") - + webdavClient.SetHeader("Authorization", "Bearer "+webdav.WebdavOptions.SharedSecret) ok, err = helpers.SameContentWebDAV(webdavClient, fileToShare.Path, structure) Expect(err).ToNot(HaveOccurred()) Expect(ok).To(BeTrue()) @@ -499,6 +501,7 @@ var _ = Describe("ocm share", func() { webdavClient := gowebdav.NewClient(webdav.WebdavOptions.Uri, "", "") data := []byte("new-content") webdavClient.SetHeader(ocdav.HeaderUploadLength, strconv.Itoa(len(data))) + webdavClient.SetHeader("Authorization", "Bearer "+webdav.WebdavOptions.SharedSecret) err = webdavClient.Write("new-file", data, 0) Expect(err).ToNot(HaveOccurred()) From 198f5845c010b9286c165dae3ed501223506fadb Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Mon, 13 May 2024 08:43:23 +0200 Subject: [PATCH 05/13] Fixed propfind --- internal/http/services/owncloud/ocdav/dav.go | 3 ++- internal/http/services/owncloud/ocdav/ocdav.go | 1 + internal/http/services/owncloud/ocdav/propfind.go | 10 ++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index 3e48fa804e..2532b63b67 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -217,7 +217,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { authRes, err := handleOCMAuth(ctx, c, ocmshare, token) switch { case err != nil: - log.Error().Err(err).Msg("error during ocm authentication") + log.Error().Err(err).Msg("error during OCM authentication") w.WriteHeader(http.StatusInternalServerError) return case authRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: @@ -240,6 +240,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { ctx = appctx.ContextSetToken(ctx, authRes.Token) ctx = appctx.ContextSetUser(ctx, authRes.User) ctx = metadata.AppendToOutgoingContext(ctx, appctx.TokenHeader, authRes.Token) + ctx = context.WithValue(ctx, ctxOCM, true) log.Debug().Str("token", token).Interface("user", authRes.User).Msg("OCM user authenticated") diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 6c4105a6c4..a14ec7e39d 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -51,6 +51,7 @@ type ctxKey int const ( ctxKeyBaseURI ctxKey = iota + ctxOCM ) var ( diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 35247fbbd4..d762f63edf 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -530,11 +530,13 @@ func (s *svc) isOpenable(path string) bool { // prefixing it with the baseURI. func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, usershares, linkshares map[string]struct{}) (*responseXML, error) { sublog := appctx.GetLogger(ctx).With().Str("ns", ns).Logger() - md.Path = strings.TrimPrefix(md.Path, ns) - // see internal/http/services/owncloud/ocdav/dav.go: - // // was injected in front of the public-link or ocm path for the routing to work, we now remove it - _, md.Path = router.ShiftPath(md.Path) + md.Path = strings.TrimPrefix(md.Path, ns) + ocm, _ := ctx.Value(ctxOCM).(bool) + if ocm { + // // was injected in front of the OCM path for the routing to work, we now remove it (see internal/http/services/owncloud/ocdav/dav.go) + _, md.Path = router.ShiftPath(md.Path) + } baseURI := ctx.Value(ctxKeyBaseURI).(string) ref := path.Join(baseURI, md.Path) From 98009f70b22e9748d53c1954d834202e0a2e6896 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Mon, 13 May 2024 08:44:10 +0200 Subject: [PATCH 06/13] Cosmetic changes --- internal/grpc/services/ocmcore/ocmcore.go | 4 +++- .../grpc/services/ocmshareprovider/ocmshareprovider.go | 9 +++++---- .../grpc/fixtures/ocm-share/ocm-server-cernbox-grpc.toml | 1 + .../grpc/fixtures/ocm-share/ocm-server-cesnet-grpc.toml | 1 + 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/internal/grpc/services/ocmcore/ocmcore.go b/internal/grpc/services/ocmcore/ocmcore.go index 218e3a3890..81f6f038da 100644 --- a/internal/grpc/services/ocmcore/ocmcore.go +++ b/internal/grpc/services/ocmcore/ocmcore.go @@ -18,6 +18,8 @@ package ocmcore +// This package implements the core OCM API for receiving external shares from remote EFSS systems. + import ( "context" "fmt" @@ -102,7 +104,7 @@ func (s *service) UnprotectedEndpoints() []string { return []string{"/cs3.ocm.core.v1beta1.OcmCoreAPI/CreateOCMCoreShare"} } -// CreateOCMCoreShare is called when an OCM request comes into this reva instance from. +// CreateOCMCoreShare is called when a remote OCM request comes into this reva instance. func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCMCoreShareRequest) (*ocmcore.CreateOCMCoreShareResponse, error) { if req.ShareType != ocm.ShareType_SHARE_TYPE_USER { return nil, errtypes.NotSupported("share type not supported") diff --git a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index 93ede0175a..2617bf87f7 100644 --- a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -18,6 +18,9 @@ package ocmshareprovider +// This package implements the OCM client API: it allows shares created on this Reva instance +// to be sent to a remote EFSS system via OCM. + import ( "context" "fmt" @@ -70,7 +73,7 @@ type config struct { GatewaySVC string `mapstructure:"gatewaysvc" validate:"required"` ProviderDomain string `docs:"The same domain registered in the provider authorizer" mapstructure:"provider_domain" validate:"required"` WebDAVEndpoint string `mapstructure:"webdav_endpoint" validate:"required"` - WebappTemplate string `mapstructure:"webapp_template"` + WebappTemplate string `mapstructure:"webapp_template" validate:"required"` } type service struct { @@ -89,9 +92,6 @@ func (c *config) ApplyDefaults() { if c.ClientTimeout == 0 { c.ClientTimeout = 10 } - if c.WebappTemplate == "" { - c.WebappTemplate = "https://cernbox.cern.ch/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}" - } c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC) } @@ -180,6 +180,7 @@ func getResourceType(info *providerpb.ResourceInfo) string { func (s *service) webdavURL(share *ocm.Share) string { // the url is expected to be in the form https://ourserver/remote.php/dav/ocm/{ShareId}, see c.WebdavRoot in ocmprovider.go + // TODO(lopresti) take the root from http.services.ocmprovider's config p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/remote.php/dav/ocm", share.Id.OpaqueId) return p } diff --git a/tests/integration/grpc/fixtures/ocm-share/ocm-server-cernbox-grpc.toml b/tests/integration/grpc/fixtures/ocm-share/ocm-server-cernbox-grpc.toml index c2c077b74c..9e40fe48f6 100644 --- a/tests/integration/grpc/fixtures/ocm-share/ocm-server-cernbox-grpc.toml +++ b/tests/integration/grpc/fixtures/ocm-share/ocm-server-cernbox-grpc.toml @@ -58,6 +58,7 @@ providers = "{{file_providers}}" [grpc.services.ocmshareprovider] driver = "json" webdav_endpoint = "http://{{cernboxwebdav_address}}" +webapp_template = "http://{{cernboxwebdav_address}}/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}" provider_domain = "cernbox.cern.ch" [grpc.services.ocmshareprovider.drivers.json] diff --git a/tests/integration/grpc/fixtures/ocm-share/ocm-server-cesnet-grpc.toml b/tests/integration/grpc/fixtures/ocm-share/ocm-server-cesnet-grpc.toml index 3a225319ab..399b310561 100644 --- a/tests/integration/grpc/fixtures/ocm-share/ocm-server-cesnet-grpc.toml +++ b/tests/integration/grpc/fixtures/ocm-share/ocm-server-cesnet-grpc.toml @@ -38,6 +38,7 @@ providers = "{{file_providers}}" [grpc.services.ocmshareprovider] driver = "json" webdav_endpoint = "http://{{cesnethttp_address}}" +webapp_template = "http://{{cesnethttp_address}}/external/sciencemesh/{{.Token}}{relative-path-to-shared-resource}" provider_domain = "cesnet.cz" [grpc.services.ocmshareprovider.drivers.json] From 7539842302cd9b2ccbe21200c1b5314d3da8420c Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Fri, 10 May 2024 17:43:38 +0200 Subject: [PATCH 07/13] Use open provider authorizer for OCM in cernbox example, and make it truly open --- examples/cernbox/cernbox.toml | 3 +- pkg/ocm/provider/authorizer/open/open.go | 58 ++++++++---------------- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/examples/cernbox/cernbox.toml b/examples/cernbox/cernbox.toml index a2091d0de1..b302b39449 100644 --- a/examples/cernbox/cernbox.toml +++ b/examples/cernbox/cernbox.toml @@ -199,7 +199,8 @@ webapp_template = "{{ vars.external_reva_endpoint }}/external/sciencemesh/{{.Tok file = "{{ vars.ocmshares_json_file }}" [grpc.services.ocmproviderauthorizer] -driver = "json" +driver = "open" # pure OCM, all remote shares are accepted +#driver = "json" # to enable sciencemesh [grpc.services.ocmproviderauthorizer.drivers.json] # this is used by the docker-based test deployment, not in production diff --git a/pkg/ocm/provider/authorizer/open/open.go b/pkg/ocm/provider/authorizer/open/open.go index 88516d6d3d..92dcc58e6a 100644 --- a/pkg/ocm/provider/authorizer/open/open.go +++ b/pkg/ocm/provider/authorizer/open/open.go @@ -20,12 +20,9 @@ package open import ( "context" - "encoding/json" - "os" "strings" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" - "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/provider" "github.com/cs3org/reva/pkg/ocm/provider/authorizer/registry" "github.com/cs3org/reva/pkg/utils/cfg" @@ -42,19 +39,7 @@ func New(ctx context.Context, m map[string]interface{}) (provider.Authorizer, er return nil, err } - f, err := os.ReadFile(c.Providers) - if err != nil { - return nil, err - } - providers := []*ocmprovider.ProviderInfo{} - err = json.Unmarshal(f, &providers) - if err != nil { - return nil, err - } - a := &authorizer{} - a.providers = a.getOCMProviders(providers) - return a, nil } @@ -64,9 +49,6 @@ type config struct { } func (c *config) ApplyDefaults() { - if c.Providers == "" { - c.Providers = "/etc/revad/ocm-providers.json" - } } type authorizer struct { @@ -79,7 +61,26 @@ func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmpr return p, nil } } - return nil, errtypes.NotFound(domain) + // not yet known: try to discover the remote OCM endpoint + //TODO + // return a fake provider info record for this domain, including the OCM service + return &ocmprovider.ProviderInfo{ + Name: "ocm_" + domain, + FullName: "", + Description: "OCM service at " + domain, + Organization: domain, + Domain: domain, + Homepage: "https://" + domain, + Email: "", + Properties: map[string]string{}, + Services: []*ocmprovider.Service{{ + Endpoint: &ocmprovider.ServiceEndpoint{ + Type: &ocmprovider.ServiceType{Name: "OCM"}, + Path: "", + }, + Host: "", + }}, + }, nil } func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovider.ProviderInfo) error { @@ -89,22 +90,3 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovide func (a *authorizer) ListAllProviders(ctx context.Context) ([]*ocmprovider.ProviderInfo, error) { return a.providers, nil } - -func (a *authorizer) getOCMProviders(providers []*ocmprovider.ProviderInfo) (po []*ocmprovider.ProviderInfo) { - for _, p := range providers { - _, err := a.getOCMHost(p) - if err == nil { - po = append(po, p) - } - } - return -} - -func (a *authorizer) getOCMHost(provider *ocmprovider.ProviderInfo) (string, error) { - for _, s := range provider.Services { - if s.Endpoint.Type.Name == "OCM" { - return s.Host, nil - } - } - return "", errtypes.NotFound("OCM Host") -} From a7ba5ae4cc7a227a43032192182f9b046feec96b Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 14 May 2024 08:43:55 +0200 Subject: [PATCH 08/13] Refactored OCM client into OCMD package --- .../ocminvitemanager/ocminvitemanager.go | 23 +- .../ocmshareprovider/ocmshareprovider.go | 19 +- .../services/opencloudmesh/ocmd}/client.go | 202 ++++++++---------- .../services/opencloudmesh/ocmd/shares.go | 37 +--- pkg/ocm/provider/authorizer/open/open.go | 50 ++++- 5 files changed, 157 insertions(+), 174 deletions(-) rename {pkg/ocm/client => internal/http/services/opencloudmesh/ocmd}/client.go (78%) diff --git a/internal/grpc/services/ocminvitemanager/ocminvitemanager.go b/internal/grpc/services/ocminvitemanager/ocminvitemanager.go index ac7956f6ff..a4f0d44039 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.NewClient(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 2617bf87f7..e26e7b02e5 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.NewClient(time.Duration(c.ClientTimeout)*time.Second, c.ClientInsecure) service := &service{ conf: &c, repo: repo, - client: client, + client: ocmcl, gateway: gateway, webappTmpl: tpl, walker: walker, @@ -180,7 +175,7 @@ func getResourceType(info *providerpb.ResourceInfo) string { func (s *service) webdavURL(share *ocm.Share) string { // the url is expected to be in the form https://ourserver/remote.php/dav/ocm/{ShareId}, see c.WebdavRoot in ocmprovider.go - // TODO(lopresti) take the root from http.services.ocmprovider's config + // TODO(lopresti) take the root from http.services.wellknown.ocmprovider's config p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/remote.php/dav/ocm", share.Id.OpaqueId) return p } @@ -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 d43f8b7379..3a6f216670 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 { +// NewClient returns a new OCMClient. +func NewClient(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 a48f040f7e..954e72ccae 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 := NewClient(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 92dcc58e6a..7995c66427 100644 --- a/pkg/ocm/provider/authorizer/open/open.go +++ b/pkg/ocm/provider/authorizer/open/open.go @@ -20,12 +20,17 @@ package open import ( "context" + "net/url" + "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 +66,50 @@ 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) + } + } + host, _ := url.Parse(ocmCaps.Endpoint) + + // 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: host.Hostname(), + }, + { + Endpoint: &ocmprovider.ServiceEndpoint{ + Type: &ocmprovider.ServiceType{Name: "Webdav"}, + Path: path, + }, + Host: host.Hostname(), }, - Host: "", - }}, + }, }, nil } From ad7e00d1cdab7e64c812df0569a520ecbd618379 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 14 May 2024 10:01:36 +0200 Subject: [PATCH 09/13] Updated changelog --- changelog/unreleased/ocm-access.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog/unreleased/ocm-access.md b/changelog/unreleased/ocm-access.md index bc4ec77e18..c99a7e2b68 100644 --- a/changelog/unreleased/ocm-access.md +++ b/changelog/unreleased/ocm-access.md @@ -2,5 +2,7 @@ Enhancement: ocm: support bearer token access This PR adds support for accessing remote OCM 1.1 shares via bearer token, as opposed to having the shared secret in the URL only. +In addition, the OCM client package is now part of the OCMD server package, +and the Discover methods have been all consolidated in one place. https://github.com/cs3org/reva/pull/4670 From 8186fcc264f50e6eb9f8137595e1f9aea257a288 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Wed, 15 May 2024 17:46:52 +0200 Subject: [PATCH 10/13] Introduced a .well-known endpoint for OCM --- examples/cernbox/cernbox.toml | 2 +- examples/ocm/server-1.toml | 2 +- examples/ocm/server-2.toml | 2 +- examples/sciencemesh/sciencemesh.toml | 2 +- examples/standalone/standalone.toml | 2 +- examples/storage-references/gateway.toml | 2 +- examples/two-server-setup/gateway-1.toml | 2 +- examples/two-server-setup/gateway-2.toml | 2 +- internal/http/services/loader/loader.go | 2 +- .../services/opencloudmesh/ocmd/client.go | 6 +- .../http/services/owncloud/ocdav/ocdav.go | 6 +- .../ocmprovider.go => wellknown/ocm.go} | 89 ++++++------------ internal/http/services/wellknown/wellknown.go | 91 +++++++++++++++++++ pkg/ocm/provider/authorizer/open/open.go | 2 +- 14 files changed, 139 insertions(+), 73 deletions(-) rename internal/http/services/{opencloudmesh/ocmprovider/ocmprovider.go => wellknown/ocm.go} (71%) create mode 100644 internal/http/services/wellknown/wellknown.go diff --git a/examples/cernbox/cernbox.toml b/examples/cernbox/cernbox.toml index b302b39449..e7991b77c2 100644 --- a/examples/cernbox/cernbox.toml +++ b/examples/cernbox/cernbox.toml @@ -280,7 +280,7 @@ sender_mail = "sciencemesh@{{ vars.provider_domain }}" smtp_server = "smtp.{{ vars.provider_domain }}" smtp_port = 25 -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] address = ":443" ocm_prefix = "ocm" provider = "Reva for CERNBox" diff --git a/examples/ocm/server-1.toml b/examples/ocm/server-1.toml index a4cb399427..424f5254b6 100644 --- a/examples/ocm/server-1.toml +++ b/examples/ocm/server-1.toml @@ -166,7 +166,7 @@ driver = "ocmreceived" address = "0.0.0.0:8080" expose_recipient_display_name = true -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] ocm_prefix = "ocm" provider = "reva@cern" endpoint = "http://localhost:{{ http.services.ocm.address.port }}" diff --git a/examples/ocm/server-2.toml b/examples/ocm/server-2.toml index 7b9bf24cb3..66a00b2249 100644 --- a/examples/ocm/server-2.toml +++ b/examples/ocm/server-2.toml @@ -166,7 +166,7 @@ driver = "ocmreceived" address = "0.0.0.0:80" expose_recipient_display_name = true -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] ocm_prefix = "ocm" provider = "reva@cesnet" endpoint = "http://localhost:{{ http.services.ocm.address.port }}" diff --git a/examples/sciencemesh/sciencemesh.toml b/examples/sciencemesh/sciencemesh.toml index c39ee95d6a..d2d93b50a2 100644 --- a/examples/sciencemesh/sciencemesh.toml +++ b/examples/sciencemesh/sciencemesh.toml @@ -253,7 +253,7 @@ sender_mail = "sciencemesh@{{ vars.provider_domain }}" smtp_server = "smtp.{{ vars.provider_domain }}" smtp_port = 25 -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] address = ":443" ocm_prefix = "ocm" provider = "Reva for ownCloud/Nextcloud" diff --git a/examples/standalone/standalone.toml b/examples/standalone/standalone.toml index 5a63df304b..e7e83ad461 100644 --- a/examples/standalone/standalone.toml +++ b/examples/standalone/standalone.toml @@ -17,6 +17,6 @@ [http.services.dataprovider] [http.services.prometheus] [http.services.ocm] -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] [http.services.ocdav] [http.services.ocs] diff --git a/examples/storage-references/gateway.toml b/examples/storage-references/gateway.toml index c57d035e51..fff03471bc 100644 --- a/examples/storage-references/gateway.toml +++ b/examples/storage-references/gateway.toml @@ -57,7 +57,7 @@ app_url = "https://your-collabora-server.org:9980" [http.services.datagateway] [http.services.prometheus] [http.services.ocm] -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] [http.services.ocdav] [http.services.ocs] diff --git a/examples/two-server-setup/gateway-1.toml b/examples/two-server-setup/gateway-1.toml index 1d3414071c..7d5d08731a 100644 --- a/examples/two-server-setup/gateway-1.toml +++ b/examples/two-server-setup/gateway-1.toml @@ -84,7 +84,7 @@ address = "0.0.0.0:19001" [http.services.datagateway] [http.services.prometheus] [http.services.ocm] -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] provider = "Reva-Server-1" endpoint = "http://localhost:19001" enable_webapp = true diff --git a/examples/two-server-setup/gateway-2.toml b/examples/two-server-setup/gateway-2.toml index 8361b3c7e8..bf3f22406d 100644 --- a/examples/two-server-setup/gateway-2.toml +++ b/examples/two-server-setup/gateway-2.toml @@ -84,7 +84,7 @@ address = "0.0.0.0:29001" [http.services.datagateway] [http.services.prometheus] [http.services.ocm] -[http.services.ocmprovider] +[http.services.wellknown.ocmprovider] provider = "Reva-Server-2" endpoint = "http://localhost:29001" enable_webapp = true diff --git a/internal/http/services/loader/loader.go b/internal/http/services/loader/loader.go index afa0e2e4c8..20982876df 100644 --- a/internal/http/services/loader/loader.go +++ b/internal/http/services/loader/loader.go @@ -29,7 +29,6 @@ import ( _ "github.com/cs3org/reva/internal/http/services/helloworld" _ "github.com/cs3org/reva/internal/http/services/metrics" _ "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmd" - _ "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmprovider" _ "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" _ "github.com/cs3org/reva/internal/http/services/owncloud/ocs" _ "github.com/cs3org/reva/internal/http/services/pingpong" @@ -37,5 +36,6 @@ import ( _ "github.com/cs3org/reva/internal/http/services/pprof" _ "github.com/cs3org/reva/internal/http/services/preferences" _ "github.com/cs3org/reva/internal/http/services/prometheus" + _ "github.com/cs3org/reva/internal/http/services/wellknown" // Add your own service here. ) diff --git a/internal/http/services/opencloudmesh/ocmd/client.go b/internal/http/services/opencloudmesh/ocmd/client.go index 3a6f216670..106f51e5ad 100644 --- a/internal/http/services/opencloudmesh/ocmd/client.go +++ b/internal/http/services/opencloudmesh/ocmd/client.go @@ -28,7 +28,7 @@ import ( "net/url" "time" - "github.com/cs3org/reva/internal/http/services/opencloudmesh/ocmprovider" + "github.com/cs3org/reva/internal/http/services/wellknown" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/pkg/errors" @@ -74,7 +74,7 @@ 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) (*ocmprovider.DiscoveryData, error) { +func (c *OCMClient) Discover(ctx context.Context, endpoint string) (*wellknown.OcmDiscoveryData, error) { url, err := url.JoinPath(endpoint, "/ocm-provider") if err != nil { return nil, err @@ -97,7 +97,7 @@ func (c *OCMClient) Discover(ctx context.Context, endpoint string) (*ocmprovider return nil, err } - var disco ocmprovider.DiscoveryData + var disco wellknown.OcmDiscoveryData err = json.Unmarshal(body, &disco) if err != nil { log := appctx.GetLogger(ctx) diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index a14ec7e39d..4e996c1ee4 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -198,7 +198,7 @@ func (s *svc) Close() error { } func (s *svc) Unprotected() []string { - return []string{"/status.php", "/remote.php/dav/public-files/", "/apps/files/", "/index.php/f/", "/index.php/s/", "/s/", "/remote.php/dav/ocm/"} + return []string{"/status.php", "/remote.php/dav/public-files/", "/apps/files/", "/index.php/f/", "/index.php/s/", "/s/", "/remote.php/dav/ocm/", "/ocm-provider"} } func (s *svc) Handler() http.Handler { @@ -255,6 +255,10 @@ func (s *svc) Handler() http.Handler { http.Redirect(w, r, rURL, http.StatusMovedPermanently) return } + case "ocm-provider": + // this is to support the current/legacy discovery endpoint for OCM + http.Redirect(w, r, "/.well-known/ocm", http.StatusMovedPermanently) + return } switch head { // the old `/webdav` endpoint uses remote.php/webdav/$path diff --git a/internal/http/services/opencloudmesh/ocmprovider/ocmprovider.go b/internal/http/services/wellknown/ocm.go similarity index 71% rename from internal/http/services/opencloudmesh/ocmprovider/ocmprovider.go rename to internal/http/services/wellknown/ocm.go index 539c7aefca..f8f4d53612 100644 --- a/internal/http/services/opencloudmesh/ocmprovider/ocmprovider.go +++ b/internal/http/services/wellknown/ocm.go @@ -16,27 +16,20 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocmprovider +package wellknown import ( - "context" "encoding/json" "net/http" "net/url" "path/filepath" "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/rhttp/global" - "github.com/cs3org/reva/pkg/utils/cfg" ) const OCMAPIVersion = "1.1.0" -func init() { - global.Register("ocmprovider", New) -} - -type config struct { +type OcmProviderConfig struct { OCMPrefix string `docs:"ocm;The prefix URL where the OCM API is served." mapstructure:"ocm_prefix"` Endpoint string `docs:"This host's full URL. If it's not configured, it is assumed OCM is not available." mapstructure:"endpoint"` Provider string `docs:"reva;A friendly name that defines this service." mapstructure:"provider"` @@ -46,7 +39,7 @@ type config struct { EnableDatatx bool `docs:"false;Whether data transfers are enabled in OCM shares." mapstructure:"enable_datatx"` } -type DiscoveryData struct { +type OcmDiscoveryData struct { Enabled bool `json:"enabled" xml:"enabled"` APIVersion string `json:"apiVersion" xml:"apiVersion"` Endpoint string `json:"endPoint" xml:"endPoint"` @@ -61,11 +54,11 @@ type resourceTypes struct { Protocols map[string]string `json:"protocols"` } -type svc struct { - data *DiscoveryData +type wkocmHandler struct { + data *OcmDiscoveryData } -func (c *config) ApplyDefaults() { +func (c *OcmProviderConfig) ApplyDefaults() { if c.OCMPrefix == "" { c.OCMPrefix = "ocm" } @@ -86,10 +79,11 @@ func (c *config) ApplyDefaults() { } } -func (c *config) prepare() *DiscoveryData { - // generates the (static) data structure to be exposed by /ocm-provider: +func (h *wkocmHandler) init(c *OcmProviderConfig) { + // generates the (static) data structure to be exposed by /.well-known/ocm: // first prepare an empty and disabled payload - d := &DiscoveryData{} + c.ApplyDefaults() + d := &OcmDiscoveryData{} d.Enabled = false d.Endpoint = "" d.APIVersion = OCMAPIVersion @@ -102,12 +96,14 @@ func (c *config) prepare() *DiscoveryData { d.Capabilities = []string{} if c.Endpoint == "" { - return d + h.data = d + return } endpointURL, err := url.Parse(c.Endpoint) if err != nil { - return d + h.data = d + return } // now prepare the enabled one @@ -129,49 +125,24 @@ func (c *config) prepare() *DiscoveryData { }} // for now we hardcode the capabilities, as this is currently only advisory d.Capabilities = []string{"/invite-accepted"} - return d + h.data = d } -// New returns a new ocmprovider object, that implements -// the OCM discovery endpoint specified in +// This handler implements the OCM discovery endpoint specified in // https://cs3org.github.io/OCM-API/docs.html?repo=OCM-API&user=cs3org#/paths/~1ocm-provider/get -func New(ctx context.Context, m map[string]interface{}) (global.Service, error) { - var c config - if err := cfg.Decode(m, &c); err != nil { - return nil, err +func (h *wkocmHandler) Ocm(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if r.UserAgent() == "Nextcloud Server Crawler" { + // Nextcloud decided to only support OCM 1.0 and 1.1, not any 1.x as per SemVer. See + // https://github.com/nextcloud/server/pull/39574#issuecomment-1679191188 + h.data.APIVersion = "1.1" + } else { + h.data.APIVersion = OCMAPIVersion + } + indented, _ := json.MarshalIndent(h.data, "", " ") + if _, err := w.Write(indented); err != nil { + log.Err(err).Msg("Error writing to ResponseWriter") } - return &svc{data: c.prepare()}, nil -} - -// Close performs cleanup. -func (s *svc) Close() error { - return nil -} - -func (s *svc) Prefix() string { - // this is hardcoded as per OCM specifications - return "/ocm-provider" -} - -func (s *svc) Unprotected() []string { - return []string{"/"} -} - -func (s *svc) Handler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log := appctx.GetLogger(r.Context()) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if r.UserAgent() == "Nextcloud Server Crawler" { - // Nextcloud decided to only support OCM 1.0 and 1.1, not any 1.x as per SemVer. See - // https://github.com/nextcloud/server/pull/39574#issuecomment-1679191188 - s.data.APIVersion = "1.1" - } else { - s.data.APIVersion = OCMAPIVersion - } - indented, _ := json.MarshalIndent(s.data, "", " ") - if _, err := w.Write(indented); err != nil { - log.Err(err).Msg("Error writing to ResponseWriter") - } - }) } diff --git a/internal/http/services/wellknown/wellknown.go b/internal/http/services/wellknown/wellknown.go new file mode 100644 index 0000000000..5ca2c2fb0d --- /dev/null +++ b/internal/http/services/wellknown/wellknown.go @@ -0,0 +1,91 @@ +// Copyright 2018-2024 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package wellknown + +import ( + "context" + "net/http" + + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/utils/cfg" + "github.com/go-chi/chi/v5" +) + +func init() { + global.Register("wellknown", New) +} + +type svc struct { + router chi.Router + Conf *config +} + +type config struct { + OCMProvider OcmProviderConfig `mapstructure:"ocmprovider"` +} + +// New returns a new wellknown object. +func New(ctx context.Context, m map[string]interface{}) (global.Service, error) { + var c config + if err := cfg.Decode(m, &c); err != nil { + return nil, err + } + + r := chi.NewRouter() + s := &svc{ + router: r, + Conf: &c, + } + if err := s.routerInit(); err != nil { + return nil, err + } + + return s, nil +} + +func (s *svc) routerInit() error { + wkocmHandler := new(wkocmHandler) + wkocmHandler.init(&s.Conf.OCMProvider) + s.router.Get("/ocm", wkocmHandler.Ocm) + return nil +} + +func (s *svc) Close() error { + return nil +} + +func (s *svc) Prefix() string { + return "/.well-known" +} + +func (s *svc) Unprotected() []string { + return []string{"/", "/ocm"} +} + +func (s *svc) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + log.Debug().Str("path", r.URL.Path).Msg(".well-known routing") + + // unset raw path, otherwise chi uses it to route and then fails to match percent encoded path segments + r.URL.RawPath = "" + s.router.ServeHTTP(w, r) + }) +} diff --git a/pkg/ocm/provider/authorizer/open/open.go b/pkg/ocm/provider/authorizer/open/open.go index 7995c66427..d0af08efb6 100644 --- a/pkg/ocm/provider/authorizer/open/open.go +++ b/pkg/ocm/provider/authorizer/open/open.go @@ -68,7 +68,7 @@ func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmpr } // not yet known: try to discover the remote OCM endpoint - ocmClient := client.NewOCMClient(time.Duration(10)*time.Second, true) + ocmClient := client.NewClient(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") From 2ee547d50a8a30cad883d13ab7568c265517eefb Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Thu, 23 May 2024 16:17:30 +0200 Subject: [PATCH 11/13] Attempt to support both OCM 1.0 and 1.1 when accessing remote shares --- pkg/ocm/storage/received/ocm.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/ocm/storage/received/ocm.go b/pkg/ocm/storage/received/ocm.go index 89e73ef29b..24657342cf 100644 --- a/pkg/ocm/storage/received/ocm.go +++ b/pkg/ocm/storage/received/ocm.go @@ -155,8 +155,16 @@ func (d *driver) webdavClient(ctx context.Context, ref *provider.Reference) (*go return nil, nil, "", err } - // use the secret as bearer authentication according to OCM v1.1+ - c := gowebdav.NewClient(endpoint, "", "") + // inject the secret as 'username' for OCM v1.0 basic auhentication: + // this is deprecated but still used by Nextcloud v28 (latest stable) at least. + parsedURL, err := url.Parse(endpoint) + if err != nil { + return nil, nil, "", err + } + parsedURL.User = url.UserPassword(secret, "") + + // and use it also as bearer authentication according to OCM v1.1+ + c := gowebdav.NewClient(parsedURL.String(), "", "") c.SetHeader("Authorization", "Bearer "+secret) log := appctx.GetLogger(ctx) From 5fa420ebb58bada1f11178d9442a380a62da892a Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Fri, 24 May 2024 10:09:49 +0200 Subject: [PATCH 12/13] Revert "Attempt to support both OCM 1.0 and 1.1 when accessing remote shares" This cannot work, by construction we can't use basic and bearer auth. We could instead store the share type and decide accordingly, but for now Reva only remains capable to connect to OCM 1.1 systems. This reverts commit e6ab3850473989de2eb8afdb287010457e918caf. --- pkg/ocm/storage/received/ocm.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pkg/ocm/storage/received/ocm.go b/pkg/ocm/storage/received/ocm.go index 24657342cf..89e73ef29b 100644 --- a/pkg/ocm/storage/received/ocm.go +++ b/pkg/ocm/storage/received/ocm.go @@ -155,16 +155,8 @@ func (d *driver) webdavClient(ctx context.Context, ref *provider.Reference) (*go return nil, nil, "", err } - // inject the secret as 'username' for OCM v1.0 basic auhentication: - // this is deprecated but still used by Nextcloud v28 (latest stable) at least. - parsedURL, err := url.Parse(endpoint) - if err != nil { - return nil, nil, "", err - } - parsedURL.User = url.UserPassword(secret, "") - - // and use it also as bearer authentication according to OCM v1.1+ - c := gowebdav.NewClient(parsedURL.String(), "", "") + // use the secret as bearer authentication according to OCM v1.1+ + c := gowebdav.NewClient(endpoint, "", "") c.SetHeader("Authorization", "Bearer "+secret) log := appctx.GetLogger(ctx) From 3e50167e12b507695ddb8d1d72ade41798a362d3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Tue, 11 Jun 2024 16:09:05 +0200 Subject: [PATCH 13/13] Removed unused config --- examples/cernbox/cernbox.toml | 2 -- examples/sciencemesh/sciencemesh.toml | 2 -- 2 files changed, 4 deletions(-) diff --git a/examples/cernbox/cernbox.toml b/examples/cernbox/cernbox.toml index e7991b77c2..3f7b47b30d 100644 --- a/examples/cernbox/cernbox.toml +++ b/examples/cernbox/cernbox.toml @@ -393,8 +393,6 @@ insecure = true [http.services.prometheus] address = ":443" -[http.services.sysinfo] - #[http.services.ui] #address = ":443" diff --git a/examples/sciencemesh/sciencemesh.toml b/examples/sciencemesh/sciencemesh.toml index d2d93b50a2..d9fc44bfa9 100644 --- a/examples/sciencemesh/sciencemesh.toml +++ b/examples/sciencemesh/sciencemesh.toml @@ -284,7 +284,5 @@ metrics_data_driver_type = "json" metrics_data_location = "/etc/revad/metrics.json" metrics_record_interval = 5000 -[http.services.sysinfo] - [http.middlewares.cors] [http.middlewares.log]