From 993df6bb1a5199f722e95c78430327f184593de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 5 Nov 2020 21:39:31 +0100 Subject: [PATCH] refactor ocs sharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../handlers/apps/sharing/shares/public.go | 470 +++++++++++ .../handlers/apps/sharing/shares/remote.go | 149 ++++ .../handlers/apps/sharing/shares/shares.go | 770 ++---------------- .../ocs/handlers/apps/sharing/shares/user.go | 125 +++ 4 files changed, 800 insertions(+), 714 deletions(-) create mode 100644 internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go create mode 100644 internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go new file mode 100644 index 00000000000..17260727093 --- /dev/null +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -0,0 +1,470 @@ +// Copyright 2018-2020 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 shares + +import ( + "encoding/json" + "fmt" + "net/http" + "path" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/rs/zerolog/log" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" + "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/pkg/errors" +) + +func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + // prefix the path with the owners home, because ocs share requests are relative to the home dir + // TODO the path actually depends on the configured webdav_namespace + hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) + return + } + + prefix := hRes.GetPath() + + statReq := provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: path.Join(prefix, r.FormValue("path")), // TODO replace path with target + }, + }, + } + + statRes, err := c.Stat(ctx, &statReq) + if err != nil { + log.Debug().Err(err).Str("createShare", "shares").Msg("error on stat call") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) + return + } + + if statRes.Status.Code != rpc.Code_CODE_OK { + if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "resource not found", fmt.Errorf("error creating share on non-existing resource")) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error when querying resource", fmt.Errorf("error when querying resource information while creating share, status %d", statRes.Status.Code)) + return + } + + err = r.ParseForm() + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not parse form from request", err) + return + } + + newPermissions, err := permissionFromRequest(r, h) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not read permission from request", err) + return + } + + if newPermissions == nil { + // default perms: read-only + // TODO: the default might change depending on allowed permissions and configs + newPermissions, err = ocPublicPermToCs3(1, h) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "Could not convert default permissions", err) + return + } + } + + req := link.CreatePublicShareRequest{ + ResourceInfo: statRes.GetInfo(), + Grant: &link.Grant{ + Permissions: &link.PublicSharePermissions{ + Permissions: newPermissions, + }, + Password: r.FormValue("password"), + }, + } + + expireTimeString, ok := r.Form["expireDate"] + if ok { + if expireTimeString[0] != "" { + expireTime, err := parseTimestamp(expireTimeString[0]) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "invalid datetime format", err) + return + } + if expireTime != nil { + req.Grant.Expiration = expireTime + } + } + } + + // set displayname and password protected as arbitrary metadata + req.ResourceInfo.ArbitraryMetadata = &provider.ArbitraryMetadata{ + Metadata: map[string]string{ + "name": r.FormValue("name"), + // "password": r.FormValue("password"), + }, + } + + createRes, err := c.CreatePublicShare(ctx, &req) + if err != nil { + log.Debug().Err(err).Str("createShare", "shares").Msgf("error creating a public share to resource id: %v", statRes.Info.GetId()) + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statRes.Info.GetId())) + return + } + + if createRes.Status.Code != rpc.Code_CODE_OK { + log.Debug().Err(errors.New("create public share failed")).Str("shares", "createShare").Msgf("create public share failed with status code: %v", createRes.Status.Code.String()) + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create public share request failed", err) + return + } + + s := conversions.PublicShare2ShareData(createRes.Share, r, h.publicURL) + err = h.addFileInfo(ctx, s, statRes.Info) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error enhancing response with share data", err) + return + } + h.addDisplaynames(ctx, c, s) + + response.WriteOCSSuccess(w, r, s) +} + +func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSharesRequest_Filter) ([]*conversions.ShareData, *rpc.Status, error) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + // TODO(refs) why is this guard needed? Are we moving towards a gateway only for service discovery? without a gateway this is dead code. + if h.gatewayAddr != "" { + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, nil, err + } + + req := link.ListPublicSharesRequest{ + Filters: filters, + } + + res, err := c.ListPublicShares(ctx, &req) + if err != nil || res.Status.Code != rpc.Code_CODE_OK { + return nil, res.Status, err + } + + ocsDataPayload := make([]*conversions.ShareData, 0) + for _, share := range res.GetShare() { + + statRequest := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{ + Id: share.ResourceId, + }, + }, + } + + statResponse, err := c.Stat(ctx, statRequest) + if err != nil || res.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", share).Interface("response", statResponse).Err(err).Msg("could not stat share, skipping") + continue + } + + sData := conversions.PublicShare2ShareData(share, r, h.publicURL) + + sData.Name = share.DisplayName + + if err := h.addFileInfo(ctx, sData, statResponse.Info); err != nil { + log.Debug().Interface("share", share).Interface("info", statResponse.Info).Err(err).Msg("could not add file info, skipping") + continue + } + h.addDisplaynames(ctx, c, sData) + + log.Debug().Interface("share", share).Interface("info", statResponse.Info).Interface("shareData", share).Msg("mapped") + + ocsDataPayload = append(ocsDataPayload, sData) + + } + + return ocsDataPayload, nil, nil + } + + return nil, nil, errors.New("bad request") +} + +func (h *Handler) isPublicShare(r *http.Request, oid string) bool { + logger := appctx.GetLogger(r.Context()) + client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + logger.Err(err) + } + + psRes, err := client.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ + Ref: &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: oid, + }, + }, + }, + }) + if err != nil { + logger.Err(err) + } + + if psRes.GetShare() != nil { + return true + } + + // check if we have a user share + uRes, err := client.GetShare(r.Context(), &collaboration.GetShareRequest{ + Ref: &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: &collaboration.ShareId{ + OpaqueId: oid, + }, + }, + }, + }) + if err != nil { + logger.Err(err) + } + + if uRes.GetShare() != nil { + return false + } + + // TODO token is neither a public or a user share. + return false +} + +func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shareID string) { + updates := []*link.UpdatePublicShareRequest_Update{} + logger := appctx.GetLogger(r.Context()) + + gwC, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + log.Err(err).Str("shareID", shareID).Msg("updatePublicShare") + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "error getting a connection to the gateway service", nil) + return + } + + before, err := gwC.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ + Ref: &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: shareID, + }, + }, + }, + }) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "failed to get public share", nil) + return + } + + err = r.ParseForm() + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not parse form from request", err) + return + } + + // indicates whether values to update were found, + // to check if the request was valid, + // not whether an actual update has been performed + updatesFound := false + + newName, ok := r.Form["name"] + if ok { + updatesFound = true + if newName[0] != before.Share.DisplayName { + updates = append(updates, &link.UpdatePublicShareRequest_Update{ + Type: link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME, + DisplayName: newName[0], + }) + } + } + + // Permissions + newPermissions, err := permissionFromRequest(r, h) + logger.Debug().Interface("newPermissions", newPermissions).Msg("Parsed permissions") + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid permissions", err) + return + } + + // update permissions if given + if newPermissions != nil { + updatesFound = true + publicSharePermissions := &link.PublicSharePermissions{ + Permissions: newPermissions, + } + beforePerm, _ := json.Marshal(before.GetShare().Permissions) + afterPerm, _ := json.Marshal(publicSharePermissions) + if string(beforePerm) != string(afterPerm) { + logger.Info().Str("shares", "update").Msgf("updating permissions from %v to: %v", string(beforePerm), string(afterPerm)) + updates = append(updates, &link.UpdatePublicShareRequest_Update{ + Type: link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS, + Grant: &link.Grant{ + Permissions: publicSharePermissions, + }, + }) + } + } + + // ExpireDate + expireTimeString, ok := r.Form["expireDate"] + // check if value is set and must be updated or cleared + if ok { + updatesFound = true + var newExpiration *types.Timestamp + if expireTimeString[0] != "" { + newExpiration, err = parseTimestamp(expireTimeString[0]) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid datetime format", err) + return + } + } + + beforeExpiration, _ := json.Marshal(before.Share.Expiration) + afterExpiration, _ := json.Marshal(newExpiration) + if string(afterExpiration) != string(beforeExpiration) { + logger.Debug().Str("shares", "update").Msgf("updating expire date from %v to: %v", string(beforeExpiration), string(afterExpiration)) + updates = append(updates, &link.UpdatePublicShareRequest_Update{ + Type: link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION, + Grant: &link.Grant{ + Expiration: newExpiration, + }, + }) + } + } + + // Password + newPassword, ok := r.Form["password"] + // update or clear password + if ok { + updatesFound = true + logger.Info().Str("shares", "update").Msg("password updated") + updates = append(updates, &link.UpdatePublicShareRequest_Update{ + Type: link.UpdatePublicShareRequest_Update_TYPE_PASSWORD, + Grant: &link.Grant{ + Password: newPassword[0], + }, + }) + } + + publicShare := before.Share + + // Updates are atomical. See: https://github.com/cs3org/cs3apis/pull/67#issuecomment-617651428 so in order to get the latest updated version + if len(updates) > 0 { + uRes := &link.UpdatePublicShareResponse{Share: before.Share} + for k := range updates { + uRes, err = gwC.UpdatePublicShare(r.Context(), &link.UpdatePublicShareRequest{ + Ref: &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: shareID, + }, + }, + }, + Update: updates[k], + }) + if err != nil { + log.Err(err).Str("shareID", shareID).Msg("sending update request to public link provider") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "Error sending update request to public link provider", err) + return + } + } + publicShare = uRes.Share + } else if !updatesFound { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "No updates specified in request", nil) + return + } + + statReq := provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{ + Id: before.Share.ResourceId, + }, + }, + } + + statRes, err := gwC.Stat(r.Context(), &statReq) + if err != nil { + log.Debug().Err(err).Str("shares", "update public share").Msg("error during stat") + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) + return + } + + s := conversions.PublicShare2ShareData(publicShare, r, h.publicURL) + err = h.addFileInfo(r.Context(), s, statRes.Info) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error enhancing response with share data", err) + return + } + h.addDisplaynames(r.Context(), gwC, s) + + response.WriteOCSSuccess(w, r, s) +} + +func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shareID string) { + ctx := r.Context() + + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + + req := &link.RemovePublicShareRequest{ + Ref: &link.PublicShareReference{ + Spec: &link.PublicShareReference_Id{ + Id: &link.PublicShareId{ + OpaqueId: shareID, + }, + }, + }, + } + + res, err := c.RemovePublicShare(ctx, req) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err) + return + } + if res.Status.Code != rpc.Code_CODE_OK { + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc delete share request failed", err) + return + } + + response.WriteOCSSuccess(w, r, nil) +} diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go index 742e132e70a..341388554fc 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/remote.go @@ -20,12 +20,161 @@ package shares import ( "net/http" + "path" + "strconv" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/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/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" + "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) +func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + // prefix the path with the owners home, because ocs share requests are relative to the home dir + // TODO the path actually depends on the configured webdav_namespace + hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) + return + } + + prefix := hRes.GetPath() + + shareWithUser, shareWithProvider := r.FormValue("shareWithUser"), r.FormValue("shareWithProvider") + if shareWithUser == "" || shareWithProvider == "" { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith parameters", nil) + return + } + + providerInfoResp, err := c.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ + Domain: shareWithProvider, + }) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get invite by domain info request", err) + return + } + + remoteUserRes, err := c.GetRemoteUser(ctx, &invitepb.GetRemoteUserRequest{ + RemoteUserId: &userpb.UserId{OpaqueId: shareWithUser, Idp: shareWithProvider}, + }) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) + return + } + if remoteUserRes.Status.Code != rpc.Code_CODE_OK { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "user not found", err) + return + } + + var permissions conversions.Permissions + var role string + + pval := r.FormValue("permissions") + if pval == "" { + // by default only allow read permissions / assign viewer role + permissions = conversions.PermissionRead + role = conversions.RoleViewer + } else { + pint, err := strconv.Atoi(pval) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) + return + } + permissions, err = conversions.NewPermissions(pint) + if err != nil { + response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) + return + } + role = conversions.Permissions2Role(permissions) + } + + var resourcePermissions *provider.ResourcePermissions + resourcePermissions, err = h.map2CS3Permissions(role, permissions) + if err != nil { + log.Warn().Err(err).Msg("unknown role, mapping legacy permissions") + resourcePermissions = asCS3Permissions(permissions, nil) + } + + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{ + Path: path.Join(prefix, r.FormValue("path")), + }, + }, + } + statRes, err := c.Stat(ctx, statReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) + return + } + if statRes.Status.Code != rpc.Code_CODE_OK { + if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) + return + } + + createShareReq := &ocm.CreateOCMShareRequest{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "permissions": &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(strconv.Itoa(int(permissions))), + }, + "name": &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(statRes.Info.Path), + }, + }, + }, + ResourceId: statRes.Info.Id, + Grant: &ocm.ShareGrant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: remoteUserRes.RemoteUser.GetId(), + }, + Permissions: &ocm.SharePermissions{ + Permissions: resourcePermissions, + }, + }, + RecipientMeshProvider: providerInfoResp.ProviderInfo, + } + + createShareResponse, err := c.CreateOCMShare(ctx, createShareReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc create ocm share request", err) + return + } + if createShareResponse.Status.Code != rpc.Code_CODE_OK { + if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create ocm share request failed", err) + return + } + + response.WriteOCSSuccess(w, r, "OCM Share created") +} + func (h *Handler) getFederatedShare(w http.ResponseWriter, r *http.Request, shareID string) { // TODO: Implement response with HAL schemating diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index bdb5bcdb908..c366f4a3782 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -32,12 +32,9 @@ import ( gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" - ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/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/rs/zerolog/log" @@ -271,7 +268,7 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request) { createShareReq := &collaboration.CreateShareRequest{ Opaque: &types.Opaque{ Map: map[string]*types.OpaqueEntry{ - "role": &types.OpaqueEntry{ + "role": { Decoder: "json", Value: val, }, @@ -317,265 +314,6 @@ func (h *Handler) createUserShare(w http.ResponseWriter, r *http.Request) { response.WriteOCSSuccess(w, r, s) } -func (h *Handler) createPublicLinkShare(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - log := appctx.GetLogger(ctx) - - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - // prefix the path with the owners home, because ocs share requests are relative to the home dir - // TODO the path actually depends on the configured webdav_namespace - hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) - return - } - - prefix := hRes.GetPath() - - statReq := provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: path.Join(prefix, r.FormValue("path")), // TODO replace path with target - }, - }, - } - - statRes, err := c.Stat(ctx, &statReq) - if err != nil { - log.Debug().Err(err).Str("createShare", "shares").Msg("error on stat call") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) - return - } - - if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "resource not found", fmt.Errorf("error creating share on non-existing resource")) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error when querying resource", fmt.Errorf("error when querying resource information while creating share, status %d", statRes.Status.Code)) - return - } - - err = r.ParseForm() - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not parse form from request", err) - return - } - - newPermissions, err := permissionFromRequest(r, h) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not read permission from request", err) - return - } - - if newPermissions == nil { - // default perms: read-only - // TODO: the default might change depending on allowed permissions and configs - newPermissions, err = ocPublicPermToCs3(1, h) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "Could not convert default permissions", err) - return - } - } - - req := link.CreatePublicShareRequest{ - ResourceInfo: statRes.GetInfo(), - Grant: &link.Grant{ - Permissions: &link.PublicSharePermissions{ - Permissions: newPermissions, - }, - Password: r.FormValue("password"), - }, - } - - expireTimeString, ok := r.Form["expireDate"] - if ok { - if expireTimeString[0] != "" { - expireTime, err := parseTimestamp(expireTimeString[0]) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "invalid datetime format", err) - return - } - if expireTime != nil { - req.Grant.Expiration = expireTime - } - } - } - - // set displayname and password protected as arbitrary metadata - req.ResourceInfo.ArbitraryMetadata = &provider.ArbitraryMetadata{ - Metadata: map[string]string{ - "name": r.FormValue("name"), - // "password": r.FormValue("password"), - }, - } - - createRes, err := c.CreatePublicShare(ctx, &req) - if err != nil { - log.Debug().Err(err).Str("createShare", "shares").Msgf("error creating a public share to resource id: %v", statRes.Info.GetId()) - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error creating public share", fmt.Errorf("error creating a public share to resource id: %v", statRes.Info.GetId())) - return - } - - if createRes.Status.Code != rpc.Code_CODE_OK { - log.Debug().Err(errors.New("create public share failed")).Str("shares", "createShare").Msgf("create public share failed with status code: %v", createRes.Status.Code.String()) - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create public share request failed", err) - return - } - - s := conversions.PublicShare2ShareData(createRes.Share, r, h.publicURL) - err = h.addFileInfo(ctx, s, statRes.Info) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error enhancing response with share data", err) - return - } - h.addDisplaynames(ctx, c, s) - - response.WriteOCSSuccess(w, r, s) -} - -func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - log := appctx.GetLogger(ctx) - - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - // prefix the path with the owners home, because ocs share requests are relative to the home dir - // TODO the path actually depends on the configured webdav_namespace - hRes, err := c.GetHome(ctx, &provider.GetHomeRequest{}) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get home request", err) - return - } - - prefix := hRes.GetPath() - - shareWithUser, shareWithProvider := r.FormValue("shareWithUser"), r.FormValue("shareWithProvider") - if shareWithUser == "" || shareWithProvider == "" { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith parameters", nil) - return - } - - providerInfoResp, err := c.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{ - Domain: shareWithProvider, - }) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get invite by domain info request", err) - return - } - - remoteUserRes, err := c.GetRemoteUser(ctx, &invitepb.GetRemoteUserRequest{ - RemoteUserId: &userpb.UserId{OpaqueId: shareWithUser, Idp: shareWithProvider}, - }) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching recipient", err) - return - } - if remoteUserRes.Status.Code != rpc.Code_CODE_OK { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "user not found", err) - return - } - - var permissions conversions.Permissions - var role string - - pval := r.FormValue("permissions") - if pval == "" { - // by default only allow read permissions / assign viewer role - permissions = conversions.PermissionRead - role = conversions.RoleViewer - } else { - pint, err := strconv.Atoi(pval) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "permissions must be an integer", nil) - return - } - permissions, err = conversions.NewPermissions(pint) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, err.Error(), nil) - return - } - role = conversions.Permissions2Role(permissions) - } - - var resourcePermissions *provider.ResourcePermissions - resourcePermissions, err = h.map2CS3Permissions(role, permissions) - if err != nil { - log.Warn().Err(err).Msg("unknown role, mapping legacy permissions") - resourcePermissions = asCS3Permissions(permissions, nil) - } - - statReq := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Path{ - Path: path.Join(prefix, r.FormValue("path")), - }, - }, - } - statRes, err := c.Stat(ctx, statReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc stat request", err) - return - } - if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) - return - } - - createShareReq := &ocm.CreateOCMShareRequest{ - Opaque: &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "permissions": &types.OpaqueEntry{ - Decoder: "plain", - Value: []byte(strconv.Itoa(int(permissions))), - }, - "name": &types.OpaqueEntry{ - Decoder: "plain", - Value: []byte(statRes.Info.Path), - }, - }, - }, - ResourceId: statRes.Info.Id, - Grant: &ocm.ShareGrant{ - Grantee: &provider.Grantee{ - Type: provider.GranteeType_GRANTEE_TYPE_USER, - Id: remoteUserRes.RemoteUser.GetId(), - }, - Permissions: &ocm.SharePermissions{ - Permissions: resourcePermissions, - }, - }, - RecipientMeshProvider: providerInfoResp.ProviderInfo, - } - - createShareResponse, err := c.CreateOCMShare(ctx, createShareReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc create ocm share request", err) - return - } - if createShareResponse.Status.Code != rpc.Code_CODE_OK { - if createShareResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc create ocm share request failed", err) - return - } - - response.WriteOCSSuccess(w, r, "OCM Share created") -} - func (h *Handler) stat(ctx context.Context, path string) (*provider.StatResponse, error) { c, err := pool.GetGatewayServiceClient(h.gatewayAddr) if err != nil { @@ -914,77 +652,6 @@ func (h *Handler) updateShare(w http.ResponseWriter, r *http.Request, shareID st response.WriteOCSSuccess(w, r, share) } -func (h *Handler) removePublicShare(w http.ResponseWriter, r *http.Request, shareID string) { - ctx := r.Context() - - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - - req := &link.RemovePublicShareRequest{ - Ref: &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: shareID, - }, - }, - }, - } - - res, err := c.RemovePublicShare(ctx, req) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err) - return - } - if res.Status.Code != rpc.Code_CODE_OK { - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc delete share request failed", err) - return - } - - response.WriteOCSSuccess(w, r, nil) -} - -func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareID string) { - ctx := r.Context() - - uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) - return - } - - uReq := &collaboration.RemoveShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: shareID, - }, - }, - }, - } - uRes, err := uClient.RemoveShare(ctx, uReq) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err) - return - } - - if uRes.Status.Code != rpc.Code_CODE_OK { - if uRes.Status.Code == rpc.Code_CODE_NOT_FOUND { - response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) - return - } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc delete share request failed", err) - return - } - response.WriteOCSSuccess(w, r, nil) -} - func (h *Handler) isListSharesWithMe(w http.ResponseWriter, r *http.Request) (listSharedWithMe bool) { if r.FormValue("shared_with_me") != "" { var err error @@ -1008,14 +675,13 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { case "all": stateFilter = ocsStateUnknown // no filter case "0": // accepted - stateFilter = collaboration.ShareState_SHARE_STATE_ACCEPTED // TODO implement accepted filter + stateFilter = collaboration.ShareState_SHARE_STATE_ACCEPTED case "1": // pending - stateFilter = collaboration.ShareState_SHARE_STATE_PENDING // TODO implement pending filter + stateFilter = collaboration.ShareState_SHARE_STATE_PENDING case "2": // rejected - stateFilter = collaboration.ShareState_SHARE_STATE_REJECTED // TODO implement rejected filter + stateFilter = collaboration.ShareState_SHARE_STATE_REJECTED default: stateFilter = collaboration.ShareState_SHARE_STATE_ACCEPTED - // TODO only list accepted shares } gwc, err := pool.GetGatewayServiceClient(h.gatewayAddr) @@ -1055,13 +721,17 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { } if statRes.Status.Code != rpc.Code_CODE_OK { - if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { + switch statRes.Status.Code { + case rpc.Code_CODE_NOT_FOUND: response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "path not found", nil) - return + case rpc.Code_CODE_PERMISSION_DENIED: + response.WriteOCSError(w, r, response.MetaUnauthorized.StatusCode, "permission denied", nil) + default: + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", nil) } - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", err) return } + info = statRes.GetInfo() } @@ -1100,24 +770,22 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { }, } - statResponse, err := gwc.Stat(r.Context(), &statRequest) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) - return - } - // TODO check status codes - info = statResponse.GetInfo() - } else { - if rs.Share.ResourceId.StorageId != info.GetId().StorageId || - rs.Share.ResourceId.OpaqueId != info.GetId().OpaqueId { + statRes, err := gwc.Stat(r.Context(), &statRequest) + if err != nil || statRes.Status.Code != rpc.Code_CODE_OK { + h.logProblems(statRes.GetStatus(), err, "could not stat, skipping") continue } + + info = statRes.GetInfo() + } else if rs.Share.ResourceId.StorageId != info.GetId().StorageId || + rs.Share.ResourceId.OpaqueId != info.GetId().OpaqueId { + continue } data, err := conversions.UserShare2ShareData(r.Context(), rs.Share) if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) - return + log.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not UserShare2ShareData, skipping") + continue } switch rs.GetState() { @@ -1131,10 +799,9 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { data.State = ocsStateUnknown } - err = h.addFileInfo(r.Context(), data, info) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) - return + if err := h.addFileInfo(ctx, data, info); err != nil { + log.Debug().Interface("received_share", rs).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") + continue } h.addDisplaynames(r.Context(), gwc, data) @@ -1167,6 +834,18 @@ func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { return } + if hRes.Status.Code != rpc.Code_CODE_OK { + switch hRes.Status.Code { + case rpc.Code_CODE_NOT_FOUND: + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "path not found", nil) + case rpc.Code_CODE_PERMISSION_DENIED: + response.WriteOCSError(w, r, response.MetaUnauthorized.StatusCode, "permission denied", nil) + default: + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc stat request failed", nil) + } + return + } + filters, linkFilters, err = h.addFilters(w, r, hRes.GetPath()) if err != nil { response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) @@ -1174,84 +853,34 @@ func (h *Handler) listSharesWithOthers(w http.ResponseWriter, r *http.Request) { } } - userShares, err := h.listUserShares(r, filters) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) - return - } + userShares, status, err := h.listUserShares(r, filters) + h.logProblems(status, err, "could not listUserShares") + + publicShares, status, err := h.listPublicShares(r, linkFilters) + h.logProblems(status, err, "could not listPublicShares") - publicShares, err := h.listPublicShares(r, linkFilters) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, err.Error(), err) - return - } shares = append(shares, append(userShares, publicShares...)...) response.WriteOCSSuccess(w, r, shares) } -func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSharesRequest_Filter) ([]*conversions.ShareData, error) { - ctx := r.Context() - log := appctx.GetLogger(ctx) - - // TODO(refs) why is this guard needed? Are we moving towards a gateway only for service discovery? without a gateway this is dead code. - if h.gatewayAddr != "" { - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - return nil, err - } - - req := link.ListPublicSharesRequest{ - Filters: filters, - } - - res, err := c.ListPublicShares(ctx, &req) - if err != nil { - return nil, err - } - - ocsDataPayload := make([]*conversions.ShareData, 0) - for _, share := range res.GetShare() { - - statRequest := &provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{ - Id: share.ResourceId, - }, - }, - } - - statResponse, err := c.Stat(ctx, statRequest) - if err != nil { - return nil, err - } - if statResponse.Status.Code != rpc.Code_CODE_OK { - if statResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - continue - } - - return nil, errors.New(fmt.Sprintf("could not stat share target: %v, code: %v", share.ResourceId, statResponse.Status)) - } - - sData := conversions.PublicShare2ShareData(share, r, h.publicURL) - - sData.Name = share.DisplayName - - if h.addFileInfo(ctx, sData, statResponse.Info) != nil { - return nil, err - } - h.addDisplaynames(ctx, c, sData) - - log.Debug().Interface("share", share).Interface("info", statResponse.Info).Interface("shareData", share).Msg("mapped") - - ocsDataPayload = append(ocsDataPayload, sData) - +func (h *Handler) logProblems(s *rpc.Status, e error, msg string) { + if e != nil { + // errors need to be taken care of + log.Error().Err(e).Msg(msg) + } + if s != nil && s.Code != rpc.Code_CODE_OK { + switch s.Code { + // not found and permission denied can happen during normal operations + case rpc.Code_CODE_NOT_FOUND: + log.Debug().Interface("status", s).Msg(msg) + case rpc.Code_CODE_PERMISSION_DENIED: + log.Debug().Interface("status", s).Msg(msg) + default: + // anything else should not happen, someone needs to dig into it + log.Error().Interface("status", s).Msg(msg) } - - return ocsDataPayload, nil } - - return nil, errors.New("bad request") } func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix string) ([]*collaboration.ListSharesRequest_Filter, []*link.ListPublicSharesRequest_Filter, error) { @@ -1311,78 +940,6 @@ func (h *Handler) addFilters(w http.ResponseWriter, r *http.Request, prefix stri return collaborationFilters, linkFilters, nil } -func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListSharesRequest_Filter) ([]*conversions.ShareData, error) { - var rInfo *provider.ResourceInfo - ctx := r.Context() - log := appctx.GetLogger(ctx) - - lsUserSharesRequest := collaboration.ListSharesRequest{ - Filters: filters, - } - - ocsDataPayload := make([]*conversions.ShareData, 0) - if h.gatewayAddr != "" { - // get a connection to the users share provider - c, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - return nil, err - } - - // do list shares request. unfiltered - lsUserSharesResponse, err := c.ListShares(ctx, &lsUserSharesRequest) - if err != nil { - return nil, err - } - - if lsUserSharesResponse.Status.Code != rpc.Code_CODE_OK { - return nil, errors.New("could not ListShares") - } - - // build OCS response payload - for _, s := range lsUserSharesResponse.Shares { - share, err := conversions.UserShare2ShareData(ctx, s) - if err != nil { - return nil, err - } - - // prepare the stat request - statReq := &provider.StatRequest{ - // prepare the reference - Ref: &provider.Reference{ - // using ResourceId from the share - Spec: &provider.Reference_Id{Id: s.ResourceId}, - }, - } - - statResponse, err := c.Stat(ctx, statReq) - if err != nil { - return nil, err - } - - if statResponse.Status.Code != rpc.Code_CODE_OK { - if statResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - // TODO share target was not found, we should not error here. - // return nil, errors.New(fmt.Sprintf("could not stat share target: %v, code: %v", s.ResourceId, statResponse.Status)) - continue - } - - return nil, errors.New(fmt.Sprintf("could not stat share target: %v, code: %v", s.ResourceId, statResponse.Status)) - } - - err = h.addFileInfo(ctx, share, statResponse.Info) - if err != nil { - return nil, err - } - h.addDisplaynames(ctx, c, share) - - log.Debug().Interface("share", s).Interface("info", rInfo).Interface("shareData", share).Msg("mapped") - ocsDataPayload = append(ocsDataPayload, share) - } - } - - return ocsDataPayload, nil -} - func wrapResourceID(r *provider.ResourceId) string { return wrap(r.StorageId, r.OpaqueId) } @@ -1491,221 +1048,6 @@ func (h *Handler) addDisplaynames(ctx context.Context, c gateway.GatewayAPIClien } } -func (h *Handler) isPublicShare(r *http.Request, oid string) bool { - logger := appctx.GetLogger(r.Context()) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - logger.Err(err) - } - - psRes, err := client.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ - Ref: &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: oid, - }, - }, - }, - }) - if err != nil { - logger.Err(err) - } - - if psRes.GetShare() != nil { - return true - } - - // check if we have a user share - uRes, err := client.GetShare(r.Context(), &collaboration.GetShareRequest{ - Ref: &collaboration.ShareReference{ - Spec: &collaboration.ShareReference_Id{ - Id: &collaboration.ShareId{ - OpaqueId: oid, - }, - }, - }, - }) - if err != nil { - logger.Err(err) - } - - if uRes.GetShare() != nil { - return false - } - - // TODO token is neither a public or a user share. - return false -} - -func (h *Handler) updatePublicShare(w http.ResponseWriter, r *http.Request, shareID string) { - updates := []*link.UpdatePublicShareRequest_Update{} - logger := appctx.GetLogger(r.Context()) - - gwC, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - log.Err(err).Str("shareID", shareID).Msg("updatePublicShare") - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "error getting a connection to the gateway service", nil) - return - } - - before, err := gwC.GetPublicShare(r.Context(), &link.GetPublicShareRequest{ - Ref: &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: shareID, - }, - }, - }, - }) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "failed to get public share", nil) - return - } - - err = r.ParseForm() - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "Could not parse form from request", err) - return - } - - // indicates whether values to update were found, - // to check if the request was valid, - // not whether an actual update has been performed - updatesFound := false - - newName, ok := r.Form["name"] - if ok { - updatesFound = true - if newName[0] != before.Share.DisplayName { - updates = append(updates, &link.UpdatePublicShareRequest_Update{ - Type: link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME, - DisplayName: newName[0], - }) - } - } - - // Permissions - newPermissions, err := permissionFromRequest(r, h) - logger.Debug().Interface("newPermissions", newPermissions).Msg("Parsed permissions") - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid permissions", err) - return - } - - // update permissions if given - if newPermissions != nil { - updatesFound = true - publicSharePermissions := &link.PublicSharePermissions{ - Permissions: newPermissions, - } - beforePerm, _ := json.Marshal(before.GetShare().Permissions) - afterPerm, _ := json.Marshal(publicSharePermissions) - if string(beforePerm) != string(afterPerm) { - logger.Info().Str("shares", "update").Msgf("updating permissions from %v to: %v", string(beforePerm), string(afterPerm)) - updates = append(updates, &link.UpdatePublicShareRequest_Update{ - Type: link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS, - Grant: &link.Grant{ - Permissions: publicSharePermissions, - }, - }) - } - } - - // ExpireDate - expireTimeString, ok := r.Form["expireDate"] - // check if value is set and must be updated or cleared - if ok { - updatesFound = true - var newExpiration *types.Timestamp - if expireTimeString[0] != "" { - newExpiration, err = parseTimestamp(expireTimeString[0]) - if err != nil { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "invalid datetime format", err) - return - } - } - - beforeExpiration, _ := json.Marshal(before.Share.Expiration) - afterExpiration, _ := json.Marshal(newExpiration) - if string(afterExpiration) != string(beforeExpiration) { - logger.Debug().Str("shares", "update").Msgf("updating expire date from %v to: %v", string(beforeExpiration), string(afterExpiration)) - updates = append(updates, &link.UpdatePublicShareRequest_Update{ - Type: link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION, - Grant: &link.Grant{ - Expiration: newExpiration, - }, - }) - } - } - - // Password - newPassword, ok := r.Form["password"] - // update or clear password - if ok { - updatesFound = true - logger.Info().Str("shares", "update").Msg("password updated") - updates = append(updates, &link.UpdatePublicShareRequest_Update{ - Type: link.UpdatePublicShareRequest_Update_TYPE_PASSWORD, - Grant: &link.Grant{ - Password: newPassword[0], - }, - }) - } - - publicShare := before.Share - - // Updates are atomical. See: https://github.com/cs3org/cs3apis/pull/67#issuecomment-617651428 so in order to get the latest updated version - if len(updates) > 0 { - uRes := &link.UpdatePublicShareResponse{Share: before.Share} - for k := range updates { - uRes, err = gwC.UpdatePublicShare(r.Context(), &link.UpdatePublicShareRequest{ - Ref: &link.PublicShareReference{ - Spec: &link.PublicShareReference_Id{ - Id: &link.PublicShareId{ - OpaqueId: shareID, - }, - }, - }, - Update: updates[k], - }) - if err != nil { - log.Err(err).Str("shareID", shareID).Msg("sending update request to public link provider") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "Error sending update request to public link provider", err) - return - } - } - publicShare = uRes.Share - } else if !updatesFound { - response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "No updates specified in request", nil) - return - } - - statReq := provider.StatRequest{ - Ref: &provider.Reference{ - Spec: &provider.Reference_Id{ - Id: before.Share.ResourceId, - }, - }, - } - - statRes, err := gwC.Stat(r.Context(), &statReq) - if err != nil { - log.Debug().Err(err).Str("shares", "update public share").Msg("error during stat") - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "missing resource information", fmt.Errorf("error getting resource information")) - return - } - - s := conversions.PublicShare2ShareData(publicShare, r, h.publicURL) - err = h.addFileInfo(r.Context(), s, statRes.Info) - if err != nil { - response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error enhancing response with share data", err) - return - } - h.addDisplaynames(r.Context(), gwC, s) - - response.WriteOCSSuccess(w, r, s) -} - func parseTimestamp(timestampString string) (*types.Timestamp, error) { parsedTime, err := time.Parse("2006-01-02T15:04:05Z0700", timestampString) if err != nil { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go new file mode 100644 index 00000000000..060506b4f3f --- /dev/null +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -0,0 +1,125 @@ +// Copyright 2018-2020 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 shares + +import ( + "net/http" + + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "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/owncloud/ocs/response" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" +) + +func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, shareID string) { + ctx := r.Context() + + uClient, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err) + return + } + + uReq := &collaboration.RemoveShareRequest{ + Ref: &collaboration.ShareReference{ + Spec: &collaboration.ShareReference_Id{ + Id: &collaboration.ShareId{ + OpaqueId: shareID, + }, + }, + }, + } + uRes, err := uClient.RemoveShare(ctx, uReq) + if err != nil { + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err) + return + } + + if uRes.Status.Code != rpc.Code_CODE_OK { + if uRes.Status.Code == rpc.Code_CODE_NOT_FOUND { + response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil) + return + } + response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc delete share request failed", err) + return + } + response.WriteOCSSuccess(w, r, nil) +} + +func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.ListSharesRequest_Filter) ([]*conversions.ShareData, *rpc.Status, error) { + var rInfo *provider.ResourceInfo + ctx := r.Context() + log := appctx.GetLogger(ctx) + + lsUserSharesRequest := collaboration.ListSharesRequest{ + Filters: filters, + } + + ocsDataPayload := make([]*conversions.ShareData, 0) + if h.gatewayAddr != "" { + // get a connection to the users share provider + c, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return nil, nil, err + } + + // do list shares request. filtered + lsUserSharesResponse, err := c.ListShares(ctx, &lsUserSharesRequest) + if err != nil || lsUserSharesResponse.Status.Code != rpc.Code_CODE_OK { + return nil, lsUserSharesResponse.Status, err + } + + // build OCS response payload + for _, s := range lsUserSharesResponse.Shares { + data, err := conversions.UserShare2ShareData(ctx, s) + if err != nil { + log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not UserShare2ShareData, skipping") + continue + } + + // prepare the stat request + statReq := &provider.StatRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Id{Id: s.ResourceId}, + }, + } + + statResponse, err := c.Stat(ctx, statReq) + if err != nil || statResponse.Status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", s).Interface("response", statResponse).Interface("shareData", data).Err(err).Msg("could not stat share, skipping") + continue + } + + if err := h.addFileInfo(ctx, data, statResponse.Info); err != nil { + log.Debug().Interface("share", s).Interface("info", statResponse.Info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") + continue + } + h.addDisplaynames(ctx, c, data) + + log.Debug().Interface("share", s).Interface("info", rInfo).Interface("shareData", data).Msg("mapped") + ocsDataPayload = append(ocsDataPayload, data) + } + } + + return ocsDataPayload, nil, nil +}