diff --git a/changelog/unreleased/ocm-share-create-sm.md b/changelog/unreleased/ocm-share-create-sm.md new file mode 100644 index 0000000000..ac2e53e05f --- /dev/null +++ b/changelog/unreleased/ocm-share-create-sm.md @@ -0,0 +1,4 @@ +Enhancement: Create OCM share from sciencemesh service + +https://github.com/cs3org/reva/pull/3695 +https://github.com/pondersource/sciencemesh-php/issues/166 \ No newline at end of file diff --git a/internal/http/services/sciencemesh/sciencemesh.go b/internal/http/services/sciencemesh/sciencemesh.go index e832583051..89f49990ee 100644 --- a/internal/http/services/sciencemesh/sciencemesh.go +++ b/internal/http/services/sciencemesh/sciencemesh.go @@ -95,6 +95,10 @@ func (s *svc) routerInit() error { if err := providersHandler.init(s.conf); err != nil { return err } + sharesHandler := new(sharesHandler) + if err := sharesHandler.init(s.conf); err != nil { + return err + } appsHandler := new(appsHandler) if err := appsHandler.init(s.conf); err != nil { @@ -106,8 +110,8 @@ func (s *svc) routerInit() error { s.router.Post("/accept-invite", tokenHandler.AcceptInvite) s.router.Get("/find-accepted-users", tokenHandler.FindAccepted) s.router.Get("/list-providers", providersHandler.ListProviders) + s.router.Post("/create-share", sharesHandler.CreateShare) s.router.Post("/open-in-app", appsHandler.OpenInApp) - return nil } diff --git a/internal/http/services/sciencemesh/share.go b/internal/http/services/sciencemesh/share.go new file mode 100644 index 0000000000..130cbd0505 --- /dev/null +++ b/internal/http/services/sciencemesh/share.go @@ -0,0 +1,176 @@ +// Copyright 2018-2023 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 sciencemesh + +import ( + "encoding/json" + "errors" + "mime" + "net/http" + + appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" + 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" + providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/internal/http/services/reqres" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/ocm/share" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/go-playground/validator/v10" +) + +var validate = validator.New() + +type sharesHandler struct { + gatewayClient gateway.GatewayAPIClient +} + +func (h *sharesHandler) init(c *config) error { + var err error + h.gatewayClient, err = pool.GetGatewayServiceClient(pool.Endpoint(c.GatewaySvc)) + return err +} + +type createShareRequest struct { + SourcePath string `json:"sourcePath" validate:"required"` + TargetPath string `json:"targetPath" validate:"required"` + Type string `json:"type"` + Role string `json:"role" validate:"oneof=viewer editor"` + RecipientUsername string `json:"recipientUsername" validate:"required"` + RecipientHost string `json:"recipientHost" validate:"required"` +} + +// CreateShare creates an OCM share. +func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) { + log := appctx.GetLogger(r.Context()) + + req, err := getCreateShareRequest(r) + if err != nil { + reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, "invalid parameters", err) + return + } + + ctx := r.Context() + + statRes, err := h.gatewayClient.Stat(ctx, &providerpb.StatRequest{ + Ref: &providerpb.Reference{ + Path: req.SourcePath, + }, + }) + switch { + case err != nil: + reqres.WriteError(w, r, reqres.APIErrorServerError, "unexpected error", err) + return + case statRes.Status.Code == rpc.Code_CODE_NOT_FOUND: + reqres.WriteError(w, r, reqres.APIErrorNotFound, statRes.Status.Message, nil) + return + case statRes.Status.Code != rpc.Code_CODE_OK: + reqres.WriteError(w, r, reqres.APIErrorServerError, statRes.Status.Message, errors.New(statRes.Status.Message)) + return + } + + recipientProviderInfo, err := h.gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ + Domain: req.RecipientHost, + }) + if err != nil { + reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc get invite by domain info request", err) + return + } + if recipientProviderInfo.Status.Code != rpc.Code_CODE_OK { + reqres.WriteError(w, r, reqres.APIErrorNotFound, recipientProviderInfo.Status.Message, errors.New(recipientProviderInfo.Status.Message)) + return + } + + perm, viewMode := getPermissionsByRole(req.Role) + + shareRes, err := h.gatewayClient.CreateOCMShare(ctx, &ocm.CreateOCMShareRequest{ + ResourceId: statRes.Info.Id, + Grantee: &providerpb.Grantee{ + Type: providerpb.GranteeType_GRANTEE_TYPE_USER, + Id: &providerpb.Grantee_UserId{ + UserId: &userpb.UserId{ + Idp: req.RecipientHost, + OpaqueId: req.RecipientUsername, + }, + }, + }, + RecipientMeshProvider: recipientProviderInfo.ProviderInfo, + AccessMethods: []*ocm.AccessMethod{ + share.NewWebDavAccessMethod(perm), + share.NewWebappAccessMethod(viewMode), + }, + }) + switch { + case err != nil: + reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc CreateOCMShare", err) + return + case shareRes.Status.Code == rpc.Code_CODE_NOT_FOUND: + reqres.WriteError(w, r, reqres.APIErrorNotFound, shareRes.Status.Message, nil) + return + case shareRes.Status.Code == rpc.Code_CODE_ALREADY_EXISTS: + reqres.WriteError(w, r, reqres.APIErrorAlreadyExist, shareRes.Status.Message, nil) + return + case shareRes.Status.Code != rpc.Code_CODE_OK: + reqres.WriteError(w, r, reqres.APIErrorAlreadyExist, shareRes.Status.Message, errors.New(shareRes.Status.Message)) + return + } + + if err := json.NewEncoder(w).Encode(shareRes); err != nil { + log.Error().Err(err).Msg("error encoding response") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +func getPermissionsByRole(role string) (*providerpb.ResourcePermissions, appprovider.ViewMode) { + switch role { + case "viewer": + return conversions.NewViewerRole().CS3ResourcePermissions(), appprovider.ViewMode_VIEW_MODE_READ_ONLY + case "editor": + return conversions.NewEditorRole().CS3ResourcePermissions(), appprovider.ViewMode_VIEW_MODE_READ_WRITE + } + return nil, 0 +} + +func getCreateShareRequest(r *http.Request) (*createShareRequest, error) { + var req createShareRequest + contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err == nil && contentType == "application/json" { + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + } else { + return nil, errors.New("body request not recognised") + } + // set defaults + if req.Type == "" { + req.Type = "viewer" + } + // validate the request + if err := validate.Struct(req); err != nil { + return nil, err + } + return &req, nil +}