diff --git a/changelog/unreleased/fix-ocmd-tutorial.md b/changelog/unreleased/fix-ocmd-tutorial.md new file mode 100644 index 0000000000..665eb53df9 --- /dev/null +++ b/changelog/unreleased/fix-ocmd-tutorial.md @@ -0,0 +1,7 @@ +Bugfix: Accept new userid idp format + +The format for userid idp [changed](https://github.com/cs3org/cs3apis/pull/159) +and this broke [the ocmd tutorial](https://reva.link/docs/tutorials/share-tutorial/#5-1-4-create-the-share) +This PR makes the provider authorizer interceptor accept both the old and the new string format. + +See https://github.com/cs3org/reva/issues/2285 and https://github.com/cs3org/reva/issues/2285 diff --git a/changelog/unreleased/nextcloud-ocm-share-manager.md b/changelog/unreleased/nextcloud-ocm-share-manager.md new file mode 100644 index 0000000000..0d20e86dbb --- /dev/null +++ b/changelog/unreleased/nextcloud-ocm-share-manager.md @@ -0,0 +1,7 @@ +Enhancement: Nextcloud-based share manager for pkg/ocm/share + +Note that pkg/ocm/share is very similar to pkg/share, +but it deals with cs3/sharing/ocm +whereas pkg/share deals with cs3/sharing/collaboration + +https://github.com/cs3org/reva/pull/2163 diff --git a/examples/nextcloud-integration/revad.toml b/examples/nextcloud-integration/revad.toml index 3410b2da01..f30fbf2da8 100644 --- a/examples/nextcloud-integration/revad.toml +++ b/examples/nextcloud-integration/revad.toml @@ -93,19 +93,19 @@ disable_tus = true ".zmd" = "application/compressed-markdown" [grpc.services.storageprovider.drivers.nextcloud] -end_point = "http://localhost/apps/sciencemesh/" +endpoint = "http://localhost/apps/sciencemesh/" user_layout = "{{.Username}}" [grpc.services.authprovider] auth_manager = "nextcloud" [grpc.services.authprovider.drivers.nextcloud] -end_point = "http://localhost/apps/sciencemesh/" +endpoint = "http://localhost/apps/sciencemesh/" [grpc.services.userprovider] driver = "nextcloud" [grpc.services.userprovider.drivers.nextcloud] -end_point = "http://localhost/apps/sciencemesh/" +endpoint = "http://localhost/apps/sciencemesh/" [http] address = "0.0.0.0:19001" diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index a0cf65ec52..3596070970 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -24,7 +24,6 @@ import ( "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -55,7 +54,7 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } - authProviderReq := &provider.AuthenticateRequest{ + authProviderReq := &authpb.AuthenticateRequest{ ClientId: req.ClientId, ClientSecret: req.ClientSecret, } @@ -200,7 +199,7 @@ func (s *svc) WhoAmI(ctx context.Context, req *gateway.WhoAmIRequest) (*gateway. return res, nil } -func (s *svc) findAuthProvider(ctx context.Context, authType string) (provider.ProviderAPIClient, error) { +func (s *svc) findAuthProvider(ctx context.Context, authType string) (authpb.ProviderAPIClient, error) { c, err := pool.GetAuthRegistryServiceClient(s.c.AuthRegistryEndpoint) if err != nil { err = errors.Wrap(err, "gateway: error getting auth registry client") diff --git a/internal/grpc/services/ocmcore/ocmcore.go b/internal/grpc/services/ocmcore/ocmcore.go index eefd709783..745306fd41 100644 --- a/internal/grpc/services/ocmcore/ocmcore.go +++ b/internal/grpc/services/ocmcore/ocmcore.go @@ -22,11 +22,11 @@ import ( "context" "encoding/json" "fmt" - "strings" ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/share" "github.com/cs3org/reva/pkg/ocm/share/manager/registry" @@ -107,17 +107,11 @@ 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 func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCMCoreShareRequest) (*ocmcore.CreateOCMCoreShareResponse, error) { - parts := strings.Split(req.ProviderId, ":") - if len(parts) < 2 { - return &ocmcore.CreateOCMCoreShareResponse{ - Status: status.NewInternal(ctx, "error decoding resource ID"), - }, nil - } - resource := &provider.ResourceId{ - StorageId: parts[0], - OpaqueId: parts[1], + StorageId: "remote", + OpaqueId: req.Name, } var resourcePermissions *provider.ResourcePermissions @@ -163,6 +157,15 @@ func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCM // For now, we only support user shares. // TODO (ishank011): To be updated once this is decided. Id: &provider.Grantee_UserId{UserId: req.ShareWith}, + // passing this in grant.Grantee.Opaque because ShareGrant itself doesn't have a root opaque. + Opaque: &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "remoteShareId": { + Decoder: "plain", + Value: []byte(req.ProviderId), + }, + }, + }, }, Permissions: &ocm.SharePermissions{ Permissions: resourcePermissions, @@ -178,6 +181,7 @@ func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCM } share, err := s.sm.Share(ctx, resource, grant, req.Name, nil, "", req.Owner, token, shareType) + if err != nil { return &ocmcore.CreateOCMCoreShareResponse{ Status: status.NewInternal(ctx, "error creating ocm core share"), diff --git a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go index e15c5c6448..5fb48f6b55 100644 --- a/internal/grpc/services/ocmshareprovider/ocmshareprovider.go +++ b/internal/grpc/services/ocmshareprovider/ocmshareprovider.go @@ -102,8 +102,20 @@ func (s *service) UnprotectedEndpoints() []string { return []string{} } +// Note: this is for outgoing OCM shares +// This function is used when you for instance +// call `ocm-share-create` in reva-cli. +// For incoming OCM shares from internal/http/services/ocmd/shares.go +// there is the very similar but slightly different function +// CreateOCMCoreShare (the "Core" somehow means "incoming"). +// So make sure to keep in mind the difference between this file for outgoing: +// internal/grpc/services/ocmshareprovider/ocmshareprovider.go +// and the other one for incoming: +// internal/grpc/service/ocmcore/ocmcore.go +// Both functions end up calling the same s.sm.Share function +// on the OCM share manager: +// pkg/ocm/share/manager/{json|nextcloud|...} func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareRequest) (*ocm.CreateOCMShareResponse, error) { - if req.Opaque == nil { return &ocm.CreateOCMShareResponse{ Status: status.NewInternal(ctx, "can't find resource permissions"), @@ -144,6 +156,7 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq // discover share type sharetype := ocm.Share_SHARE_TYPE_REGULAR + // FIXME: https://github.com/cs3org/reva/issues/2402 protocol, ok := req.Opaque.Map["protocol"] if ok { switch protocol.Decoder { @@ -156,9 +169,12 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq Status: status.NewInternal(ctx, "error creating share"), }, nil } + // token = protocol FIXME! } - share, err := s.sm.Share(ctx, req.ResourceId, req.Grant, name, req.RecipientMeshProvider, permissions, nil, "", sharetype) + var sharedSecret string = "" + share, err := s.sm.Share(ctx, req.ResourceId, req.Grant, name, req.RecipientMeshProvider, permissions, nil, sharedSecret, sharetype) + if err != nil { return &ocm.CreateOCMShareResponse{ Status: status.NewInternal(ctx, "error creating share"), diff --git a/internal/http/interceptors/providerauthorizer/providerauthorizer.go b/internal/http/interceptors/providerauthorizer/providerauthorizer.go index 532fe56d23..450614dcc8 100644 --- a/internal/http/interceptors/providerauthorizer/providerauthorizer.go +++ b/internal/http/interceptors/providerauthorizer/providerauthorizer.go @@ -21,6 +21,8 @@ package providerauthorizer import ( "fmt" "net/http" + "net/url" + "strings" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" @@ -83,8 +85,19 @@ func New(m map[string]interface{}, unprotected []string, ocmPrefix string) (glob return } + userIdp := ctxpkg.ContextMustGetUser(ctx).Id.Idp + if !(strings.Contains(userIdp, "://")) { + userIdp = "https://" + userIdp + } + userIdpURL, err := url.Parse(userIdp) + if err != nil { + log.Error().Err(err).Msg("error parsing user idp in provider authorizer") + w.WriteHeader(http.StatusUnauthorized) + return + } + err = authorizer.IsProviderAllowed(ctx, &ocmprovider.ProviderInfo{ - Domain: ctxpkg.ContextMustGetUser(ctx).Id.Idp, + Domain: userIdpURL.Hostname(), }) if err != nil { log.Error().Err(err).Msg("provider not registered in OCM") diff --git a/internal/http/services/ocmd/config.go b/internal/http/services/ocmd/config.go index c5acbff898..79364d890b 100644 --- a/internal/http/services/ocmd/config.go +++ b/internal/http/services/ocmd/config.go @@ -30,7 +30,7 @@ type configData struct { Enabled bool `json:"enabled" xml:"enabled"` APIVersion string `json:"apiVersion" xml:"apiVersion"` Host string `json:"host" xml:"host"` - Endpoint string `json:"endpoint" xml:"endpoint"` + Endpoint string `json:"endPoint" xml:"endPoint"` Provider string `json:"provider" xml:"provider"` ResourceTypes []resourceTypes `json:"resourceTypes" xml:"resourceTypes"` } @@ -61,7 +61,11 @@ func (h *configHandler) init(c *Config) { h.c.Provider = "cernbox" } h.c.Enabled = true - h.c.Endpoint = fmt.Sprintf("https://%s/%s", h.c.Host, c.Prefix) + if len(c.Prefix) > 0 { + h.c.Endpoint = fmt.Sprintf("https://%s/%s", h.c.Host, c.Prefix) + } else { + h.c.Endpoint = fmt.Sprintf("https://%s", h.c.Host) + } h.c.ResourceTypes = []resourceTypes{{ Name: "file", ShareTypes: []string{"user"}, diff --git a/internal/http/services/ocmd/invites.go b/internal/http/services/ocmd/invites.go index f3b5ca6057..0b0c3ff1c8 100644 --- a/internal/http/services/ocmd/invites.go +++ b/internal/http/services/ocmd/invites.go @@ -22,7 +22,11 @@ import ( "encoding/json" "errors" "fmt" + "io" + "mime" "net/http" + "net/url" + "strings" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" @@ -64,6 +68,10 @@ func (h *invitesHandler) Handler() http.Handler { h.forwardInvite(w, r) case "accept": h.acceptInvite(w, r) + case "find-accepted-users": + h.findAcceptedUsers(w, r) + case "generate": + h.generate(w, r) default: w.WriteHeader(http.StatusNotFound) } @@ -128,8 +136,22 @@ func (h *invitesHandler) generateInviteToken(w http.ResponseWriter, r *http.Requ func (h *invitesHandler) forwardInvite(w http.ResponseWriter, r *http.Request) { ctx := r.Context() log := appctx.GetLogger(ctx) - - if r.FormValue("token") == "" || r.FormValue("providerDomain") == "" { + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + var token, providerDomain string + if err == nil && contentType == "application/json" { + defer r.Body.Close() + reqBody, err := io.ReadAll(r.Body) + if err == nil { + reqMap := make(map[string]string) + err = json.Unmarshal(reqBody, &reqMap) + if err == nil { + token, providerDomain = reqMap["token"], reqMap["providerDomain"] + } + } + } else { + token, providerDomain = r.FormValue("token"), r.FormValue("providerDomain") + } + if token == "" || providerDomain == "" { WriteError(w, r, APIErrorInvalidParameter, "token and providerDomain must not be null", nil) return } @@ -140,12 +162,12 @@ func (h *invitesHandler) forwardInvite(w http.ResponseWriter, r *http.Request) { return } - token := &invitepb.InviteToken{ - Token: r.FormValue("token"), + inviteToken := &invitepb.InviteToken{ + Token: token, } providerInfo, err := gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ - Domain: r.FormValue("providerDomain"), + Domain: providerDomain, }) if err != nil { WriteError(w, r, APIErrorServerError, "error sending a grpc get invite by domain info request", err) @@ -157,7 +179,7 @@ func (h *invitesHandler) forwardInvite(w http.ResponseWriter, r *http.Request) { } forwardInviteReq := &invitepb.ForwardInviteRequest{ - InviteToken: token, + InviteToken: inviteToken, OriginSystemProvider: providerInfo.ProviderInfo, } forwardInviteResponse, err := gatewayClient.ForwardInvite(ctx, forwardInviteReq) @@ -170,23 +192,37 @@ func (h *invitesHandler) forwardInvite(w http.ResponseWriter, r *http.Request) { return } - _, err = w.Write([]byte("Accepted invite from: " + r.FormValue("providerDomain"))) + _, err = w.Write([]byte("Accepted invite from: " + providerDomain)) if err != nil { WriteError(w, r, APIErrorServerError, "error writing token data", err) return } w.WriteHeader(http.StatusOK) - log.Info().Msgf("Invite forwarded to: %s", r.FormValue("providerDomain")) + log.Info().Msgf("Invite forwarded to: %s", providerDomain) } func (h *invitesHandler) acceptInvite(w http.ResponseWriter, r *http.Request) { ctx := r.Context() log := appctx.GetLogger(ctx) - - token, userID, recipientProvider := r.FormValue("token"), r.FormValue("userID"), r.FormValue("recipientProvider") - name, email := r.FormValue("name"), r.FormValue("email") - if token == "" || userID == "" || recipientProvider == "" || email == "" { + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + var token, userID, recipientProvider, name, email string + if err == nil && contentType == "application/json" { + defer r.Body.Close() + reqBody, err := io.ReadAll(r.Body) + if err == nil { + reqMap := make(map[string]string) + err = json.Unmarshal(reqBody, &reqMap) + if err == nil { + token, userID, recipientProvider = reqMap["token"], reqMap["userID"], reqMap["recipientProvider"] + name, email = reqMap["name"], reqMap["email"] + } + } + } else { + token, userID, recipientProvider = r.FormValue("token"), r.FormValue("userID"), r.FormValue("recipientProvider") + name, email = r.FormValue("name"), r.FormValue("email") + } + if token == "" || userID == "" || recipientProvider == "" { WriteError(w, r, APIErrorInvalidParameter, "missing parameters in request", nil) return } @@ -202,8 +238,19 @@ func (h *invitesHandler) acceptInvite(w http.ResponseWriter, r *http.Request) { WriteError(w, r, APIErrorServerError, fmt.Sprintf("error retrieving client IP from request: %s", r.RemoteAddr), err) return } + + if !(strings.Contains(recipientProvider, "://")) { + recipientProvider = "https://" + recipientProvider + } + + recipientProviderURL, err := url.Parse(recipientProvider) + if err != nil { + WriteError(w, r, APIErrorServerError, fmt.Sprintf("error parseing recipientProvider URL: %s", recipientProvider), err) + return + } + providerInfo := ocmprovider.ProviderInfo{ - Domain: recipientProvider, + Domain: recipientProviderURL.Hostname(), Services: []*ocmprovider.Service{ { Host: clientIP, @@ -250,3 +297,53 @@ func (h *invitesHandler) acceptInvite(w http.ResponseWriter, r *http.Request) { log.Info().Msgf("User: %+v added to accepted users.", userObj) } + +func (h *invitesHandler) findAcceptedUsers(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + + ctx := r.Context() + gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + WriteError(w, r, APIErrorServerError, "error getting gateway grpc client", err) + return + } + + response, err := gatewayClient.FindAcceptedUsers(ctx, &invitepb.FindAcceptedUsersRequest{ + Filter: "", + }) + if err != nil { + WriteError(w, r, APIErrorServerError, "error sending a grpc find accepted users request", err) + return + } + + indentedResponse, _ := json.MarshalIndent(response, "", " ") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if _, err := w.Write(indentedResponse); err != nil { + log.Err(err).Msg("Error writing to ResponseWriter") + } +} + +func (h *invitesHandler) generate(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + + ctx := r.Context() + gatewayClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + WriteError(w, r, APIErrorServerError, "error getting gateway grpc client", err) + return + } + + response, err := gatewayClient.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{}) + if err != nil { + WriteError(w, r, APIErrorServerError, "error sending a grpc generate invite token request", err) + return + } + + indentedResponse, _ := json.MarshalIndent(response, "", " ") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if _, err := w.Write(indentedResponse); err != nil { + log.Err(err).Msg("Error writing to ResponseWriter") + } +} diff --git a/internal/http/services/ocmd/ocmd.go b/internal/http/services/ocmd/ocmd.go index da6ebe46ba..776cf36414 100644 --- a/internal/http/services/ocmd/ocmd.go +++ b/internal/http/services/ocmd/ocmd.go @@ -47,9 +47,9 @@ type Config struct { func (c *Config) init() { c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) - if c.Prefix == "" { - c.Prefix = "ocm" - } + // if c.Prefix == "" { + // c.Prefix = "ocm" + // } } type svc struct { @@ -58,6 +58,7 @@ type svc struct { NotificationsHandler *notificationsHandler ConfigHandler *configHandler InvitesHandler *invitesHandler + SendHandler *sendHandler } // New returns a new ocmd object @@ -76,10 +77,14 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) s.NotificationsHandler = new(notificationsHandler) s.ConfigHandler = new(configHandler) s.InvitesHandler = new(invitesHandler) + s.SendHandler = new(sendHandler) s.SharesHandler.init(s.Conf) s.NotificationsHandler.init(s.Conf) + log.Debug().Str("initializing ConfigHandler Host", s.Conf.Host) + s.ConfigHandler.init(s.Conf) s.InvitesHandler.init(s.Conf) + s.SendHandler.init(s.Conf) return s, nil } @@ -94,7 +99,7 @@ func (s *svc) Prefix() string { } func (s *svc) Unprotected() []string { - return []string{"/invites/accept", "/shares", "/ocm-provider"} + return []string{"/invites/accept", "/shares", "/ocm-provider", "/notifications"} } func (s *svc) Handler() http.Handler { @@ -120,9 +125,11 @@ func (s *svc) Handler() http.Handler { case "invites": s.InvitesHandler.Handler().ServeHTTP(w, r) return + case "send": + s.SendHandler.Handler().ServeHTTP(w, r) } - log.Warn().Msg("resource not found") + log.Warn().Msg("request not handled") w.WriteHeader(http.StatusNotFound) }) } diff --git a/internal/http/services/ocmd/send.go b/internal/http/services/ocmd/send.go new file mode 100644 index 0000000000..7391a9c0ec --- /dev/null +++ b/internal/http/services/ocmd/send.go @@ -0,0 +1,200 @@ +// Copyright 2018-2021 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 ocmd + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "strconv" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "google.golang.org/grpc/metadata" + + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" +) + +type sendHandler struct { + GatewaySvc string +} + +func (h *sendHandler) init(c *Config) { + h.GatewaySvc = c.GatewaySvc +} + +func (h *sendHandler) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + defer r.Body.Close() + reqBody, err := io.ReadAll(r.Body) + if err != nil { + log.Error().Msg("cannot read POST body!") + w.WriteHeader(http.StatusInternalServerError) + return + } + + reqMap := make(map[string]string) + err = json.Unmarshal(reqBody, &reqMap) + if err != nil { + log.Error().Msg("cannot parse POST body!") + w.WriteHeader(http.StatusInternalServerError) + return + } + sourcePath, targetPath, sharedSecret := reqMap["sourcePath"], reqMap["targetPath"], reqMap["sharedSecret"] + recipientUsername, recipientHost := reqMap["recipientUsername"], reqMap["recipientHost"] + loginType, loginUsername, loginPassword := reqMap["loginType"], reqMap["loginUsername"], reqMap["loginPassword"] + + // "sourcePath": "other", + // "targetPath": "sciencemesh\/other", + // "type": "dir", (unused) + // "recipientUsername": "marie", + // "recipientHost": "revanc2.docker", + // "loginType": "basic", + // "loginUsername": "einstein", + // "loginPassword": "Ny4Nv6WLoC1o70kVgrVOZLZ2vRgPjuej" + + gatewayAddr := h.GatewaySvc + gatewayClient, err := pool.GetGatewayServiceClient(gatewayAddr) + if err != nil { + log.Error().Msg("cannot get grpc client!") + w.WriteHeader(http.StatusInternalServerError) + return + } + loginReq := &gateway.AuthenticateRequest{ + Type: loginType, + ClientId: loginUsername, + ClientSecret: loginPassword, + } + + loginCtx := context.Background() + res, err := gatewayClient.Authenticate(loginCtx, loginReq) + if err != nil { + log.Error().Msg("error logging in") + w.WriteHeader(http.StatusInternalServerError) + return + } + authCtx := context.Background() + + authCtx = ctxpkg.ContextSetToken(authCtx, res.Token) + authCtx = metadata.AppendToOutgoingContext(authCtx, ctxpkg.TokenHeader, res.Token) + + // copied from cmd/reva/public-share-create.go: + ref := &provider.Reference{Path: sourcePath} + + req := &provider.StatRequest{Ref: ref} + res2, err := gatewayClient.Stat(authCtx, req) + if err != nil { + log.Error().Msg("error sending: stat file/folder to share") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if res2.Status.Code != rpc.Code_CODE_OK { + log.Error().Msg("error returned: stat file/folder to share") + w.WriteHeader(http.StatusInternalServerError) + return + } + + // see cmd/reva/share-creat.go:getSharePerm + readerPermission := &provider.ResourcePermissions{ + GetPath: true, + InitiateFileDownload: true, + ListFileVersions: true, + ListContainer: true, + Stat: true, + } + + grant := &ocm.ShareGrant{ + Permissions: &ocm.SharePermissions{ + Permissions: readerPermission, + }, + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: &userpb.UserId{ + Idp: recipientHost, + OpaqueId: recipientUsername, + }, + }, + }, + } + recipientProviderInfo, err := gatewayClient.GetInfoByDomain(authCtx, &ocmprovider.GetInfoByDomainRequest{ + Domain: recipientHost, + }) + if err != nil { + WriteError(w, r, APIErrorServerError, "error sending a grpc get invite by domain info request", err) + return + } + if recipientProviderInfo.Status.Code != rpc.Code_CODE_OK { + WriteError(w, r, APIErrorServerError, "grpc forward invite request failed", errors.New(recipientProviderInfo.Status.Message)) + return + } + + shareRequest := &ocm.CreateOCMShareRequest{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "permissions": { + Decoder: "plain", + Value: []byte(strconv.Itoa(0)), + }, + "name": { + Decoder: "plain", + Value: []byte(targetPath), + }, + "protocol": { + Decoder: "plain", + Value: []byte("webdav"), // TODO: support datatx too + }, + "token": { + Decoder: "plain", + Value: []byte(sharedSecret), + }, + }, + }, + ResourceId: res2.Info.Id, + Grant: grant, + RecipientMeshProvider: recipientProviderInfo.ProviderInfo, + } + + shareRes, err := gatewayClient.CreateOCMShare(authCtx, shareRequest) + if err != nil { + log.Error().Msg("error sending: CreateShare") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if shareRes.Status.Code != rpc.Code_CODE_OK { + log.Error().Msg("error returned: CreateShare") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + }) +} diff --git a/internal/http/services/ocmd/shares.go b/internal/http/services/ocmd/shares.go index 60ebfa9f6d..3e1e443317 100644 --- a/internal/http/services/ocmd/shares.go +++ b/internal/http/services/ocmd/shares.go @@ -22,7 +22,12 @@ import ( "encoding/json" "errors" "fmt" + "io" + "math" + "mime" "net/http" + "reflect" + "strings" "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -59,15 +64,47 @@ func (h *sharesHandler) Handler() http.Handler { func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { ctx := r.Context() log := appctx.GetLogger(ctx) - - shareWith, protocol, meshProvider := r.FormValue("shareWith"), r.FormValue("protocol"), r.FormValue("meshProvider") - resource, providerID, owner := r.FormValue("name"), r.FormValue("providerId"), r.FormValue("owner") + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + var shareWith, meshProvider, resource, providerID, owner string + var protocol map[string]interface{} + if err == nil && contentType == "application/json" { + defer r.Body.Close() + reqBody, err := io.ReadAll(r.Body) + if err == nil { + reqMap := make(map[string]interface{}) + err = json.Unmarshal(reqBody, &reqMap) + if err == nil { + meshProvider = reqMap["meshProvider"].(string) // FIXME: get this from sharedBy string? + shareWith, protocol = reqMap["shareWith"].(string), reqMap["protocol"].(map[string]interface{}) + resource, owner = reqMap["name"].(string), reqMap["owner"].(string) + // Note that if an OCM request were to go directly from a Nextcloud server + // to a Reva server, it will (incorrectly) sends an integer provider_id instead a string one. + // This doesn't happen when using the sciencemesh-nextcloud app, but in order to make the OCM + // test suite pass, this code works around that: + if reflect.ValueOf(reqMap["providerId"]).Kind() == reflect.Float64 { + providerID = fmt.Sprintf("%i", int(math.Round(reqMap["providerId"].(float64)))) + } else { + providerID = reqMap["providerId"].(string) + } + } else { + WriteError(w, r, APIErrorInvalidParameter, "could not parse json request body", nil) + } + } + } else { + var protocolJson string + shareWith, protocolJson, meshProvider = r.FormValue("shareWith"), r.FormValue("protocol"), r.FormValue("meshProvider") + resource, providerID, owner = r.FormValue("name"), r.FormValue("providerId"), r.FormValue("owner") + err = json.Unmarshal([]byte(protocolJson), &protocol) + if err != nil { + WriteError(w, r, APIErrorInvalidParameter, "invalid protocol parameters", nil) + } + } if resource == "" || providerID == "" || owner == "" { WriteError(w, r, APIErrorInvalidParameter, "missing details about resource to be shared", nil) return } - if shareWith == "" || protocol == "" || meshProvider == "" { + if shareWith == "" || protocol["name"] == "" || meshProvider == "" { WriteError(w, r, APIErrorInvalidParameter, "missing request parameters", nil) return } @@ -104,8 +141,9 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } + var shareWithParts []string = strings.Split(shareWith, "@") userRes, err := gatewayClient.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{OpaqueId: shareWith}, + UserId: &userpb.UserId{OpaqueId: shareWithParts[0]}, }) if err != nil { WriteError(w, r, APIErrorServerError, "error searching recipient", err) @@ -116,26 +154,22 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } - var protocolDecoded map[string]interface{} - err = json.Unmarshal([]byte(protocol), &protocolDecoded) - if err != nil { - WriteError(w, r, APIErrorInvalidParameter, "invalid protocol parameters", nil) - } - var permissions conversions.Permissions var token string - options, ok := protocolDecoded["options"].(map[string]interface{}) + options, ok := protocol["options"].(map[string]interface{}) if !ok { WriteError(w, r, APIErrorInvalidParameter, "protocol: webdav token not provided", nil) return } - token, ok = options["token"].(string) + token, ok = options["sharedSecret"].(string) if !ok { - WriteError(w, r, APIErrorInvalidParameter, "protocol: webdav token not provided", nil) - return + token, ok = options["token"].(string) + if !ok { + WriteError(w, r, APIErrorInvalidParameter, "protocol: webdav token not provided", nil) + return + } } - var role *conversions.Role pval, ok := options["permissions"].(int) if !ok { @@ -166,7 +200,7 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { Owner: ownerID, ShareWith: userRes.User.GetId(), Protocol: &ocmcore.Protocol{ - Name: protocolDecoded["name"].(string), + Name: protocol["name"].(string), Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ "permissions": { @@ -181,7 +215,6 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { }, }, } - createShareResponse, err := gatewayClient.CreateOCMCoreShare(ctx, createShareReq) if err != nil { WriteError(w, r, APIErrorServerError, "error sending a grpc create ocm core share request", err) @@ -208,13 +241,14 @@ func (h *sharesHandler) createShare(w http.ResponseWriter, r *http.Request) { return } + w.WriteHeader(http.StatusCreated) + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonOut) if err != nil { WriteError(w, r, APIErrorServerError, "error writing shares data", err) return } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) log.Info().Msg("Share created.") } diff --git a/pkg/auth/manager/nextcloud/nextcloud.go b/pkg/auth/manager/nextcloud/nextcloud.go index 7992f36e59..2e9647698c 100644 --- a/pkg/auth/manager/nextcloud/nextcloud.go +++ b/pkg/auth/manager/nextcloud/nextcloud.go @@ -42,14 +42,16 @@ func init() { // Manager is the Nextcloud-based implementation of the auth.Manager interface // see https://github.com/cs3org/reva/blob/v1.13.0/pkg/auth/auth.go#L32-L35 type Manager struct { - client *http.Client - endPoint string + client *http.Client + sharedSecret string + endPoint string } // AuthManagerConfig contains config for a Nextcloud-based AuthManager type AuthManagerConfig struct { - EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` - MockHTTP bool `mapstructure:"mock_http"` + EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` + SharedSecret string `mapstructure:"shared_secret"` + MockHTTP bool `mapstructure:"mock_http"` } // Action describes a REST request to forward to the Nextcloud backend @@ -93,12 +95,16 @@ func NewAuthManager(c *AuthManagerConfig) (*Manager, error) { // Wait for SetHTTPClient to be called later client = nil } else { + if len(c.EndPoint) == 0 { + return nil, errors.New("Please specify 'endpoint' in '[grpc.services.authprovider.auth_managers.nextcloud]'") + } client = &http.Client{} } return &Manager{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" - client: client, + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + sharedSecret: c.SharedSecret, + client: client, }, nil } @@ -115,8 +121,9 @@ func (am *Manager) SetHTTPClient(c *http.Client) { func (am *Manager) do(ctx context.Context, a Action) (int, []byte, error) { log := appctx.GetLogger(ctx) url := am.endPoint + "~" + a.username + "/api/auth/" + a.verb - log.Info().Msgf("am.do %s %s", url, a.argS) + log.Info().Msgf("am.do %s %s %s", url, a.argS, am.sharedSecret) req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) + req.Header.Set("X-Reva-Secret", am.sharedSecret) if err != nil { return 0, nil, err } diff --git a/pkg/auth/manager/nextcloud/nextcloud_server_mock.go b/pkg/auth/manager/nextcloud/nextcloud_server_mock.go index 67914a0887..34936ea4fc 100644 --- a/pkg/auth/manager/nextcloud/nextcloud_server_mock.go +++ b/pkg/auth/manager/nextcloud/nextcloud_server_mock.go @@ -55,22 +55,13 @@ func GetNextcloudServerMock(called *[]string) http.Handler { panic("Error reading response into buffer") } var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String()) - fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String())) - fmt.Printf("Nextcloud Server Mock key %s\n", key) *called = append(*called, key) response := responses[key] if (response == Response{}) { key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState) - fmt.Printf("Nextcloud Server Mock key with State %s\n", key) - // *called = append(*called, key) response = responses[key] } if (response == Response{}) { - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - fmt.Printf("Nextcloud Server Mock key not found! %s\n", key) - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty} } serverState = responses[key].newServerState diff --git a/pkg/auth/manager/nextcloud/nextcloud_test.go b/pkg/auth/manager/nextcloud/nextcloud_test.go index dd14b8a1e8..47353efcfa 100644 --- a/pkg/auth/manager/nextcloud/nextcloud_test.go +++ b/pkg/auth/manager/nextcloud/nextcloud_test.go @@ -20,7 +20,6 @@ package nextcloud_test import ( "context" - "fmt" "os" "google.golang.org/grpc/metadata" @@ -43,7 +42,6 @@ func setUpNextcloudServer() (*nextcloud.Manager, *[]string, func()) { var conf *nextcloud.AuthManagerConfig ncHost := os.Getenv("NEXTCLOUD") - fmt.Printf(`NEXTCLOUD env var: "%s"`, ncHost) if len(ncHost) == 0 { conf = &nextcloud.AuthManagerConfig{ EndPoint: "http://mock.com/apps/sciencemesh/", diff --git a/pkg/ocm/invite/manager/json/json.go b/pkg/ocm/invite/manager/json/json.go index f50846fa10..6a9b6d483f 100644 --- a/pkg/ocm/invite/manager/json/json.go +++ b/pkg/ocm/invite/manager/json/json.go @@ -200,10 +200,19 @@ func (m *manager) GenerateToken(ctx context.Context) (*invitepb.InviteToken, err func (m *manager) ForwardInvite(ctx context.Context, invite *invitepb.InviteToken, originProvider *ocmprovider.ProviderInfo) error { contextUser := ctxpkg.ContextMustGetUser(ctx) + recipientProvider := contextUser.GetId().GetIdp() + + // recipientProvider should be a URL, see https://github.com/cs3org/OCM-API/pull/41/files#diff-9cfca4a1b73e1e28e30fb9b0b984aad6d4caaf0819c61ed40ad338600531f745R569 + // And going forward, UserId Idp will also include the https:// prefix, see https://github.com/cs3org/cs3apis/pull/159 + // But historically, reva used UserId Idps that were domains instead of full URLs, so we need to support that case too, see + // https://github.com/cs3org/reva/issues/2288 + if !(strings.Contains(recipientProvider, "://")) { + recipientProvider = "https://" + recipientProvider + } requestBody := url.Values{ "token": {invite.GetToken()}, "userID": {contextUser.GetId().GetOpaqueId()}, - "recipientProvider": {contextUser.GetId().GetIdp()}, + "recipientProvider": {recipientProvider}, "email": {contextUser.GetMail()}, "name": {contextUser.GetDisplayName()}, } diff --git a/pkg/ocm/provider/authorizer/json/json.go b/pkg/ocm/provider/authorizer/json/json.go index ca62034900..575287c586 100644 --- a/pkg/ocm/provider/authorizer/json/json.go +++ b/pkg/ocm/provider/authorizer/json/json.go @@ -84,9 +84,29 @@ type authorizer struct { conf *config } +func normalizeDomain(d string) (string, error) { + var urlString string + if strings.Contains(d, "://") { + urlString = d + } else { + urlString = "https://" + d + } + + u, err := url.Parse(urlString) + if err != nil { + return "", err + } + + return u.Hostname(), nil +} + func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmprovider.ProviderInfo, error) { + normalizedDomain, err := normalizeDomain(domain) + if err != nil { + return nil, err + } for _, p := range a.providers { - if strings.Contains(p.Domain, domain) { + if strings.Contains(p.Domain, normalizedDomain) { return p, nil } } @@ -94,11 +114,15 @@ func (a *authorizer) GetInfoByDomain(ctx context.Context, domain string) (*ocmpr } func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovider.ProviderInfo) error { - + var err error + normalizedDomain, err := normalizeDomain(provider.Domain) + if err != nil { + return err + } var providerAuthorized bool - if provider.Domain != "" { + if normalizedDomain != "" { for _, p := range a.providers { - if p.Domain == provider.Domain { + if p.Domain == normalizedDomain { providerAuthorized = true break } @@ -117,9 +141,8 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovide } var ocmHost string - var err error for _, p := range a.providers { - if p.Domain == provider.Domain { + if p.Domain == normalizedDomain { ocmHost, err = a.getOCMHost(p) if err != nil { return err @@ -127,7 +150,7 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovide } } if ocmHost == "" { - return errors.Wrap(err, "json: ocm host not specified for mesh provider") + return errtypes.InternalError("json: ocm host not specified for mesh provider") } providerAuthorized = false diff --git a/pkg/ocm/provider/authorizer/mentix/mentix.go b/pkg/ocm/provider/authorizer/mentix/mentix.go index cc665e001b..eee16d1407 100644 --- a/pkg/ocm/provider/authorizer/mentix/mentix.go +++ b/pkg/ocm/provider/authorizer/mentix/mentix.go @@ -180,7 +180,7 @@ func (a *authorizer) IsProviderAllowed(ctx context.Context, provider *ocmprovide } } if ocmHost == "" { - return errors.Wrap(err, "json: ocm host not specified for mesh provider") + return errtypes.InternalError("mentix: ocm host not specified for mesh provider") } providerAuthorized = false diff --git a/pkg/ocm/share/manager/json/json.go b/pkg/ocm/share/manager/json/json.go index 791da87d43..8a1f9545ae 100644 --- a/pkg/ocm/share/manager/json/json.go +++ b/pkg/ocm/share/manager/json/json.go @@ -23,11 +23,7 @@ import ( "encoding/json" "fmt" "io/ioutil" - "net/http" - "net/url" "os" - "path" - "strings" "sync" "time" @@ -39,8 +35,9 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/share" + "github.com/cs3org/reva/pkg/ocm/share/manager/registry" - "github.com/cs3org/reva/pkg/rhttp" + "github.com/cs3org/reva/pkg/ocm/share/sender" "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" "github.com/mitchellh/mapstructure" @@ -48,8 +45,6 @@ import ( "google.golang.org/genproto/protobuf/field_mask" ) -const createOCMCoreShareEndpoint = "shares" - func init() { registry.Register("json", New) } @@ -73,9 +68,6 @@ func New(m map[string]interface{}) (share.Manager, error) { mgr := &mgr{ c: c, model: model, - client: rhttp.GetHTTPClient( - rhttp.Timeout(5 * time.Second), - ), } return mgr, nil @@ -141,7 +133,6 @@ type mgr struct { c *config sync.Mutex // concurrent access to the file model *shareModel - client *http.Client } func (m *shareModel) Save() error { @@ -186,18 +177,12 @@ func genID() string { return uuid.New().String() } -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") -} - +// Called from both grpc CreateOCMShare for outgoing +// and http /ocm/shares for incoming +// pi is provider info +// pm is permissions func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGrant, name string, pi *ocmprovider.ProviderInfo, pm string, owner *userpb.UserId, token string, st ocm.Share_ShareType) (*ocm.Share, error) { - id := genID() now := time.Now().UnixNano() ts := &typespb.Timestamp{ @@ -269,84 +254,42 @@ func (m *mgr) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGr } if isOwnersMeshProvider { - token, ok := ctxpkg.ContextGetToken(ctx) - if !ok { - return nil, errors.New("Could not get token from context") - } - var protocol []byte + // token, ok := ctxpkg.ContextGetToken(ctx) + // if !ok { + // return nil, errors.New("Could not get token from context") + // } + var protocol map[string]interface{} if st == ocm.Share_SHARE_TYPE_TRANSFER { - protocol, err = json.Marshal( - map[string]interface{}{ - "name": "datatx", - "options": map[string]string{ - "permissions": pm, - "token": token, - }, + protocol = map[string]interface{}{ + "name": "datatx", + "options": map[string]string{ + "permissions": pm, + "token": token, }, - ) - if err != nil { - err = errors.Wrap(err, "error marshalling protocol data") - return nil, err } - } else { - protocol, err = json.Marshal( - map[string]interface{}{ - "name": "webdav", - "options": map[string]string{ - "permissions": pm, - "token": ctxpkg.ContextMustGetToken(ctx), - }, + protocol = map[string]interface{}{ + "name": "webdav", + "options": map[string]string{ + "permissions": pm, + "token": token, }, - ) - if err != nil { - err = errors.Wrap(err, "error marshalling protocol data") - return nil, err } } - - requestBody := url.Values{ - "shareWith": {g.Grantee.GetUserId().OpaqueId}, - "name": {name}, - "providerId": {fmt.Sprintf("%s:%s", md.StorageId, md.OpaqueId)}, - "owner": {userID.OpaqueId}, - "protocol": {string(protocol)}, - "meshProvider": {userID.Idp}, - } - - ocmEndpoint, err := getOCMEndpoint(pi) - if err != nil { - return nil, err - } - u, err := url.Parse(ocmEndpoint) - if err != nil { - return nil, err - } - u.Path = path.Join(u.Path, createOCMCoreShareEndpoint) - recipientURL := u.String() - - req, err := http.NewRequest("POST", recipientURL, strings.NewReader(requestBody.Encode())) - if err != nil { - return nil, errors.Wrap(err, "json: error framing post request") + requestBodyMap := map[string]interface{}{ + "shareWith": g.Grantee.GetUserId().OpaqueId, + "name": name, + "providerId": fmt.Sprintf("%s:%s", md.StorageId, md.OpaqueId), + "owner": userID.OpaqueId, + "protocol": protocol, + "meshProvider": userID.Idp, // FIXME: move this into the 'owner' string? } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") - - resp, err := m.client.Do(req) + err = sender.Send(requestBodyMap, pi) if err != nil { - err = errors.Wrap(err, "json: error sending post request") + err = errors.Wrap(err, "error sending OCM POST") return nil, err } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - respBody, e := ioutil.ReadAll(resp.Body) - if e != nil { - e = errors.Wrap(e, "json: error reading request body") - return nil, e - } - err = errors.Wrap(errors.New(fmt.Sprintf("%s: %s", resp.Status, string(respBody))), "json: error sending create ocm core share post request") - return nil, err - } } m.Lock() diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud.go b/pkg/ocm/share/manager/nextcloud/nextcloud.go index b00673f7cf..c3ba49eb69 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud.go @@ -23,41 +23,58 @@ import ( "context" "encoding/json" "io" + "math/rand" "net/http" "strings" + "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/utils" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/ocm/share" "github.com/cs3org/reva/pkg/ocm/share/manager/registry" + "github.com/cs3org/reva/pkg/ocm/share/sender" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/genproto/protobuf/field_mask" ) +var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func randSeq(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + func init() { + rand.Seed(time.Now().UnixNano()) registry.Register("nextcloud", New) } // Manager is the Nextcloud-based implementation of the share.Manager interface // see https://github.com/cs3org/reva/blob/v1.13.0/pkg/ocm/share/share.go#L30-L57 type Manager struct { - client *http.Client - endPoint string + client *http.Client + sharedSecret string + endPoint string } // ShareManagerConfig contains config for a Nextcloud-based ShareManager type ShareManagerConfig struct { - EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` - MockHTTP bool `mapstructure:"mock_http"` + EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` + SharedSecret string `mapstructure:"shared_secret"` + MockHTTP bool `mapstructure:"mock_http"` } // Action describes a REST request to forward to the Nextcloud backend @@ -81,8 +98,8 @@ type ShareAltMap struct { Grantee *GranteeAltMap `json:"grantee"` Owner *userpb.UserId `json:"owner"` Creator *userpb.UserId `json:"creator"` - Ctime *types.Timestamp `json:"ctime"` - Mtime *types.Timestamp `json:"mtime"` + Ctime *typespb.Timestamp `json:"ctime"` + Mtime *typespb.Timestamp `json:"mtime"` } // ReceivedShareAltMap is an alternative map to JSON-unmarshal a ReceivedShare @@ -134,12 +151,16 @@ func NewShareManager(c *ShareManagerConfig) (*Manager, error) { // Wait for SetHTTPClient to be called later client = nil } else { + if len(c.EndPoint) == 0 { + return nil, errors.New("Please specify 'endpoint' in '[grpc.services.ocmshareprovider.drivers.nextcloud]' and '[grpc.services.ocmcore.drivers.nextcloud]'") + } client = &http.Client{} } return &Manager{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" - client: client, + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + sharedSecret: c.SharedSecret, + client: client, }, nil } @@ -148,18 +169,28 @@ func (sm *Manager) SetHTTPClient(c *http.Client) { sm.client = c } -func (sm *Manager) do(ctx context.Context, a Action) (int, []byte, error) { - log := appctx.GetLogger(ctx) +func getUsername(ctx context.Context) string { user, err := getUser(ctx) if err != nil { - return 0, nil, err + return "unknown" + } + if len(user.Username) > 0 { + return user.Username + } + if len(user.Id.OpaqueId) > 0 { + return user.Id.OpaqueId } - // url := am.endPoint + "~" + a.username + "/api/" + a.verb - // url := "http://localhost/apps/sciencemesh/~" + user.Username + "/api/share/" + a.verb - url := sm.endPoint + "~" + user.Username + "/api/ocm/" + a.verb + return "empty-username" +} + +func (sm *Manager) do(ctx context.Context, a Action, username string) (int, []byte, error) { + url := sm.endPoint + "~" + username + "/api/ocm/" + a.verb + + log := appctx.GetLogger(ctx) log.Info().Msgf("am.do %s %s", url, a.argS) req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) + req.Header.Set("X-Reva-Secret", sm.sharedSecret) if err != nil { return 0, nil, err } @@ -176,50 +207,163 @@ func (sm *Manager) do(ctx context.Context, a Action) (int, []byte, error) { return 0, nil, err } + // curl -i -H 'application/json' -H 'X-Reva-Secret: shared-secret-1' -d '{"md":{"opaque_id":"fileid-/other/q/as"},"g":{"grantee":{"type":1,"Id":{"UserId":{"idp":"revanc2.docker","opaque_id":"marie"}}},"permissions":{"permissions":{"get_path":true,"initiate_file_download":true,"list_container":true,"list_file_versions":true,"stat":true}}},"provider_domain":"cern.ch","resource_type":"file","provider_id":2,"owner_opaque_id":"einstein","owner_display_name":"Albert Einstein","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' https://nc1.docker/index.php/apps/sciencemesh/~/api/ocm/addSentShare + log.Info().Msgf("am.do response %d %s", resp.StatusCode, body) return resp.StatusCode, body, nil } -// Share as defined in the ocm.share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/ocm/share/share.go#L30-L57 +// Called from both grpc CreateOCMShare for outgoing +// and http /ocm/shares for incoming +// pi is provider info +// pm is permissions func (sm *Manager) Share(ctx context.Context, md *provider.ResourceId, g *ocm.ShareGrant, name string, pi *ocmprovider.ProviderInfo, pm string, owner *userpb.UserId, token string, st ocm.Share_ShareType) (*ocm.Share, error) { - type paramsObj struct { - Md *provider.ResourceId `json:"md"` - G *ocm.ShareGrant `json:"g"` + + // Since both OCMCore and OCMShareProvider use the same package, we distinguish + // between calls received from them on the basis of whether they provide info + // about the remote provider on which the share is to be created. + // If this info is provided, this call is on the owner's mesh provider and so + // we call the CreateOCMCoreShare method on the remote provider as well, + // else this is received from another provider and we only create a local share. + var isOwnersMeshProvider bool + var apiMethod string + var username string + if pi != nil { + isOwnersMeshProvider = true + apiMethod = "addSentShare" + username = getUsername(ctx) + token = randSeq(10) + } else { + apiMethod = "addReceivedShare" + username = g.Grantee.GetUserId().OpaqueId } - bodyObj := ¶msObj{ - Md: md, - G: g, + + var userID *userpb.UserId + if !isOwnersMeshProvider { + // Since this call is on the remote provider, the owner of the resource is expected to be specified. + if owner == nil { + return nil, errors.New("nextcloud: owner of resource not provided") + } + userID = owner + } else { + userID = ctxpkg.ContextMustGetUser(ctx).GetId() } - bodyStr, err := json.Marshal(bodyObj) - if err != nil { - return nil, err + + // do not allow share to myself if share is for a user + if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER && utils.UserEqual(g.Grantee.GetUserId(), userID) { + return nil, errors.New("nextcloud: user and grantee are the same") + } + + s := &ocm.Share{ + Name: name, + ResourceId: md, + Permissions: g.Permissions, + Grantee: g.Grantee, + Owner: userID, + Creator: userID, + ShareType: st, + } + + var encShare []byte + var err error + + if isOwnersMeshProvider { + // adding the webdav sharedSecret in the Grantee because Share itself doesn't have an Opaque field, + // see https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.Grantee + // and https://cs3org.github.io/cs3apis/#cs3.sharing.ocm.v1beta1.Share + s.Grantee.Opaque = &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "sharedSecret": { + Decoder: "plain", + Value: []byte(token), + }, + }, + } + + encShare, err = utils.MarshalProtoV1ToJSON(s) + if err != nil { + return nil, err + } + } else { + // adding the webdav sharedSecret and remote share id (called the "ProviderID" in OCM) in the Grantee because Share itself doesn't have an Opaque field, + // see https://cs3org.github.io/cs3apis/#cs3.storage.provider.v1beta1.Grantee + // and https://cs3org.github.io/cs3apis/#cs3.sharing.ocm.v1beta1.Share + s.Grantee.Opaque = &typespb.Opaque{ + Map: map[string]*typespb.OpaqueEntry{ + "sharedSecret": { + Decoder: "plain", + Value: []byte(token), + }, + "remoteShareId": { + Decoder: "plain", + Value: g.Grantee.Opaque.Map["remoteShareId"].Value, + }, + }, + } + + encShare, err = utils.MarshalProtoV1ToJSON(&ocm.ReceivedShare{ + Share: s, + State: ocm.ShareState_SHARE_STATE_PENDING, + }) + if err != nil { + return nil, err + } } - _, body, err := sm.do(ctx, Action{"Share", string(bodyStr)}) + _, body, err := sm.do(ctx, Action{apiMethod, string(encShare)}, username) + s.Id = &ocm.ShareId{ + OpaqueId: string(body), + } + now := time.Now().UnixNano() + s.Ctime = &typespb.Timestamp{ + Seconds: uint64(now / 1000000000), + Nanos: uint32(now % 1000000000), + } if err != nil { return nil, err } - altResult := &ShareAltMap{} - err = json.Unmarshal(body, &altResult) - if altResult == nil { - return nil, err + if isOwnersMeshProvider { + // token, ok := ctxpkg.ContextGetToken(ctx) + // if !ok { + // return nil, errors.New("Could not get token from context") + // } + var protocol map[string]interface{} + if st == ocm.Share_SHARE_TYPE_TRANSFER { + protocol = map[string]interface{}{ + "name": "datatx", + "options": map[string]string{ + "permissions": pm, + "token": token, // FIXME: Where is the token for datatx generated? + }, + } + } else { + protocol = map[string]interface{}{ + "name": "webdav", + "options": map[string]string{ + "permissions": pm, + "sharedSecret": token, + }, + } + } + requestBodyMap := map[string]interface{}{ + "shareWith": g.Grantee.GetUserId().OpaqueId, + "name": name, + "providerId": s.Id.OpaqueId, + "owner": userID.OpaqueId, + "protocol": protocol, + "meshProvider": userID.Idp, // FIXME: move this into the 'owner' string? + } + err = sender.Send(requestBodyMap, pi) + if err != nil { + err = errors.Wrap(err, "error sending OCM POST") + return nil, err + } } - return &ocm.Share{ - Id: altResult.ID, - ResourceId: altResult.ResourceID, - Permissions: altResult.Permissions, - Grantee: &provider.Grantee{ - Id: altResult.Grantee.ID, - }, - Owner: altResult.Owner, - Creator: altResult.Creator, - Ctime: altResult.Ctime, - Mtime: altResult.Mtime, - }, err + + return s, nil } // GetShare as defined in the ocm.share.Manager interface @@ -229,7 +373,7 @@ func (sm *Manager) GetShare(ctx context.Context, ref *ocm.ShareReference) (*ocm. if err != nil { return nil, err } - _, body, err := sm.do(ctx, Action{"GetShare", string(bodyStr)}) + _, body, err := sm.do(ctx, Action{"GetShare", string(bodyStr)}, getUsername(ctx)) if err != nil { return nil, err } @@ -261,7 +405,7 @@ func (sm *Manager) Unshare(ctx context.Context, ref *ocm.ShareReference) error { return err } - _, _, err = sm.do(ctx, Action{"Unshare", string(bodyStr)}) + _, _, err = sm.do(ctx, Action{"Unshare", string(bodyStr)}, getUsername(ctx)) return err } @@ -281,7 +425,7 @@ func (sm *Manager) UpdateShare(ctx context.Context, ref *ocm.ShareReference, p * return nil, err } - _, body, err := sm.do(ctx, Action{"UpdateShare", string(bodyStr)}) + _, body, err := sm.do(ctx, Action{"UpdateShare", string(bodyStr)}, getUsername(ctx)) if err != nil { return nil, err @@ -314,7 +458,7 @@ func (sm *Manager) ListShares(ctx context.Context, filters []*ocm.ListOCMSharesR return nil, err } - _, respBody, err := sm.do(ctx, Action{"ListShares", string(bodyStr)}) + _, respBody, err := sm.do(ctx, Action{"ListShares", string(bodyStr)}, getUsername(ctx)) if err != nil { return nil, err } @@ -347,7 +491,7 @@ func (sm *Manager) ListShares(ctx context.Context, filters []*ocm.ListOCMSharesR // ListReceivedShares as defined in the ocm.share.Manager interface // https://github.com/cs3org/reva/blob/v1.13.0/pkg/ocm/share/share.go#L30-L57 func (sm *Manager) ListReceivedShares(ctx context.Context) ([]*ocm.ReceivedShare, error) { - _, respBody, err := sm.do(ctx, Action{"ListReceivedShares", string("")}) + _, respBody, err := sm.do(ctx, Action{"ListReceivedShares", string("")}, getUsername(ctx)) if err != nil { return nil, err } @@ -395,7 +539,7 @@ func (sm *Manager) GetReceivedShare(ctx context.Context, ref *ocm.ShareReference return nil, err } - _, respBody, err := sm.do(ctx, Action{"GetReceivedShare", string(bodyStr)}) + _, respBody, err := sm.do(ctx, Action{"GetReceivedShare", string(bodyStr)}, getUsername(ctx)) if err != nil { return nil, err } @@ -446,7 +590,7 @@ func (sm Manager) UpdateReceivedShare(ctx context.Context, receivedShare *ocm.Re return nil, err } - _, respBody, err := sm.do(ctx, Action{"UpdateReceivedShare", string(bodyStr)}) + _, respBody, err := sm.do(ctx, Action{"UpdateReceivedShare", string(bodyStr)}, getUsername(ctx)) if err != nil { return nil, err } diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud_server_mock.go b/pkg/ocm/share/manager/nextcloud/nextcloud_server_mock.go index 738bef43f6..10c8dbaeba 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud_server_mock.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud_server_mock.go @@ -43,14 +43,14 @@ const serverStateHome = "HOME" var serverState = serverStateEmpty var responses = map[string]Response{ - `POST /apps/sciencemesh/~tester/api/ocm/Share {"md":{"opaque_id":"fileid-/some/path"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{"get_path":true}}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, ``, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`: {200, `[{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}]`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/ListReceivedShares `: {200, `[{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}]`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/ocm/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/addReceivedShare {"md":{"opaque_id":"fileid-/some/path"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{"get_path":true}}},"provider_domain":"cern.ch","resource_type":"file","provider_id":2,"owner_opaque_id":"einstein","owner_display_name":"Albert Einstein","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, ``, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`: {200, `[{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}]`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/ListReceivedShares `: {200, `[{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}]`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, + `POST /index.php/apps/sciencemesh/~marie/api/ocm/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, } // GetNextcloudServerMock returns a handler that pretends to be a remote Nextcloud server @@ -62,22 +62,13 @@ func GetNextcloudServerMock(called *[]string) http.Handler { panic("Error reading response into buffer") } var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String()) - fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String())) - fmt.Printf("Nextcloud Server Mock key %s\n", key) *called = append(*called, key) response := responses[key] if (response == Response{}) { key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState) - fmt.Printf("Nextcloud Server Mock key with State %s\n", key) - // *called = append(*called, key) response = responses[key] } if (response == Response{}) { - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - fmt.Printf("Nextcloud Server Mock key not found! %s\n", key) - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty} } serverState = responses[key].newServerState diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go index c3469d6305..f44eb35cd4 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go @@ -20,7 +20,6 @@ package nextcloud_test import ( "context" - "fmt" "os" "google.golang.org/genproto/protobuf/field_mask" @@ -47,7 +46,6 @@ func setUpNextcloudServer() (*nextcloud.Manager, *[]string, func()) { var conf *nextcloud.ShareManagerConfig ncHost := os.Getenv("NEXTCLOUD") - fmt.Printf(`NEXTCLOUD env var: "%s"`, ncHost) if len(ncHost) == 0 { conf = &nextcloud.ShareManagerConfig{ EndPoint: "http://mock.com/apps/sciencemesh/", @@ -132,7 +130,6 @@ var _ = Describe("Nextcloud", func() { // Share(ctx context.Context, md *provider.ResourceInfo, g *ocm.ShareGrant) (*ocm.Share, error) Describe("Share", func() { It("calls the Share endpoint", func() { - fmt.Println("Calling setUpNextCloudServer!") am, called, teardown := setUpNextcloudServer() defer teardown() var md = &provider.ResourceId{ @@ -246,7 +243,7 @@ var _ = Describe("Nextcloud", func() { XXX_sizecache: 0, }, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/Share {"md":{"opaque_id":"fileid-/some/path"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{"get_path":true}}}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/addReceivedShare {"md":{"opaque_id":"fileid-/some/path"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{"get_path":true}}},"provider_domain":"cern.ch","resource_type":"file","provider_id":2,"owner_opaque_id":"einstein","owner_display_name":"Albert Einstein","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}`) }) }) @@ -324,7 +321,7 @@ var _ = Describe("Nextcloud", func() { XXX_sizecache: 0, }, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) }) }) @@ -342,7 +339,7 @@ var _ = Describe("Nextcloud", func() { }, }) Expect(err).ToNot(HaveOccurred()) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) }) }) @@ -443,7 +440,7 @@ var _ = Describe("Nextcloud", func() { XXX_sizecache: 0, }, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`) }) }) @@ -527,7 +524,7 @@ var _ = Describe("Nextcloud", func() { XXX_sizecache: 0, }, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`) }) }) @@ -603,7 +600,7 @@ var _ = Describe("Nextcloud", func() { }, State: ocm.ShareState_SHARE_STATE_ACCEPTED, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/ListReceivedShares `) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/ListReceivedShares `) }) }) @@ -684,7 +681,7 @@ var _ = Describe("Nextcloud", func() { }, State: ocm.ShareState_SHARE_STATE_ACCEPTED, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) }) }) @@ -825,7 +822,7 @@ var _ = Describe("Nextcloud", func() { }, State: ocm.ShareState_SHARE_STATE_ACCEPTED, })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/ocm/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`) + checkCalled(called, `POST /index.php/apps/sciencemesh/~marie/api/ocm/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`) }) }) diff --git a/pkg/ocm/share/sender/sender.go b/pkg/ocm/share/sender/sender.go new file mode 100644 index 0000000000..2644305123 --- /dev/null +++ b/pkg/ocm/share/sender/sender.go @@ -0,0 +1,91 @@ +// Copyright 2018-2021 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 ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "strings" + "time" + + ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + "github.com/cs3org/reva/pkg/rhttp" + + "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") +} + +func Send(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() + + req, err := http.NewRequest("POST", recipientURL, strings.NewReader(string(requestBody))) + if err != nil { + return errors.Wrap(err, "sender: error framing post request") + } + req.Header.Set("Content-Type", "application/json; param=value") + client := rhttp.GetHTTPClient( + rhttp.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 := ioutil.ReadAll(resp.Body) + if e != nil { + e = errors.Wrap(e, "sender: error reading request body") + return e + } + err = errors.Wrap(errors.New(fmt.Sprintf("%s: %s", resp.Status, string(respBody))), "sender: error sending create ocm core share post request") + return err + } + return nil +} diff --git a/pkg/rhttp/rhttp.go b/pkg/rhttp/rhttp.go index e3a4c12680..4ed8297c60 100644 --- a/pkg/rhttp/rhttp.go +++ b/pkg/rhttp/rhttp.go @@ -78,6 +78,8 @@ type config struct { Address string `mapstructure:"address"` Services map[string]map[string]interface{} `mapstructure:"services"` Middlewares map[string]map[string]interface{} `mapstructure:"middlewares"` + CertFile string `mapstructure:"certfile"` + KeyFile string `mapstructure:"keyfile"` } func (c *config) init() { @@ -109,8 +111,13 @@ func (s *Server) Start(ln net.Listener) error { s.httpServer.Handler = handler s.listener = ln - s.log.Info().Msgf("http server listening at %s://%s", "http", s.conf.Address) - err = s.httpServer.Serve(s.listener) + if (s.conf.CertFile != "") && (s.conf.KeyFile != "") { + s.log.Info().Msgf("https server listening at https://%s '%s' '%s'", s.conf.Address, s.conf.CertFile, s.conf.KeyFile) + err = s.httpServer.ServeTLS(s.listener, s.conf.CertFile, s.conf.KeyFile) + } else { + s.log.Info().Msgf("http server listening at http://%s '%s' '%s'", s.conf.Address, s.conf.CertFile, s.conf.KeyFile) + err = s.httpServer.Serve(s.listener) + } if err == nil || err == http.ErrServerClosed { return nil } diff --git a/pkg/share/manager/loader/loader.go b/pkg/share/manager/loader/loader.go index eada9e68e6..b3ebbcae29 100644 --- a/pkg/share/manager/loader/loader.go +++ b/pkg/share/manager/loader/loader.go @@ -22,7 +22,6 @@ import ( // Load core share manager drivers. _ "github.com/cs3org/reva/pkg/share/manager/json" _ "github.com/cs3org/reva/pkg/share/manager/memory" - _ "github.com/cs3org/reva/pkg/share/manager/nextcloud" _ "github.com/cs3org/reva/pkg/share/manager/sql" // Add your own here ) diff --git a/pkg/share/manager/nextcloud/nextcloud.go b/pkg/share/manager/nextcloud/nextcloud.go deleted file mode 100644 index 69d5e41cf3..0000000000 --- a/pkg/share/manager/nextcloud/nextcloud.go +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright 2018-2021 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 nextcloud verifies a clientID and clientSecret against a Nextcloud backend. -package nextcloud - -import ( - "context" - "encoding/json" - "io" - "net/http" - "strings" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/appctx" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/share" - "github.com/cs3org/reva/pkg/share/manager/registry" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "google.golang.org/genproto/protobuf/field_mask" -) - -func init() { - registry.Register("nextcloud", New) -} - -// Manager is the Nextcloud-based implementation of the share.Manager interface -// see https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -type Manager struct { - client *http.Client - endPoint string -} - -// ShareManagerConfig contains config for a Nextcloud-based ShareManager -type ShareManagerConfig struct { - EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"` - MockHTTP bool `mapstructure:"mock_http"` -} - -// Action describes a REST request to forward to the Nextcloud backend -type Action struct { - verb string - argS string -} - -// GranteeAltMap is an alternative map to JSON-unmarshal a Grantee -// Grantees are hard to unmarshal, so unmarshalling into a map[string]interface{} first, -// see also https://github.com/pondersource/sciencemesh-nextcloud/issues/27 -type GranteeAltMap struct { - ID *provider.Grantee_UserId `json:"id"` -} - -// ShareAltMap is an alternative map to JSON-unmarshal a Share -type ShareAltMap struct { - ID *collaboration.ShareId `json:"id"` - ResourceID *provider.ResourceId `json:"resource_id"` - Permissions *collaboration.SharePermissions `json:"permissions"` - Grantee *GranteeAltMap `json:"grantee"` - Owner *userpb.UserId `json:"owner"` - Creator *userpb.UserId `json:"creator"` - Ctime *types.Timestamp `json:"ctime"` - Mtime *types.Timestamp `json:"mtime"` -} - -// ReceivedShareAltMap is an alternative map to JSON-unmarshal a ReceivedShare -type ReceivedShareAltMap struct { - Share *ShareAltMap `json:"share"` - State collaboration.ShareState `json:"state"` -} - -func (c *ShareManagerConfig) init() { -} - -func parseConfig(m map[string]interface{}) (*ShareManagerConfig, error) { - c := &ShareManagerConfig{} - if err := mapstructure.Decode(m, c); err != nil { - err = errors.Wrap(err, "error decoding conf") - return nil, err - } - return c, nil -} - -func getUser(ctx context.Context) (*userpb.User, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired(""), "nextcloud storage driver: error getting user from ctx") - return nil, err - } - return u, nil -} - -// New returns a share manager implementation that verifies against a Nextcloud backend. -func New(m map[string]interface{}) (share.Manager, error) { - c, err := parseConfig(m) - if err != nil { - return nil, err - } - c.init() - - return NewShareManager(c) -} - -// NewShareManager returns a new Nextcloud-based ShareManager -func NewShareManager(c *ShareManagerConfig) (*Manager, error) { - var client *http.Client - if c.MockHTTP { - // called := make([]string, 0) - // nextcloudServerMock := GetNextcloudServerMock(&called) - // client, _ = TestingHTTPClient(nextcloudServerMock) - - // Wait for SetHTTPClient to be called later - client = nil - } else { - client = &http.Client{} - } - - return &Manager{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" - client: client, - }, nil -} - -// SetHTTPClient sets the HTTP client -func (sm *Manager) SetHTTPClient(c *http.Client) { - sm.client = c -} - -func (sm *Manager) do(ctx context.Context, a Action) (int, []byte, error) { - log := appctx.GetLogger(ctx) - user, err := getUser(ctx) - if err != nil { - return 0, nil, err - } - // url := am.endPoint + "~" + a.username + "/api/" + a.verb - // url := "http://localhost/apps/sciencemesh/~" + user.Username + "/api/share/" + a.verb - url := sm.endPoint + "~" + user.Username + "/api/share/" + a.verb - - log.Info().Msgf("am.do %s %s", url, a.argS) - req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) - if err != nil { - return 0, nil, err - } - - req.Header.Set("Content-Type", "application/json") - resp, err := sm.client.Do(req) - if err != nil { - return 0, nil, err - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return 0, nil, err - } - - log.Info().Msgf("am.do response %d %s", resp.StatusCode, body) - return resp.StatusCode, body, nil -} - -// Share as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) Share(ctx context.Context, md *provider.ResourceInfo, g *collaboration.ShareGrant) (*collaboration.Share, error) { - type paramsObj struct { - Md *provider.ResourceInfo `json:"md"` - G *collaboration.ShareGrant `json:"g"` - } - bodyObj := ¶msObj{ - Md: md, - G: g, - } - bodyStr, err := json.Marshal(bodyObj) - if err != nil { - return nil, err - } - - _, body, err := sm.do(ctx, Action{"Share", string(bodyStr)}) - - if err != nil { - return nil, err - } - - altResult := &ShareAltMap{} - err = json.Unmarshal(body, &altResult) - if altResult == nil { - return nil, err - } - return &collaboration.Share{ - Id: altResult.ID, - ResourceId: altResult.ResourceID, - Permissions: altResult.Permissions, - Grantee: &provider.Grantee{ - Id: altResult.Grantee.ID, - }, - Owner: altResult.Owner, - Creator: altResult.Creator, - Ctime: altResult.Ctime, - Mtime: altResult.Mtime, - }, err -} - -// GetShare as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) { - bodyStr, err := json.Marshal(ref) - if err != nil { - return nil, err - } - _, body, err := sm.do(ctx, Action{"GetShare", string(bodyStr)}) - if err != nil { - return nil, err - } - - altResult := &ShareAltMap{} - err = json.Unmarshal(body, &altResult) - if altResult == nil { - return nil, err - } - return &collaboration.Share{ - Id: altResult.ID, - ResourceId: altResult.ResourceID, - Permissions: altResult.Permissions, - Grantee: &provider.Grantee{ - Id: altResult.Grantee.ID, - }, - Owner: altResult.Owner, - Creator: altResult.Creator, - Ctime: altResult.Ctime, - Mtime: altResult.Mtime, - }, err -} - -// Unshare as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) Unshare(ctx context.Context, ref *collaboration.ShareReference) error { - bodyStr, err := json.Marshal(ref) - if err != nil { - return err - } - - _, _, err = sm.do(ctx, Action{"Unshare", string(bodyStr)}) - return err -} - -// UpdateShare as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) { - type paramsObj struct { - Ref *collaboration.ShareReference `json:"ref"` - P *collaboration.SharePermissions `json:"p"` - } - bodyObj := ¶msObj{ - Ref: ref, - P: p, - } - bodyStr, err := json.Marshal(bodyObj) - if err != nil { - return nil, err - } - - _, body, err := sm.do(ctx, Action{"UpdateShare", string(bodyStr)}) - - if err != nil { - return nil, err - } - - altResult := &ShareAltMap{} - err = json.Unmarshal(body, &altResult) - if altResult == nil { - return nil, err - } - return &collaboration.Share{ - Id: altResult.ID, - ResourceId: altResult.ResourceID, - Permissions: altResult.Permissions, - Grantee: &provider.Grantee{ - Id: altResult.Grantee.ID, - }, - Owner: altResult.Owner, - Creator: altResult.Creator, - Ctime: altResult.Ctime, - Mtime: altResult.Mtime, - }, err -} - -// ListShares as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { - bodyStr, err := json.Marshal(filters) - if err != nil { - return nil, err - } - - _, respBody, err := sm.do(ctx, Action{"ListShares", string(bodyStr)}) - if err != nil { - return nil, err - } - - var respArr []ShareAltMap - err = json.Unmarshal(respBody, &respArr) - if err != nil { - return nil, err - } - - var pointers = make([]*collaboration.Share, len(respArr)) - for i := 0; i < len(respArr); i++ { - altResult := respArr[i] - pointers[i] = &collaboration.Share{ - Id: altResult.ID, - ResourceId: altResult.ResourceID, - Permissions: altResult.Permissions, - Grantee: &provider.Grantee{ - Id: altResult.Grantee.ID, - }, - Owner: altResult.Owner, - Creator: altResult.Creator, - Ctime: altResult.Ctime, - Mtime: altResult.Mtime, - } - } - return pointers, err -} - -// ListReceivedShares as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) { - bodyStr, err := json.Marshal(filters) - if err != nil { - return nil, err - } - - _, respBody, err := sm.do(ctx, Action{"ListReceivedShares", string(bodyStr)}) - if err != nil { - return nil, err - } - - var respArr []ReceivedShareAltMap - err = json.Unmarshal(respBody, &respArr) - if err != nil { - return nil, err - } - var pointers = make([]*collaboration.ReceivedShare, len(respArr)) - for i := 0; i < len(respArr); i++ { - altResultShare := respArr[i].Share - if altResultShare == nil { - pointers[i] = &collaboration.ReceivedShare{ - Share: nil, - State: respArr[i].State, - } - } else { - pointers[i] = &collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: altResultShare.ID, - ResourceId: altResultShare.ResourceID, - Permissions: altResultShare.Permissions, - Grantee: &provider.Grantee{ - Id: altResultShare.Grantee.ID, - }, - Owner: altResultShare.Owner, - Creator: altResultShare.Creator, - Ctime: altResultShare.Ctime, - Mtime: altResultShare.Mtime, - }, - State: respArr[i].State, - } - } - } - return pointers, err - -} - -// GetReceivedShare as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm *Manager) GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) { - bodyStr, err := json.Marshal(ref) - if err != nil { - return nil, err - } - - _, respBody, err := sm.do(ctx, Action{"GetReceivedShare", string(bodyStr)}) - if err != nil { - return nil, err - } - - var altResult ReceivedShareAltMap - err = json.Unmarshal(respBody, &altResult) - if err != nil { - return nil, err - } - altResultShare := altResult.Share - if altResultShare == nil { - return &collaboration.ReceivedShare{ - Share: nil, - State: altResult.State, - }, err - } - return &collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: altResultShare.ID, - ResourceId: altResultShare.ResourceID, - Permissions: altResultShare.Permissions, - Grantee: &provider.Grantee{ - Id: altResultShare.Grantee.ID, - }, - Owner: altResultShare.Owner, - Creator: altResultShare.Creator, - Ctime: altResultShare.Ctime, - Mtime: altResultShare.Mtime, - }, - State: altResult.State, - }, err -} - -// UpdateReceivedShare as defined in the share.Manager interface -// https://github.com/cs3org/reva/blob/v1.13.0/pkg/share/share.go#L29-L54 -func (sm Manager) UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) { - type paramsObj struct { - ReceivedShare *collaboration.ReceivedShare `json:"received_share"` - FieldMask *field_mask.FieldMask `json:"field_mask"` - } - - bodyObj := ¶msObj{ - ReceivedShare: receivedShare, - FieldMask: fieldMask, - } - bodyStr, err := json.Marshal(bodyObj) - if err != nil { - return nil, err - } - - _, respBody, err := sm.do(ctx, Action{"UpdateReceivedShare", string(bodyStr)}) - if err != nil { - return nil, err - } - - var altResult ReceivedShareAltMap - err = json.Unmarshal(respBody, &altResult) - if err != nil { - return nil, err - } - altResultShare := altResult.Share - if altResultShare == nil { - return &collaboration.ReceivedShare{ - Share: nil, - State: altResult.State, - }, err - } - return &collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: altResultShare.ID, - ResourceId: altResultShare.ResourceID, - Permissions: altResultShare.Permissions, - Grantee: &provider.Grantee{ - Id: altResultShare.Grantee.ID, - }, - Owner: altResultShare.Owner, - Creator: altResultShare.Creator, - Ctime: altResultShare.Ctime, - Mtime: altResultShare.Mtime, - }, - State: altResult.State, - }, err -} diff --git a/pkg/share/manager/nextcloud/nextcloud_server_mock.go b/pkg/share/manager/nextcloud/nextcloud_server_mock.go deleted file mode 100644 index 023da07293..0000000000 --- a/pkg/share/manager/nextcloud/nextcloud_server_mock.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018-2021 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 nextcloud - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "strings" -) - -// Response contains data for the Nextcloud mock server to respond -// and to switch to a new server state -type Response struct { - code int - body string - newServerState string -} - -const serverStateError = "ERROR" -const serverStateEmpty = "EMPTY" -const serverStateHome = "HOME" - -var serverState = serverStateEmpty - -var responses = map[string]Response{ - `POST /apps/sciencemesh/~tester/api/share/Share {"md":{"opaque":{},"type":1,"id":{"opaque_id":"fileid-/some/path"},"checksum":{},"etag":"deadbeef","mime_type":"text/plain","mtime":{"seconds":1234567890},"path":"/some/path","permission_set":{},"size":12345,"canonical_metadata":{},"arbitrary_metadata":{"metadata":{"da":"ta","some":"arbi","trary":"meta"}}},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{}}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, ``, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`: {200, `{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`: {200, `[{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}}]`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/ListReceivedShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`: {200, `[{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}]`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, - `POST /apps/sciencemesh/~tester/api/share/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`: {200, `{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2}`, serverStateHome}, -} - -// GetNextcloudServerMock returns a handler that pretends to be a remote Nextcloud server -func GetNextcloudServerMock(called *[]string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - buf := new(strings.Builder) - _, err := io.Copy(buf, r.Body) - if err != nil { - panic("Error reading response into buffer") - } - var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String()) - fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String())) - fmt.Printf("Nextcloud Server Mock key %s\n", key) - *called = append(*called, key) - response := responses[key] - if (response == Response{}) { - key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState) - fmt.Printf("Nextcloud Server Mock key with State %s\n", key) - // *called = append(*called, key) - response = responses[key] - } - if (response == Response{}) { - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - fmt.Printf("Nextcloud Server Mock key not found! %s\n", key) - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty} - } - serverState = responses[key].newServerState - if serverState == `` { - serverState = serverStateError - } - w.WriteHeader(response.code) - // w.Header().Set("Etag", "mocker-etag") - _, err = w.Write([]byte(responses[key].body)) - if err != nil { - panic(err) - } - }) -} - -// TestingHTTPClient thanks to https://itnext.io/how-to-stub-requests-to-remote-hosts-with-go-6c2c1db32bf2 -// Ideally, this function would live in tests/helpers, but -// if we put it there, it gets excluded by .dockerignore, and the -// Docker build fails (see https://github.com/cs3org/reva/issues/1999) -// So putting it here for now - open to suggestions if someone knows -// a better way to inject this. -func TestingHTTPClient(handler http.Handler) (*http.Client, func()) { - s := httptest.NewServer(handler) - - cli := &http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, network, _ string) (net.Conn, error) { - return net.Dial(network, s.Listener.Addr().String()) - }, - }, - } - - return cli, s.Close -} diff --git a/pkg/share/manager/nextcloud/nextcloud_suite_test.go b/pkg/share/manager/nextcloud/nextcloud_suite_test.go deleted file mode 100644 index 7d75b64879..0000000000 --- a/pkg/share/manager/nextcloud/nextcloud_suite_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018-2021 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 nextcloud_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestNextcloud(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Nextcloud Suite") -} diff --git a/pkg/share/manager/nextcloud/nextcloud_test.go b/pkg/share/manager/nextcloud/nextcloud_test.go deleted file mode 100644 index 2284560035..0000000000 --- a/pkg/share/manager/nextcloud/nextcloud_test.go +++ /dev/null @@ -1,881 +0,0 @@ -// Copyright 2018-2021 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 nextcloud_test - -import ( - "context" - "fmt" - "os" - - "google.golang.org/genproto/protobuf/field_mask" - "google.golang.org/grpc/metadata" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - - "github.com/cs3org/reva/pkg/auth/scope" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - - "github.com/cs3org/reva/pkg/share/manager/nextcloud" - jwt "github.com/cs3org/reva/pkg/token/manager/jwt" - "github.com/cs3org/reva/tests/helpers" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func setUpNextcloudServer() (*nextcloud.Manager, *[]string, func()) { - var conf *nextcloud.ShareManagerConfig - - ncHost := os.Getenv("NEXTCLOUD") - fmt.Printf(`NEXTCLOUD env var: "%s"`, ncHost) - if len(ncHost) == 0 { - conf = &nextcloud.ShareManagerConfig{ - EndPoint: "http://mock.com/apps/sciencemesh/", - MockHTTP: true, - } - nc, _ := nextcloud.NewShareManager(conf) - called := make([]string, 0) - h := nextcloud.GetNextcloudServerMock(&called) - mock, teardown := nextcloud.TestingHTTPClient(h) - nc.SetHTTPClient(mock) - return nc, &called, teardown - } - conf = &nextcloud.ShareManagerConfig{ - EndPoint: ncHost + "/apps/sciencemesh/", - MockHTTP: false, - } - nc, _ := nextcloud.NewShareManager(conf) - return nc, nil, func() {} -} - -func checkCalled(called *[]string, expected string) { - if called == nil { - return - } - Expect(len(*called)).To(Equal(1)) - Expect((*called)[0]).To(Equal(expected)) -} - -var _ = Describe("Nextcloud", func() { - var ( - ctx context.Context - options map[string]interface{} - tmpRoot string - user = &userpb.User{ - Id: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Username: "tester", - } - ) - - BeforeEach(func() { - var err error - tmpRoot, err := helpers.TempDir("reva-unit-tests-*-root") - Expect(err).ToNot(HaveOccurred()) - - options = map[string]interface{}{ - "root": tmpRoot, - "enable_home": true, - "share_folder": "/Shares", - } - - ctx = context.Background() - - // Add auth token - tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"}) - Expect(err).ToNot(HaveOccurred()) - scope, err := scope.AddOwnerScope(nil) - Expect(err).ToNot(HaveOccurred()) - t, err := tokenManager.MintToken(ctx, user, scope) - Expect(err).ToNot(HaveOccurred()) - ctx = ctxpkg.ContextSetToken(ctx, t) - ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, t) - ctx = ctxpkg.ContextSetUser(ctx, user) - }) - - AfterEach(func() { - if tmpRoot != "" { - os.RemoveAll(tmpRoot) - } - }) - - Describe("New", func() { - It("returns a new instance", func() { - _, err := nextcloud.New(options) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - // Share(ctx context.Context, md *provider.ResourceInfo, g *collaboration.ShareGrant) (*collaboration.Share, error) - Describe("Share", func() { - It("calls the Share endpoint", func() { - fmt.Println("Calling setUpNextCloudServer!") - am, called, teardown := setUpNextcloudServer() - defer teardown() - - share, err := am.Share(ctx, &provider.ResourceInfo{ - Opaque: &types.Opaque{ - Map: nil, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Type: provider.ResourceType_RESOURCE_TYPE_FILE, - Id: &provider.ResourceId{ - StorageId: "", - OpaqueId: "fileid-/some/path", - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Checksum: &provider.ResourceChecksum{ - Type: 0, - Sum: "", - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Etag: "deadbeef", - MimeType: "text/plain", - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Path: "/some/path", - PermissionSet: &provider.ResourcePermissions{ - AddGrant: false, - CreateContainer: false, - Delete: false, - GetPath: false, - GetQuota: false, - InitiateFileDownload: false, - InitiateFileUpload: false, - ListGrants: false, - ListContainer: false, - ListFileVersions: false, - ListRecycle: false, - Move: false, - RemoveGrant: false, - PurgeRecycle: false, - RestoreFileVersion: false, - RestoreRecycleItem: false, - Stat: false, - UpdateGrant: false, - DenyGrant: false, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Size: 12345, - Owner: nil, - Target: "", - CanonicalMetadata: &provider.CanonicalMetadata{ - Target: nil, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - ArbitraryMetadata: &provider.ArbitraryMetadata{ - Metadata: map[string]string{"some": "arbi", "trary": "meta", "da": "ta"}, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, &collaboration.ShareGrant{ - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{}, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(*share).To(Equal(collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/Share {"md":{"opaque":{},"type":1,"id":{"opaque_id":"fileid-/some/path"},"checksum":{},"etag":"deadbeef","mime_type":"text/plain","mtime":{"seconds":1234567890},"path":"/some/path","permission_set":{},"size":12345,"canonical_metadata":{},"arbitrary_metadata":{"metadata":{"da":"ta","some":"arbi","trary":"meta"}}},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"permissions":{}}}}`) - }) - }) - - // GetShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.Share, error) - Describe("GetShare", func() { - It("calls the GetShare endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - share, err := am.GetShare(ctx, &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: "some-share-id", - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(*share).To(Equal(collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/GetShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) - }) - }) - - // Unshare(ctx context.Context, ref *collaboration.ShareReference) error - Describe("Unshare", func() { - It("calls the Unshare endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - err := am.Unshare(ctx, &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: "some-share-id", - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/Unshare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) - }) - }) - - // UpdateShare(ctx context.Context, ref *collaboration.ShareReference, p *collaboration.SharePermissions) (*collaboration.Share, error) - Describe("UpdateShare", func() { - It("calls the UpdateShare endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - share, err := am.UpdateShare(ctx, &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: "some-share-id", - }, - }, - }, - &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(*share).To(Equal(collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/UpdateShare {"ref":{"Spec":{"Id":{"opaque_id":"some-share-id"}}},"p":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`) - }) - }) - - // ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) - Describe("ListShares", func() { - It("calls the ListShares endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - shares, err := am.ListShares(ctx, []*collaboration.Filter{ - { - Type: collaboration.Filter_TYPE_CREATOR, - Term: &collaboration.Filter_Creator{ - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(len(shares)).To(Equal(1)) - Expect(*shares[0]).To(Equal(collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/ListShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`) - }) - }) - - // ListReceivedShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.ReceivedShare, error) - Describe("ListReceivedShares", func() { - It("calls the ListReceivedShares endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - receivedShares, err := am.ListReceivedShares(ctx, []*collaboration.Filter{ - { - Type: collaboration.Filter_TYPE_CREATOR, - Term: &collaboration.Filter_Creator{ - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(len(receivedShares)).To(Equal(1)) - Expect(*receivedShares[0]).To(Equal(collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - }, - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/ListReceivedShares [{"type":4,"Term":{"Creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}]`) - }) - }) - - // GetReceivedShare(ctx context.Context, ref *collaboration.ShareReference) (*collaboration.ReceivedShare, error) - Describe("GetReceivedShare", func() { - It("calls the GetReceivedShare endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - receivedShare, err := am.GetReceivedShare(ctx, &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: "some-share-id", - }, - }, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(*receivedShare).To(Equal(collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - }, - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/GetReceivedShare {"Spec":{"Id":{"opaque_id":"some-share-id"}}}`) - }) - }) - - // UpdateReceivedShare(ctx context.Context, receivedShare *collaboration.ReceivedShare, fieldMask *field_mask.FieldMask) (*collaboration.ReceivedShare, error) - Describe("UpdateReceivedShare", func() { - It("calls the UpdateReceivedShare endpoint", func() { - am, called, teardown := setUpNextcloudServer() - defer teardown() - - receivedShare, err := am.UpdateReceivedShare(ctx, - &collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - }, - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - }, - &field_mask.FieldMask{ - Paths: []string{"state"}, - }) - Expect(err).ToNot(HaveOccurred()) - Expect(*receivedShare).To(Equal(collaboration.ReceivedShare{ - Share: &collaboration.Share{ - Id: &collaboration.ShareId{}, - ResourceId: &provider.ResourceId{}, - Permissions: &collaboration.SharePermissions{ - Permissions: &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListGrants: true, - ListContainer: true, - ListFileVersions: true, - ListRecycle: true, - Move: true, - RemoveGrant: true, - PurgeRecycle: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - DenyGrant: true, - }, - }, - Grantee: &provider.Grantee{ - Id: &provider.Grantee_UserId{ - UserId: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - }, - }, - Owner: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Creator: &userpb.UserId{ - Idp: "0.0.0.0:19000", - OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Ctime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - Mtime: &types.Timestamp{ - Seconds: 1234567890, - Nanos: 0, - XXX_NoUnkeyedLiteral: struct{}{}, - XXX_unrecognized: nil, - XXX_sizecache: 0, - }, - }, - State: collaboration.ShareState_SHARE_STATE_ACCEPTED, - })) - checkCalled(called, `POST /apps/sciencemesh/~tester/api/share/UpdateReceivedShare {"received_share":{"share":{"id":{},"resource_id":{},"permissions":{"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}},"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"creator":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"ctime":{"seconds":1234567890},"mtime":{"seconds":1234567890}},"state":2},"field_mask":{"paths":["state"]}}`) - }) - }) - -}) diff --git a/pkg/siteacc/data/sites.go b/pkg/siteacc/data/sites.go index ab64130d84..adc4654584 100644 --- a/pkg/siteacc/data/sites.go +++ b/pkg/siteacc/data/sites.go @@ -20,7 +20,6 @@ package data import ( "encoding/json" - "fmt" "sort" "github.com/cs3org/reva/pkg/mentix/utils/network" @@ -52,7 +51,6 @@ func QueryAvailableSites(mentixHost, dataEndpoint string) ([]SiteInformation, er } sites := siteData{} if err := json.Unmarshal(data, &sites); err != nil { - fmt.Println(err) return nil, errors.Wrap(err, "error while decoding the JSON data") } diff --git a/pkg/siteacc/manager/gocdb/account.go b/pkg/siteacc/manager/gocdb/account.go index e834b44f28..8b164a4876 100644 --- a/pkg/siteacc/manager/gocdb/account.go +++ b/pkg/siteacc/manager/gocdb/account.go @@ -21,7 +21,6 @@ package gocdb import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" @@ -66,8 +65,6 @@ func writeAccount(account *data.Account, operation string, address string, apiKe return errors.Wrap(err, "unable to marshal the user data") } - fmt.Println(string(jsonData)) - req, err := http.NewRequest(http.MethodPost, endpointURL.String(), bytes.NewReader(jsonData)) if err != nil { return errors.Wrap(err, "unable to create HTTP request") diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index 116bfa71dc..0017846f49 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -44,15 +44,17 @@ func init() { // StorageDriverConfig is the configuration struct for a NextcloudStorageDriver type StorageDriverConfig struct { - EndPoint string `mapstructure:"end_point"` // e.g. "http://nc/apps/sciencemesh/~alice/" - MockHTTP bool `mapstructure:"mock_http"` + EndPoint string `mapstructure:"endpoint"` // e.g. "http://nc/apps/sciencemesh/~alice/" + SharedSecret string `mapstructure:"shared_secret"` + MockHTTP bool `mapstructure:"mock_http"` } // StorageDriver implements the storage.FS interface // and connects with a StorageDriver server as its backend type StorageDriver struct { - endPoint string - client *http.Client + endPoint string + sharedSecret string + client *http.Client } func parseConfig(m map[string]interface{}) (*StorageDriverConfig, error) { @@ -90,11 +92,15 @@ func NewStorageDriver(c *StorageDriverConfig) (*StorageDriver, error) { client, _ = TestingHTTPClient(h) // FIXME: defer teardown() } else { + if len(c.EndPoint) == 0 { + return nil, errors.New("Please specify 'endpoint' in '[grpc.services.storageprovider.drivers.nextcloud]'") + } client = &http.Client{} } return &StorageDriver{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" - client: client, + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + sharedSecret: c.SharedSecret, + client: client, }, nil } @@ -132,6 +138,7 @@ func (nc *StorageDriver) doUpload(ctx context.Context, filePath string, r io.Rea panic(err) } + req.Header.Set("X-Reva-Secret", nc.sharedSecret) // set the request header Content-Type for the upload // FIXME: get the actual content type from somewhere req.Header.Set("Content-Type", "text/plain") @@ -177,6 +184,7 @@ func (nc *StorageDriver) doDownloadRevision(ctx context.Context, filePath string // See https://github.com/pondersource/nc-sciencemesh/issues/5 url := nc.endPoint + "~" + user.Username + "/api/storage/DownloadRevision/" + url.QueryEscape(key) + "/" + filePath req, err := http.NewRequest(http.MethodGet, url, strings.NewReader("")) + req.Header.Set("X-Reva-Secret", nc.sharedSecret) if err != nil { panic(err) } @@ -198,12 +206,15 @@ func (nc *StorageDriver) do(ctx context.Context, a Action) (int, []byte, error) if err != nil { return 0, nil, err } - url := nc.endPoint + "~" + user.Username + "/api/storage/" + a.verb + // See https://github.com/cs3org/reva/issues/2377 + // for discussion of user.Username vs user.Id.OpaqueId + url := nc.endPoint + "~" + user.Id.OpaqueId + "/api/storage/" + a.verb log.Info().Msgf("nc.do req %s %s", url, a.argS) req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) if err != nil { return 0, nil, err } + req.Header.Set("X-Reva-Secret", nc.sharedSecret) req.Header.Set("Content-Type", "application/json") resp, err := nc.client.Do(req) @@ -335,10 +346,7 @@ func (nc *StorageDriver) ListFolder(ctx context.Context, ref *provider.Reference if err != nil { return nil, err } - fmt.Println("calling!") - fmt.Println(string(bodyStr)) status, body, err := nc.do(ctx, Action{"ListFolder", string(bodyStr)}) - fmt.Println(string(body)) if err != nil { return nil, err } @@ -806,7 +814,6 @@ func (nc *StorageDriver) UpdateStorageSpace(ctx context.Context, req *provider.U return nil, err } var respObj provider.UpdateStorageSpaceResponse - fmt.Println(string(respBody)) err = json.Unmarshal(respBody, &respObj) if err != nil { return nil, err diff --git a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go index f36fc698da..6f92bc6718 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go +++ b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go @@ -180,22 +180,13 @@ func GetNextcloudServerMock(called *[]string) http.Handler { panic("Error reading response into buffer") } var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String()) - // fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String())) - // fmt.Printf("Nextcloud Server Mock key %s\n", key) *called = append(*called, key) response := responses[key] if (response == Response{}) { key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState) - // fmt.Printf("Nextcloud Server Mock key with State %s\n", key) - // *called = append(*called, key) response = responses[key] } if (response == Response{}) { - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - fmt.Printf("Nextcloud Server Mock key not found! %s\n", key) - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty} } serverState = responses[key].newServerState diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index 09cb0ae60c..4c10a12999 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -20,7 +20,6 @@ package nextcloud_test import ( "context" - "fmt" // "fmt" "io" @@ -47,7 +46,6 @@ func setUpNextcloudServer() (*nextcloud.StorageDriver, *[]string, func()) { var conf *nextcloud.StorageDriverConfig ncHost := os.Getenv("NEXTCLOUD") - fmt.Printf(`NEXTCLOUD env var: "%s"`, ncHost) if len(ncHost) == 0 { conf = &nextcloud.StorageDriverConfig{ EndPoint: "http://mock.com/apps/sciencemesh/", diff --git a/pkg/user/manager/nextcloud/nextcloud.go b/pkg/user/manager/nextcloud/nextcloud.go index 87a15c0165..32a49a0c2a 100644 --- a/pkg/user/manager/nextcloud/nextcloud.go +++ b/pkg/user/manager/nextcloud/nextcloud.go @@ -44,14 +44,16 @@ func init() { // Manager is the Nextcloud-based implementation of the share.Manager interface // see https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 type Manager struct { - client *http.Client - endPoint string + client *http.Client + sharedSecret string + endPoint string } // UserManagerConfig contains config for a Nextcloud-based UserManager type UserManagerConfig struct { - EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user management"` - MockHTTP bool `mapstructure:"mock_http"` + EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user management"` + SharedSecret string `mapstructure:"shared_secret"` + MockHTTP bool `mapstructure:"mock_http"` } func (c *UserManagerConfig) init() { @@ -94,12 +96,16 @@ func NewUserManager(c *UserManagerConfig) (*Manager, error) { // Wait for SetHTTPClient to be called later client = nil } else { + if len(c.EndPoint) == 0 { + return nil, errors.New("Please specify 'endpoint' in '[grpc.services.userprovider.drivers.nextcloud]'") + } client = &http.Client{} } return &Manager{ - endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" - client: client, + endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/" + sharedSecret: c.SharedSecret, + client: client, }, nil } @@ -117,16 +123,13 @@ func getUser(ctx context.Context) (*userpb.User, error) { return u, nil } -func (um *Manager) do(ctx context.Context, a Action) (int, []byte, error) { - user, err := getUser(ctx) - if err != nil { - return 0, nil, err - } - url := um.endPoint + "~" + user.Username + "/api/user/" + a.verb +func (um *Manager) do(ctx context.Context, a Action, username string) (int, []byte, error) { + url := um.endPoint + "~" + username + "/api/user/" + a.verb req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS)) if err != nil { panic(err) } + req.Header.Set("X-Reva-Secret", um.sharedSecret) req.Header.Set("Content-Type", "application/json") resp, err := um.client.Do(req) @@ -146,21 +149,13 @@ func (um *Manager) Configure(ml map[string]interface{}) error { // GetUser method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 func (um *Manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { - bodyStr, err := json.Marshal(uid) - if err != nil { - return nil, err - } - _, respBody, err := um.do(ctx, Action{"GetUser", string(bodyStr)}) - if err != nil { - return nil, err - } - - result := &userpb.User{} - err = json.Unmarshal(respBody, &result) - if err != nil { - return nil, err - } - return result, err + // FIXME: work around https://github.com/pondersource/nc-sciencemesh/issues/148 + return &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: uid.OpaqueId, + Idp: "local", + }, + }, nil } // GetUserByClaim method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 @@ -173,8 +168,13 @@ func (um *Manager) GetUserByClaim(ctx context.Context, claim, value string) (*us Claim: claim, Value: value, } + user, err := getUser(ctx) + if err != nil { + return nil, err + } + bodyStr, _ := json.Marshal(bodyObj) - _, respBody, err := um.do(ctx, Action{"GetUserByClaim", string(bodyStr)}) + _, respBody, err := um.do(ctx, Action{"GetUserByClaim", string(bodyStr)}, user.Username) if err != nil { return nil, err } @@ -192,7 +192,12 @@ func (um *Manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]str if err != nil { return nil, err } - _, respBody, err := um.do(ctx, Action{"GetUserGroups", string(bodyStr)}) + user, err := getUser(ctx) + if err != nil { + return nil, err + } + + _, respBody, err := um.do(ctx, Action{"GetUserGroups", string(bodyStr)}, user.Username) if err != nil { return nil, err } @@ -206,7 +211,12 @@ func (um *Manager) GetUserGroups(ctx context.Context, uid *userpb.UserId) ([]str // FindUsers method as defined in https://github.com/cs3org/reva/blob/v1.13.0/pkg/user/user.go#L29-L35 func (um *Manager) FindUsers(ctx context.Context, query string) ([]*userpb.User, error) { - _, respBody, err := um.do(ctx, Action{"FindUsers", query}) + user, err := getUser(ctx) + if err != nil { + return nil, err + } + + _, respBody, err := um.do(ctx, Action{"FindUsers", query}, user.Username) if err != nil { return nil, err } diff --git a/pkg/user/manager/nextcloud/nextcloud_server_mock.go b/pkg/user/manager/nextcloud/nextcloud_server_mock.go index 213f9eae51..13226e7ab1 100644 --- a/pkg/user/manager/nextcloud/nextcloud_server_mock.go +++ b/pkg/user/manager/nextcloud/nextcloud_server_mock.go @@ -58,22 +58,14 @@ func GetNextcloudServerMock(called *[]string) http.Handler { panic("Error reading response into buffer") } var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String()) - fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String())) - fmt.Printf("Nextcloud Server Mock key %s\n", key) *called = append(*called, key) response := responses[key] if (response == Response{}) { key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState) - fmt.Printf("Nextcloud Server Mock key with State %s\n", key) // *called = append(*called, key) response = responses[key] } if (response == Response{}) { - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") - fmt.Printf("Nextcloud Server Mock key not found! %s\n", key) - fmt.Println("ERROR!!") - fmt.Println("ERROR!!") response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty} } serverState = responses[key].newServerState diff --git a/pkg/user/manager/nextcloud/nextcloud_test.go b/pkg/user/manager/nextcloud/nextcloud_test.go index d043038925..3a82e18884 100644 --- a/pkg/user/manager/nextcloud/nextcloud_test.go +++ b/pkg/user/manager/nextcloud/nextcloud_test.go @@ -20,7 +20,6 @@ package nextcloud_test import ( "context" - "fmt" "os" "google.golang.org/grpc/metadata" @@ -41,7 +40,6 @@ func setUpNextcloudServer() (*nextcloud.Manager, *[]string, func()) { var conf *nextcloud.UserManagerConfig ncHost := os.Getenv("NEXTCLOUD") - fmt.Printf(`NEXTCLOUD env var: "%s"`, ncHost) if len(ncHost) == 0 { conf = &nextcloud.UserManagerConfig{ EndPoint: "http://mock.com/apps/sciencemesh/",