diff --git a/changelog/unreleased/fix-ocm-share-id.md b/changelog/unreleased/fix-ocm-share-id.md new file mode 100644 index 0000000000..3b1ad26a10 --- /dev/null +++ b/changelog/unreleased/fix-ocm-share-id.md @@ -0,0 +1,5 @@ +Bugfix: fix ocm-share-id + +We now use the share id to correctly identify ocm shares. + +https://github.com/cs3org/reva/pull/4630 \ No newline at end of file diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go index 5cb6183c61..78a604caf4 100644 --- a/internal/grpc/interceptors/auth/scope.go +++ b/internal/grpc/interceptors/auth/scope.go @@ -89,7 +89,7 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s return nil } } - log.Err(err).Msgf("error resolving reference %s under scope %+v", ref.String(), k) + log.Err(err).Interface("ref", ref).Interface("scope", k).Msg("error resolving reference under scope") } } else if ref, ok := extractShareRef(req); ok { @@ -179,6 +179,11 @@ func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb return err } + // for ListOCMSharesRequest, the ref resource id is empty and we set path to . to indicate the root of the share + if ref.GetResourceId() == nil && ref.Path == "." { + ref.ResourceId = share.GetResourceId() + } + if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil { return nil } @@ -213,7 +218,7 @@ func checkRelativeReference(ctx context.Context, requested *provider.Reference, if sharedResource.ParentId == nil { // Is the requested resource part of the shared space? if requested.ResourceId.StorageId != sharedResource.Id.StorageId || requested.ResourceId.SpaceId != sharedResource.Id.SpaceId { - return errtypes.PermissionDenied("access forbidden via public link") + return errtypes.PermissionDenied("space access forbidden via public link") } } else { parentID := sharedResource.ParentId @@ -388,6 +393,11 @@ func extractRefForReaderRole(req interface{}) (*provider.Reference, bool) { return v.GetRef(), true case *provider.UnlockRequest: return v.GetRef(), true + + // OCM shares + case *ocmv1beta1.ListReceivedOCMSharesRequest: + return &provider.Reference{Path: "."}, true // we will try to stat the shared node + } return nil, false diff --git a/internal/grpc/services/gateway/ocmshareprovider.go b/internal/grpc/services/gateway/ocmshareprovider.go index 3cdac238d2..63e2a39eee 100644 --- a/internal/grpc/services/gateway/ocmshareprovider.go +++ b/internal/grpc/services/gateway/ocmshareprovider.go @@ -39,6 +39,11 @@ import ( // TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled. func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) { + if len(req.AccessMethods) == 0 { + return &ocm.CreateOCMShareResponse{ + Status: status.NewInvalidArg(ctx, "access methods cannot be empty"), + }, nil + } c, err := pool.GetOCMShareProviderClient(s.c.OCMShareProviderEndpoint) if err != nil { return &ocm.CreateOCMShareResponse{ @@ -48,7 +53,7 @@ func (s *svc) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest res, err := c.CreateOCMShare(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling CreateShare") + return nil, errors.Wrap(err, "gateway: error calling CreateOCMShare") } status, err := s.addGrant(ctx, req.ResourceId, req.Grantee, req.AccessMethods[0].GetWebdavOptions().Permissions, req.Expiration, nil) @@ -78,7 +83,7 @@ func (s *svc) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest res, err := c.RemoveOCMShare(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling RemoveShare") + return nil, errors.Wrap(err, "gateway: error calling RemoveOCMShare") } return res, nil @@ -102,7 +107,7 @@ func (s *svc) getOCMShare(ctx context.Context, req *ocm.GetOCMShareRequest) (*oc res, err := c.GetOCMShare(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling GetShare") + return nil, errors.Wrap(err, "gateway: error calling GetOCMShare") } return res, nil @@ -134,7 +139,7 @@ func (s *svc) ListOCMShares(ctx context.Context, req *ocm.ListOCMSharesRequest) res, err := c.ListOCMShares(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling ListShares") + return nil, errors.Wrap(err, "gateway: error calling ListOCMShares") } return res, nil @@ -151,7 +156,7 @@ func (s *svc) UpdateOCMShare(ctx context.Context, req *ocm.UpdateOCMShareRequest res, err := c.UpdateOCMShare(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling UpdateShare") + return nil, errors.Wrap(err, "gateway: error calling UpdateOCMShare") } return res, nil @@ -168,7 +173,7 @@ func (s *svc) ListReceivedOCMShares(ctx context.Context, req *ocm.ListReceivedOC res, err := c.ListReceivedOCMShares(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling ListReceivedShares") + return nil, errors.Wrap(err, "gateway: error calling ListReceivedOCMShares") } return res, nil @@ -223,7 +228,7 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive res, err := c.UpdateReceivedOCMShare(ctx, req) if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare") return &ocm.UpdateReceivedOCMShareResponse{ Status: &rpc.Status{ Code: rpc.Code_CODE_INTERNAL, @@ -256,7 +261,7 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive // handle transfer in case it has not already been accepted if s.isTransferShare(share) && req.GetShare().State == ocm.ShareState_SHARE_STATE_ACCEPTED { if share.State == ocm.ShareState_SHARE_STATE_ACCEPTED { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare, share already accepted.") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare, share already accepted.") return &ocm.UpdateReceivedOCMShareResponse{ Status: &rpc.Status{ Code: rpc.Code_CODE_FAILED_PRECONDITION, @@ -268,7 +273,7 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive transferDestinationPath, err := s.getTransferDestinationPath(ctx, req) if err != nil { if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare") return &ocm.UpdateReceivedOCMShareResponse{ Status: &rpc.Status{ Code: rpc.Code_CODE_INTERNAL, @@ -279,7 +284,7 @@ func (s *svc) UpdateReceivedOCMShare(ctx context.Context, req *ocm.UpdateReceive error := s.handleTransfer(ctx, share, transferDestinationPath) if error != nil { - log.Err(error).Msg("gateway: error handling transfer in UpdateReceivedShare") + log.Err(error).Msg("gateway: error handling transfer in UpdateReceivedOCMShare") return &ocm.UpdateReceivedOCMShareResponse{ Status: &rpc.Status{ Code: rpc.Code_CODE_INTERNAL, @@ -309,17 +314,17 @@ func (s *svc) handleTransfer(ctx context.Context, share *ocm.ReceivedShare, tran } destWebdavEndpoint, err := s.getWebdavEndpoint(ctx, granteeIdp) if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare") return err } destWebdavEndpointURL, err := url.Parse(destWebdavEndpoint) if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare: unable to parse webdav endpoint \"" + destWebdavEndpoint + "\" into URL structure") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav endpoint \"" + destWebdavEndpoint + "\" into URL structure") return err } destWebdavHost, err := s.getWebdavHost(ctx, granteeIdp) if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare") return err } var dstWebdavURLString string @@ -330,7 +335,7 @@ func (s *svc) handleTransfer(ctx context.Context, share *ocm.ReceivedShare, tran } dstWebdavHostURL, err := url.Parse(dstWebdavURLString) if err != nil { - log.Err(err).Msg("gateway: error calling UpdateReceivedShare: unable to parse webdav service host \"" + dstWebdavURLString + "\" into URL structure") + log.Err(err).Msg("gateway: error calling UpdateReceivedOCMShare: unable to parse webdav service host \"" + dstWebdavURLString + "\" into URL structure") return err } destServiceHost := dstWebdavHostURL.Host + dstWebdavHostURL.Path @@ -393,7 +398,7 @@ func (s *svc) GetReceivedOCMShare(ctx context.Context, req *ocm.GetReceivedOCMSh res, err := c.GetReceivedOCMShare(ctx, req) if err != nil { - return nil, errors.Wrap(err, "gateway: error calling GetReceivedShare") + return nil, errors.Wrap(err, "gateway: error calling GetReceivedOCMShare") } return res, nil diff --git a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index 5c5824540c..b42ce7fa4f 100644 --- a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -171,9 +171,9 @@ func getResourceType(info *providerpb.ResourceInfo) string { return "unknown" } -func (s *service) webdavURL(ctx context.Context, share *ocm.Share) string { +func (s *service) webdavURL(_ 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, "/dav/ocm", share.Token) + p, _ := url.JoinPath(s.conf.WebDAVEndpoint, "/dav/ocm", share.GetId().GetOpaqueId()) return p } diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index 4704c5e02a..9fb92cd26c 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -405,7 +405,7 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora Status: &rpc.Status{Code: rpc.Code_CODE_INVALID_ARGUMENT, Message: err.Error()}, }, nil } - if resID.SpaceId != utils.PublicStorageSpaceID { + if resID.SpaceId != utils.PublicStorageSpaceID && resID.SpaceId != utils.OCMStorageSpaceID { return &provider.ListStorageSpacesResponse{ // a specific id was requested, return not found instead of empty list Status: &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, @@ -415,7 +415,7 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora } } - info, _, grantee, token, err := s.extractLinkFromScope(ctx) + info, share, grantee, token, err := s.extractLinkFromScope(ctx) if err != nil { switch err.(type) { case errtypes.NotFound: @@ -468,6 +468,9 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora SpaceId: utils.PublicStorageSpaceID, OpaqueId: token, // the link share has no id, only the token } + if ocmShare, ok := share.(*ocm.Share); ok { + root.OpaqueId = ocmShare.GetId().GetOpaqueId() + } if spaceID != nil { switch { case utils.ResourceIDEqual(spaceID, root): @@ -506,7 +509,7 @@ func (s *service) extractLinkFromScope(ctx context.Context) (*provider.ResourceI share := &ocm.Share{} err := utils.UnmarshalJSONToProtoV1(v.Resource.Value, share) if err != nil { - return nil, nil, nil, "", errtypes.InternalError("failed to unmarshal public share") + return nil, nil, nil, "", errtypes.InternalError("failed to unmarshal ocm share") } // the share is minimally populated, we need more than the token diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index ec3fc85a3f..5c5b6e258b 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -185,38 +185,38 @@ func (h *DavHandler) Handler(s *svc) http.Handler { } // OC10 and Nextcloud (OCM 1.0) are using basic auth for carrying the - // shared token. - var token string + // ocm share id. + var ocmshare, sharedSecret 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, net.CtxOCM10, true) + ocmshare = username + sharedSecret = username + r.URL.Path = filepath.Join("/", ocmshare, r.URL.Path) } else { - token, _ = router.ShiftPath(r.URL.Path) - ctx = context.WithValue(ctx, net.CtxOCM10, false) + ocmshare, _ = router.ShiftPath(r.URL.Path) + sharedSecret = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") } - authRes, err := handleOCMAuth(ctx, c, token) + authRes, err := handleOCMAuth(ctx, c, ocmshare, sharedSecret) switch { case err != nil: log.Error().Err(err).Msg("error during ocm authentication") w.WriteHeader(http.StatusInternalServerError) return case authRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED: - log.Debug().Str("token", token).Msg("permission denied") + log.Debug().Str("ocmshare", ocmshare).Msg("permission denied") fallthrough case authRes.Status.Code == rpc.Code_CODE_UNAUTHENTICATED: - log.Debug().Str("token", token).Msg("unauthorized") + log.Debug().Str("ocmshare", ocmshare).Msg("unauthorized") w.WriteHeader(http.StatusUnauthorized) return case authRes.Status.Code == rpc.Code_CODE_NOT_FOUND: - log.Debug().Str("token", token).Msg("not found") + log.Debug().Str("ocmshare", ocmshare).Msg("not found") w.WriteHeader(http.StatusNotFound) return case authRes.Status.Code != rpc.Code_CODE_OK: - log.Error().Str("token", token).Interface("status", authRes.Status).Msg("grpc auth request failed") + log.Error().Str("ocmshare", ocmshare).Interface("status", authRes.Status).Msg("grpc auth request failed") w.WriteHeader(http.StatusInternalServerError) return } @@ -225,7 +225,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { ctx = ctxpkg.ContextSetUser(ctx, authRes.User) ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, authRes.Token) - log.Debug().Str("token", token).Interface("user", authRes.User).Msg("OCM user authenticated") + log.Debug().Str("ocmshare", ocmshare).Interface("user", authRes.User).Msg("OCM user authenticated") r = r.WithContext(ctx) h.OCMSharesHandler.Handler(s).ServeHTTP(w, r) @@ -375,9 +375,10 @@ func handleSignatureAuth(ctx context.Context, selector pool.Selectable[gatewayv1 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, sharedSecret string) (*gatewayv1beta1.AuthenticateResponse, error) { return c.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ - Type: "ocmshares", - ClientId: token, + Type: "ocmshares", + ClientId: ocmshare, + ClientSecret: sharedSecret, }) } diff --git a/internal/http/services/owncloud/ocdav/net/net.go b/internal/http/services/owncloud/ocdav/net/net.go index c0080a41d3..8d94ccd4ad 100644 --- a/internal/http/services/owncloud/ocdav/net/net.go +++ b/internal/http/services/owncloud/ocdav/net/net.go @@ -35,7 +35,6 @@ type ctxKey int const ( // CtxKeyBaseURI is the key of the base URI context field CtxKeyBaseURI ctxKey = iota - CtxOCM10 // NsDav is the Dav ns NsDav = "DAV:" diff --git a/pkg/auth/manager/ocmshares/ocmshares.go b/pkg/auth/manager/ocmshares/ocmshares.go index 5644650376..2cd14b9a24 100644 --- a/pkg/auth/manager/ocmshares/ocmshares.go +++ b/pkg/auth/manager/ocmshares/ocmshares.go @@ -82,10 +82,11 @@ 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, sharedSecret string) (*userpb.User, map[string]*authpb.Scope, error) { + log := appctx.GetLogger(ctx).With().Str("ocmshare", ocmshare).Logger() + // We need to use GetOCMShareByToken, as GetOCMShare would require a user in the context shareRes, err := m.gw.GetOCMShareByToken(ctx, &ocm.GetOCMShareByTokenRequest{ - Token: token, + Token: sharedSecret, }) switch { @@ -103,6 +104,12 @@ func (m *manager) Authenticate(ctx context.Context, token, _ string) (*userpb.Us return nil, nil, errtypes.InternalError(shareRes.Status.Message) } + // compare ocm share id + if shareRes.GetShare().GetId().GetOpaqueId() != ocmshare { + log.Error().Str("persisted", ocmshare).Str("requested", 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() diff --git a/pkg/auth/scope/ocmshare.go b/pkg/auth/scope/ocmshare.go index a868954ec4..af01f0a152 100644 --- a/pkg/auth/scope/ocmshare.go +++ b/pkg/auth/scope/ocmshare.go @@ -28,6 +28,7 @@ import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" @@ -123,6 +124,13 @@ func ocmShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, return true, nil case *provider.ListStorageSpacesRequest: return true, nil + // FIXME why do we need to add them? I think the whole listing of received OCM shares change might be unnecessary ... need to reevaluate that after switching the dav namespace for from /ocm back to /public + case *ocmv1beta1.ListReceivedOCMSharesRequest: + return true, nil + case *ocmv1beta1.ListOCMSharesRequest: + return true, nil + case *collaboration.ListReceivedSharesRequest: + return true, nil case *ocmv1beta1.GetOCMShareRequest: return checkOCMShareRef(&share, v.GetRef()), nil @@ -136,24 +144,24 @@ func ocmShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, func checkStorageRefForOCMShare(s *ocmv1beta1.Share, r *provider.Reference, ns string) bool { if r.ResourceId != nil { - return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) || strings.HasPrefix(r.ResourceId.OpaqueId, s.Token) + return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId()) || strings.HasPrefix(r.ResourceId.OpaqueId, s.GetId().GetOpaqueId()) } // FIXME: the paths here are hardcoded - if strings.HasPrefix(r.GetPath(), "/public/"+s.Token) { + if strings.HasPrefix(r.GetPath(), "/public/"+s.GetId().GetOpaqueId()) { return true } - return strings.HasPrefix(r.GetPath(), filepath.Join(ns, s.Token)) + return strings.HasPrefix(r.GetPath(), filepath.Join(ns, s.GetId().GetOpaqueId())) } func checkOCMShareRef(s *ocmv1beta1.Share, ref *ocmv1beta1.ShareReference) bool { - return ref.GetToken() == s.Token + return ref.GetId().GetOpaqueId() == s.GetId().GetOpaqueId() } // AddOCMShareScope adds the scope to allow access to an OCM share and the share resource. func AddOCMShareScope(share *ocmv1beta1.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { - // Create a new "scope share" to only expose the required fields `ResourceId` and `Token` to the scope. - scopeShare := ocmv1beta1.Share{ResourceId: share.ResourceId, Token: share.Token} + // Create a new "scope share" to only expose the required fields `ResourceId`, `Id` and `Token` to the scope. + scopeShare := ocmv1beta1.Share{ResourceId: share.ResourceId, Id: share.GetId(), Token: share.Token} val, err := utils.MarshalProtoV1ToJSON(&scopeShare) if err != nil { return nil, err diff --git a/pkg/ocm/share/repository/json/json.go b/pkg/ocm/share/repository/json/json.go index 8e32517729..139a320b4e 100644 --- a/pkg/ocm/share/repository/json/json.go +++ b/pkg/ocm/share/repository/json/json.go @@ -396,7 +396,7 @@ func (m *mgr) ListShares(ctx context.Context, user *userpb.User, filters []*ocm. } for _, share := range m.model.Shares { - if utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator) { + if utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator) || utils.UserEqual(user.Id, share.GetGrantee().GetUserId()) { // no filter we return earlier if len(filters) == 0 { ss = append(ss, share) diff --git a/pkg/ocm/storage/received/ocm.go b/pkg/ocm/storage/received/ocm.go index 28acb07a0a..4703476fdf 100644 --- a/pkg/ocm/storage/received/ocm.go +++ b/pkg/ocm/storage/received/ocm.go @@ -73,12 +73,12 @@ func (c *config) ApplyDefaults() { // BearerAuthenticator represents an authenticator that adds a Bearer token to the Authorization header of HTTP requests. type BearerAuthenticator struct { - token string + Token string } // Authorize adds the Bearer token to the Authorization header of the provided HTTP request. func (b BearerAuthenticator) Authorize(_ *http.Client, r *http.Request, _ string) error { - r.Header.Add("Authorization", "Bearer "+b.token) + r.Header.Add("Authorization", "Bearer "+b.Token) return nil } @@ -88,8 +88,8 @@ func (BearerAuthenticator) Verify(*http.Client, *http.Response, string) (bool, e } // Clone creates a new instance of the BearerAuthenticator. -func (BearerAuthenticator) Clone() gowebdav.Authenticator { - return BearerAuthenticator{} +func (b BearerAuthenticator) Clone() gowebdav.Authenticator { + return BearerAuthenticator{Token: b.Token} } // Close is not implemented for the BearerAuthenticator. It always returns nil. @@ -196,7 +196,7 @@ func (d *driver) webdavClient(ctx context.Context, forUser *userpb.UserId, ref * // 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 - c := gowebdav.NewAuthClient(endpoint, gowebdav.NewPreemptiveAuth(BearerAuthenticator{token: secret})) + c := gowebdav.NewAuthClient(endpoint, gowebdav.NewPreemptiveAuth(BearerAuthenticator{Token: secret})) if d.c.Insecure { c.SetTransport(&http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -250,17 +250,21 @@ func convertStatToResourceInfo(ref *provider.Reference, f fs.FileInfo, share *oc t = provider.ResourceType_RESOURCE_TYPE_CONTAINER } - var name string - if share.ResourceType == provider.ResourceType_RESOURCE_TYPE_FILE { - name = share.Name - } else { - name = f.Name() - } - webdavFile, ok := f.(gowebdav.File) if !ok { return nil, errtypes.InternalError("could not get webdav props") } + + var name string + switch { + case share.ResourceType == provider.ResourceType_RESOURCE_TYPE_FILE: + name = share.Name + case webdavFile.Path() == "/": + name = share.Name + default: + name = webdavFile.Name() + } + opaqueid := base64.StdEncoding.EncodeToString([]byte(webdavFile.Path())) // ids are of the format $! @@ -461,6 +465,13 @@ func (d *driver) ListStorageSpaces(ctx context.Context, filters []*provider.List if err != nil { return nil, err } + // FIXME This might have to be ListOCMShares + // these are grants + + lsRes, err := d.gateway.ListOCMShares(ctx, &ocmpb.ListOCMSharesRequest{}) + if err != nil { + return nil, err + } if len(spaceTypes) == 0 { spaceTypes["mountpoint"] = exists @@ -489,6 +500,25 @@ func (d *driver) ListStorageSpaces(ctx context.Context, filters []*provider.List Root: root, } + spaces = append(spaces, space) + } + for _, share := range lsRes.Shares { + root := &provider.ResourceId{ + StorageId: utils.OCMStorageProviderID, + SpaceId: share.Id.OpaqueId, + OpaqueId: share.Id.OpaqueId, + } + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: storagespace.FormatResourceID(*root), + }, + SpaceType: "mountpoint", + Owner: &userv1beta1.User{ + Id: share.Grantee.GetUserId(), + }, + Root: root, + } + spaces = append(spaces, space) } } diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 075aee2128..86d2b06512 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -9,11 +9,11 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + invitev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - revactx "github.com/cs3org/reva/v2/pkg/ctx" "google.golang.org/grpc/metadata" ) @@ -58,7 +58,7 @@ func GetServiceUserContextWithContext(ctx context.Context, gwc gateway.GatewayAP return nil, err } - return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil + return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, authRes.Token), nil } // GetUser gets the specified user @@ -76,11 +76,25 @@ func GetUserWithContext(ctx context.Context, userID *user.UserId, gwc gateway.Ga if err := checkStatusCode("getting user", getUserResponse.GetStatus().GetCode()); err != nil { return nil, err - } + } return getUserResponse.GetUser(), nil } +// GetUserWithContext gets the specified accepted user +func GetAcceptedUserWithContext(ctx context.Context, userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) { + getAcceptedUserResponse, err := gwc.GetAcceptedUser(ctx, &invitev1beta1.GetAcceptedUserRequest{RemoteUserId: userID}) + if err != nil { + return nil, err + } + + if err := checkStatusCode("getting accepted user", getAcceptedUserResponse.GetStatus().GetCode()); err != nil { + return nil, err + } + + return getAcceptedUserResponse.GetRemoteUser(), nil +} + // GetSpace returns the given space func GetSpace(ctx context.Context, spaceID string, gwc gateway.GatewayAPIClient) (*storageprovider.StorageSpace, error) { res, err := gwc.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID)) diff --git a/tests/integration/grpc/ocm_share_test.go b/tests/integration/grpc/ocm_share_test.go index 2474a1633f..926de16945 100644 --- a/tests/integration/grpc/ocm_share_test.go +++ b/tests/integration/grpc/ocm_share_test.go @@ -34,9 +34,12 @@ import ( ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" storagep "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/datagateway" "github.com/cs3org/reva/v2/pkg/conversions" + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/ocm/share" + ocm "github.com/cs3org/reva/v2/pkg/ocm/storage/received" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/rhttp" "github.com/cs3org/reva/v2/pkg/storage/fs/ocis" @@ -45,6 +48,8 @@ import ( "github.com/owncloud/ocis/v2/services/webdav/pkg/net" "github.com/pkg/errors" "github.com/studio-b12/gowebdav" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/fieldmaskpb" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -241,7 +246,40 @@ var _ = Describe("ocm share", func() { Expect(err).ToNot(HaveOccurred()) Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - By("marie access the share") + // get auth context for ocm share + ocmCtx := context.Background() + authRes, err := cernboxgw.Authenticate(ocmCtx, &gatewaypb.AuthenticateRequest{ + Type: "ocmshares", + ClientId: createShareRes.GetShare().GetId().GetOpaqueId(), + ClientSecret: createShareRes.GetShare().GetToken(), + }) + Expect(err).ToNot(HaveOccurred()) + Expect(authRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + // create ocm context + ocmCtx = ctxpkg.ContextSetToken(ocmCtx, authRes.Token) + ocmCtx = metadata.AppendToOutgoingContext(ocmCtx, ctxpkg.TokenHeader, authRes.Token) + // I commented this because we currently do return a space ... but IMO we should not. the share is not accepted / synced yet + /* + // try finding the space by path + lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ + Opaque: &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), + }, + "metadata": { + Decoder: "plain", + Value: []byte("*"), + }, + }, + }}) + Expect(err).ToNot(HaveOccurred()) + Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(lssRes.StorageSpaces).To(HaveLen(0), "pending ocm share should not be listed as a space") + */ + By("marie accepts the share") listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -250,12 +288,51 @@ var _ = Describe("ocm share", func() { share := listRes.Shares[0] Expect(share.Protocols).To(HaveLen(1)) + Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_PENDING)) + + share.State = ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED + _, err = cesnetgw.UpdateReceivedOCMShare(ctxMarie, &ocmv1beta1.UpdateReceivedOCMShareRequest{ + Share: share, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + By("marie accesses the share") + + // try finding the space by path again + lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ + Opaque: &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), + }, + "metadata": { + Decoder: "plain", + Value: []byte("*"), + }, + }, + }}) + Expect(err).ToNot(HaveOccurred()) + Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(lssRes.StorageSpaces).To(HaveLen(1), "accepted ocm share should be listed as a space") + + listRes, err = cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + Expect(listRes.Shares).To(HaveLen(1)) + + share = listRes.Shares[0] + Expect(share.Protocols).To(HaveLen(1)) + Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED)) protocol := share.Protocols[0] webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) Expect(ok).To(BeTrue()) - webdavClient := newWebDAVClient(webdav.WebdavOptions.Uri) + webdavClient := newWebDAVClient(webdav.WebdavOptions) d, err := webdavClient.Read(".") Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal([]byte("test"))) @@ -338,7 +415,7 @@ var _ = Describe("ocm share", func() { Expect(ok).To(BeTrue()) data := []byte("new-content") - webdavClient := newWebDAVClient(webdav.WebdavOptions.Uri) + webdavClient := newWebDAVClient(webdav.WebdavOptions) err = webdavClient.Write(".", data, 0) Expect(err).ToNot(HaveOccurred()) @@ -420,7 +497,7 @@ var _ = Describe("ocm share", func() { Expect(err).ToNot(HaveOccurred()) Expect(createShareRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - By("marie see the content of the folder") + By("marie accepts the share") listRes, err := cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -429,12 +506,64 @@ var _ = Describe("ocm share", func() { share := listRes.Shares[0] Expect(share.Protocols).To(HaveLen(1)) + Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_PENDING)) + + share.State = ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED + _, err = cesnetgw.UpdateReceivedOCMShare(ctxMarie, &ocmv1beta1.UpdateReceivedOCMShareRequest{ + Share: share, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}}, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + // get auth context for ocm share + ocmCtx := context.Background() + authRes, err := cernboxgw.Authenticate(ocmCtx, &gatewaypb.AuthenticateRequest{ + Type: "ocmshares", + ClientId: createShareRes.GetShare().GetId().GetOpaqueId(), + ClientSecret: createShareRes.GetShare().GetToken(), + }) + Expect(err).ToNot(HaveOccurred()) + Expect(authRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + // create ocm context + ocmCtx = ctxpkg.ContextSetToken(ocmCtx, authRes.Token) + ocmCtx = metadata.AppendToOutgoingContext(ocmCtx, ctxpkg.TokenHeader, authRes.Token) + + // try finding the space by path again + lssRes, err := cernboxgw.ListStorageSpaces(ocmCtx, &provider.ListStorageSpacesRequest{ + Opaque: &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/public/" + createShareRes.GetShare().GetId().GetOpaqueId()), + }, + "metadata": { + Decoder: "plain", + Value: []byte("*"), + }, + }, + }}) + Expect(err).ToNot(HaveOccurred()) + Expect(lssRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(lssRes.StorageSpaces).To(HaveLen(1), "accepted ocm share should be listed as a space") + + By("marie see the content of the folder") + listRes, err = cesnetgw.ListReceivedOCMShares(ctxMarie, &ocmv1beta1.ListReceivedOCMSharesRequest{}) + Expect(err).ToNot(HaveOccurred()) + Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + Expect(listRes.Shares).To(HaveLen(1)) + + share = listRes.Shares[0] + Expect(share.Protocols).To(HaveLen(1)) + Expect(share.State).To(Equal(ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED)) protocol := share.Protocols[0] webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) Expect(ok).To(BeTrue()) - webdavClient := newWebDAVClient(webdav.WebdavOptions.Uri) + webdavClient := newWebDAVClient(webdav.WebdavOptions) ok, err = helpers.SameContentWebDAV(webdavClient, "/", structure) Expect(err).ToNot(HaveOccurred()) @@ -546,7 +675,7 @@ var _ = Describe("ocm share", func() { webdav, ok := protocol.Term.(*ocmv1beta1.Protocol_WebdavOptions) Expect(ok).To(BeTrue()) - webdavClient := newWebDAVClient(webdav.WebdavOptions.Uri) + webdavClient := newWebDAVClient(webdav.WebdavOptions) data := []byte("new-content") Expect(webdavClient.Write("new-file", data, 0)).To(Succeed()) @@ -789,8 +918,8 @@ func checkResourceInfoList(l1, l2 []*provider.ResourceInfo) { } } -func newWebDAVClient(uri string) *gowebdav.Client { - webdavClient := gowebdav.NewClient(uri, "", "") +func newWebDAVClient(options *ocmv1beta1.WebDAVProtocol) *gowebdav.Client { + webdavClient := gowebdav.NewAuthClient(options.Uri, gowebdav.NewPreemptiveAuth(ocm.BearerAuthenticator{Token: options.SharedSecret})) webdavClient.SetInterceptor(func(method string, rq *http.Request) { if rq.Body == nil { return