github.com/cs3org/reva/v2@v2.27.7/internal/http/services/ocmd/shares.go (about) 1 // Copyright 2018-2023 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package ocmd 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "mime" 26 "net/http" 27 "strings" 28 29 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 30 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 31 ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" 32 ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" 33 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 34 ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" 35 providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 36 types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 37 "github.com/cs3org/reva/v2/internal/http/services/reqres" 38 "github.com/cs3org/reva/v2/pkg/appctx" 39 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 40 "github.com/cs3org/reva/v2/pkg/utils" 41 "github.com/go-playground/validator/v10" 42 ) 43 44 var validate = validator.New() 45 46 type sharesHandler struct { 47 gatewaySelector *pool.Selector[gateway.GatewayAPIClient] 48 exposeRecipientDisplayName bool 49 } 50 51 func (h *sharesHandler) init(c *config) error { 52 var err error 53 54 gatewaySelector, err := pool.GatewaySelector(c.GatewaySvc) 55 if err != nil { 56 return err 57 } 58 h.gatewaySelector = gatewaySelector 59 60 h.exposeRecipientDisplayName = c.ExposeRecipientDisplayName 61 return nil 62 } 63 64 type createShareRequest struct { 65 ShareWith string `json:"shareWith" validate:"required"` // identifier of the recipient of the share 66 Name string `json:"name" validate:"required"` // name of the resource 67 Description string `json:"description"` // (optional) description of the resource 68 ProviderID string `json:"providerId" validate:"required"` // unique identifier of the resource at provider side 69 Owner string `json:"owner" validate:"required"` // unique identifier of the owner at provider side 70 Sender string `json:"sender" validate:"required"` // unique indentifier of the user who wants to share the resource at provider side 71 OwnerDisplayName string `json:"ownerDisplayName"` // display name of the owner of the resource 72 SenderDisplayName string `json:"senderDisplayName"` // dispay name of the user who wants to share the resource 73 ShareType string `json:"shareType" validate:"required,oneof=user group"` // recipient share type (user or group) 74 ResourceType string `json:"resourceType" validate:"required,oneof=file folder"` 75 Expiration uint64 `json:"expiration"` 76 Protocols Protocols `json:"protocol" validate:"required"` 77 } 78 79 // CreateShare sends all the information to the consumer needed to start 80 // synchronization between the two services. 81 func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) { 82 ctx := r.Context() 83 log := appctx.GetLogger(ctx) 84 req, err := getCreateShareRequest(r) 85 if err != nil { 86 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil) 87 return 88 } 89 90 _, meshProvider, err := getIDAndMeshProvider(req.Sender) 91 log.Debug().Msgf("Determined Mesh Provider '%s' from req.Sender '%s'", meshProvider, req.Sender) 92 if err != nil { 93 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil) 94 return 95 } 96 97 clientIP, err := utils.GetClientIP(r) 98 if err != nil { 99 reqres.WriteError(w, r, reqres.APIErrorServerError, fmt.Sprintf("error retrieving client IP from request: %s", r.RemoteAddr), err) 100 return 101 } 102 providerInfo := ocmprovider.ProviderInfo{ 103 Domain: meshProvider, 104 Services: []*ocmprovider.Service{ 105 { 106 Host: clientIP, 107 }, 108 }, 109 } 110 gatewayClient, err := h.gatewaySelector.Next() 111 if err != nil { 112 reqres.WriteError(w, r, reqres.APIErrorServerError, "error getting gateway client", err) 113 return 114 } 115 providerAllowedResp, err := gatewayClient.IsProviderAllowed(ctx, &ocmprovider.IsProviderAllowedRequest{ 116 Provider: &providerInfo, 117 }) 118 if err != nil { 119 reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending a grpc is provider allowed request", err) 120 return 121 } 122 if providerAllowedResp.Status.Code != rpc.Code_CODE_OK { 123 reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "provider not authorized", errors.New(providerAllowedResp.Status.Message)) 124 return 125 } 126 127 shareWith, _, err := getIDAndMeshProvider(req.ShareWith) 128 if err != nil { 129 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil) 130 return 131 } 132 133 userRes, err := gatewayClient.GetUser(ctx, &userpb.GetUserRequest{ 134 UserId: &userpb.UserId{OpaqueId: shareWith}, SkipFetchingUserGroups: true, 135 }) 136 if err != nil { 137 reqres.WriteError(w, r, reqres.APIErrorServerError, "error searching recipient", err) 138 return 139 } 140 if userRes.Status.Code != rpc.Code_CODE_OK { 141 reqres.WriteError(w, r, reqres.APIErrorNotFound, "user not found", errors.New(userRes.Status.Message)) 142 return 143 } 144 145 owner, err := getUserIDFromOCMUser(req.Owner) 146 if err != nil { 147 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil) 148 return 149 } 150 151 sender, err := getUserIDFromOCMUser(req.Sender) 152 if err != nil { 153 reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil) 154 return 155 } 156 157 createShareReq := &ocmcore.CreateOCMCoreShareRequest{ 158 Description: req.Description, 159 Name: req.Name, 160 ResourceId: req.ProviderID, 161 Owner: owner, 162 Sender: sender, 163 ShareWith: userRes.User.Id, 164 ResourceType: getResourceTypeFromOCMRequest(req.ResourceType), 165 ShareType: getOCMShareType(req.ShareType), 166 Protocols: getProtocols(req.Protocols), 167 } 168 169 if req.Expiration != 0 { 170 createShareReq.Expiration = &types.Timestamp{ 171 Seconds: req.Expiration, 172 } 173 } 174 175 createShareResp, err := gatewayClient.CreateOCMCoreShare(ctx, createShareReq) 176 if err != nil { 177 reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", err) 178 return 179 } 180 181 if createShareResp.Status.Code != rpc.Code_CODE_OK { 182 // TODO: define errors in the cs3apis 183 reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", errors.New(createShareResp.Status.Message)) 184 return 185 } 186 187 response := map[string]any{} 188 189 if h.exposeRecipientDisplayName { 190 response["recipientDisplayName"] = userRes.User.DisplayName 191 } 192 193 _ = json.NewEncoder(w).Encode(response) 194 w.WriteHeader(http.StatusCreated) 195 } 196 197 func getUserIDFromOCMUser(user string) (*userpb.UserId, error) { 198 id, idp, err := getIDAndMeshProvider(user) 199 if err != nil { 200 return nil, err 201 } 202 return &userpb.UserId{ 203 OpaqueId: id, 204 Idp: idp, 205 // the remote user is a federated account for the local reva 206 Type: userpb.UserType_USER_TYPE_FEDERATED, 207 }, nil 208 } 209 210 func getIDAndMeshProvider(user string) (string, string, error) { 211 // the user is in the form of dimitri@apiwise.nl 212 split := strings.Split(user, "@") 213 if len(split) < 2 { 214 return "", "", errors.New("not in the form <id>@<provider>") 215 } 216 return strings.Join(split[:len(split)-1], "@"), split[len(split)-1], nil 217 } 218 219 func getCreateShareRequest(r *http.Request) (*createShareRequest, error) { 220 var req createShareRequest 221 contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 222 if err == nil && contentType == "application/json" { 223 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 224 return nil, err 225 } 226 } else { 227 return nil, errors.New("body request not recognised") 228 } 229 // validate the request 230 if err := validate.Struct(req); err != nil { 231 return nil, err 232 } 233 return &req, nil 234 } 235 236 func getResourceTypeFromOCMRequest(t string) providerpb.ResourceType { 237 switch t { 238 case "file": 239 return providerpb.ResourceType_RESOURCE_TYPE_FILE 240 case "folder": 241 return providerpb.ResourceType_RESOURCE_TYPE_CONTAINER 242 default: 243 return providerpb.ResourceType_RESOURCE_TYPE_INVALID 244 } 245 } 246 247 func getOCMShareType(t string) ocm.ShareType { 248 if t == "user" { 249 return ocm.ShareType_SHARE_TYPE_USER 250 } 251 return ocm.ShareType_SHARE_TYPE_GROUP 252 } 253 254 func getProtocols(p Protocols) []*ocm.Protocol { 255 prot := make([]*ocm.Protocol, 0, len(p)) 256 for _, data := range p { 257 prot = append(prot, data.ToOCMProtocol()) 258 } 259 return prot 260 }