github.com/cs3org/reva/v2@v2.27.7/internal/http/services/ocmd/notifications.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 "context" 23 "encoding/json" 24 "fmt" 25 "mime" 26 "net/http" 27 28 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 29 ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" 30 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 31 typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 32 "github.com/cs3org/reva/v2/pkg/appctx" 33 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 34 "github.com/cs3org/reva/v2/pkg/utils" 35 "github.com/go-chi/render" 36 ) 37 38 const ( 39 SHARE_UNSHARED = "SHARE_UNSHARED" 40 SHARE_CHANGE_PERMISSION = "SHARE_CHANGE_PERMISSION" 41 ) 42 43 // var validate = validator.New() 44 45 type notifHandler struct { 46 gatewaySelector *pool.Selector[gateway.GatewayAPIClient] 47 } 48 49 func (h *notifHandler) init(c *config) error { 50 gatewaySelector, err := pool.GatewaySelector(c.GatewaySvc) 51 if err != nil { 52 return err 53 } 54 h.gatewaySelector = gatewaySelector 55 56 return nil 57 } 58 59 // https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post 60 type notificationRequest struct { 61 NotificationType string `json:"notificationType" validate:"required"` 62 ResourceType string `json:"resourceType" validate:"required"` 63 // Identifier to identify the shared resource at the provider side. This is unique per provider such that if the same resource is shared twice, this providerId will not be repeated. 64 ProviderId string `json:"providerId" validate:"required"` 65 // Optional additional parameters, depending on the notification and the resource type. 66 Notification *notification `json:"notification,omitempty"` 67 } 68 69 type notification struct { 70 Owner string `json:"owner,omitempty"` 71 Grantee string `json:"grantee,omitempty"` 72 SharedSecret string `json:"sharedSecret,omitempty"` 73 Expiration uint64 `json:"expiration,omitempty"` 74 Protocols Protocols `json:"protocol,omitempty" validate:"required"` 75 } 76 77 // ErrorMessageResponse is the response returned by the OCM API in case of an error. 78 // https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post 79 type ErrorMessageResponse struct { 80 Message string `json:"message"` 81 ValidationErrors []*ValidationError `json:"validationErrors,omitempty"` 82 } 83 84 // ValidationError is the payload for the validationErrors field in the ErrorMessageResponse. 85 type ValidationError struct { 86 Name string `json:"name"` 87 Message string `json:"message"` 88 } 89 90 // Notifications dispatches any notifications received from remote OCM sites 91 // according to the specifications at: 92 // https://cs3org.github.io/OCM-API/docs.html?branch=v1.1.0&repo=OCM-API&user=cs3org#/paths/~1notifications/post 93 func (h *notifHandler) Notifications(w http.ResponseWriter, r *http.Request) { 94 ctx := r.Context() 95 log := appctx.GetLogger(ctx) 96 req, err := getNotification(r) 97 if err != nil { 98 renderErrorBadRequest(w, r, http.StatusBadRequest, err.Error()) 99 return 100 } 101 102 // TODO(lopresti) this is all to be implemented. For now we just log what we got 103 log.Debug().Msgf("Received OCM notification: %+v", req) 104 105 var status *rpc.Status 106 if req.Notification.Grantee == "" { 107 renderErrorBadRequest(w, r, http.StatusBadRequest, "grantee is required") 108 } 109 switch req.NotificationType { 110 case SHARE_UNSHARED: 111 status, err = h.handleShareUnshared(ctx, req) 112 if err != nil { 113 log.Err(err).Any("notificationRequest", req).Msg("error getting gateway client") 114 renderErrorBadRequest(w, r, http.StatusInternalServerError, status.GetMessage()) 115 } 116 case SHARE_CHANGE_PERMISSION: 117 status, err = h.handleShareChangePermission(ctx, req) 118 if err != nil { 119 log.Err(err).Any("notificationRequest", req).Msg("error getting gateway client") 120 renderErrorBadRequest(w, r, http.StatusInternalServerError, status.GetMessage()) 121 } 122 default: 123 renderErrorBadRequest(w, r, http.StatusBadRequest, "NotificationType "+req.NotificationType+" is not supported") 124 return 125 } 126 // parse the response status 127 switch status.GetCode() { 128 case rpc.Code_CODE_OK: 129 w.WriteHeader(http.StatusCreated) 130 return 131 case rpc.Code_CODE_INVALID_ARGUMENT: 132 renderErrorBadRequest(w, r, http.StatusBadRequest, status.GetMessage()) 133 return 134 case rpc.Code_CODE_UNAUTHENTICATED: 135 w.WriteHeader(http.StatusUnauthorized) 136 return 137 case rpc.Code_CODE_PERMISSION_DENIED: 138 w.WriteHeader(http.StatusForbidden) 139 return 140 default: 141 log.Error().Str("code", status.GetCode().String()).Str("message", status.GetMessage()).Str("NotificationType", req.NotificationType).Msg("error handling notification") 142 w.WriteHeader(http.StatusInternalServerError) 143 } 144 } 145 146 func (h *notifHandler) handleShareUnshared(ctx context.Context, req *notificationRequest) (*rpc.Status, error) { 147 gatewayClient, err := h.gatewaySelector.Next() 148 if err != nil { 149 return nil, fmt.Errorf("error getting gateway client: %w", err) 150 } 151 152 o := &typesv1beta1.Opaque{} 153 utils.AppendPlainToOpaque(o, "grantee", req.Notification.Grantee) 154 155 res, err := gatewayClient.DeleteOCMCoreShare(ctx, &ocmcore.DeleteOCMCoreShareRequest{ 156 Id: req.ProviderId, 157 Opaque: o, 158 }) 159 if err != nil { 160 return nil, fmt.Errorf("error calling DeleteOCMCoreShare: %w", err) 161 } 162 return res.GetStatus(), nil 163 } 164 165 func (h *notifHandler) handleShareChangePermission(ctx context.Context, req *notificationRequest) (*rpc.Status, error) { 166 gatewayClient, err := h.gatewaySelector.Next() 167 if err != nil { 168 return nil, fmt.Errorf("error getting gateway client: %w", err) 169 } 170 171 if req.Notification == nil || req.Notification.Protocols == nil { 172 return nil, fmt.Errorf("error getting protocols from notification") 173 } 174 175 o := &typesv1beta1.Opaque{} 176 utils.AppendPlainToOpaque(o, "grantee", req.Notification.Grantee) 177 178 res, err := gatewayClient.UpdateOCMCoreShare(ctx, &ocmcore.UpdateOCMCoreShareRequest{ 179 OcmShareId: req.ProviderId, 180 Protocols: getProtocols(req.Notification.Protocols), 181 Opaque: o, 182 }) 183 if err != nil { 184 return nil, fmt.Errorf("error calling DeleteOCMCoreShare: %w", err) 185 } 186 return res.GetStatus(), nil 187 } 188 189 func getNotification(r *http.Request) (*notificationRequest, error) { 190 contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 191 if err == nil && contentType == "application/json" { 192 n := ¬ificationRequest{} 193 err := json.NewDecoder(r.Body).Decode(&n) 194 if err != nil { 195 return nil, err 196 } 197 return n, nil 198 } 199 return nil, err 200 } 201 202 func renderJSON(w http.ResponseWriter, r *http.Request, statusCode int, resp any) { 203 render.Status(r, statusCode) 204 render.JSON(w, r, resp) 205 } 206 207 func renderErrorBadRequest(w http.ResponseWriter, r *http.Request, statusCode int, message string) { 208 resp := &ErrorMessageResponse{ 209 Message: "BAD_REQUEST", 210 ValidationErrors: []*ValidationError{ 211 { 212 Name: "notification", 213 Message: message, 214 }, 215 }, 216 } 217 renderJSON(w, r, http.StatusBadRequest, resp) 218 }