Skip to content

Commit 346f9b3

Browse files
committed
ocm share notification handling added
1 parent 2a7145a commit 346f9b3

File tree

15 files changed

+370
-24
lines changed

15 files changed

+370
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enhancement: Add the ocm notification handler
2+
3+
Added the ocm notification handler that allows receiving a notification from a remote party about changes to a previously known entity.
4+
5+
https://github.com/cs3org/reva/pull/5075

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/eventials/go-tus v0.0.0-20220610120217-05d0564bb571
2525
github.com/gdexlab/go-render v1.0.1
2626
github.com/go-chi/chi/v5 v5.1.0
27+
github.com/go-chi/render v1.0.3
2728
github.com/go-ldap/ldap/v3 v3.4.8
2829
github.com/go-micro/plugins/v4/events/natsjs v1.2.2
2930
github.com/go-micro/plugins/v4/server/http v1.2.2
@@ -109,6 +110,7 @@ require (
109110
github.com/Masterminds/semver v1.5.0 // indirect
110111
github.com/Microsoft/go-winio v0.6.2 // indirect
111112
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
113+
github.com/ajg/form v1.5.1 // indirect
112114
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
113115
github.com/beorn7/perks v1.0.1 // indirect
114116
github.com/bitly/go-simplejson v0.5.0 // indirect

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
6161
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
6262
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
6363
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
64+
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
65+
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
6466
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6567
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6668
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -181,6 +183,8 @@ github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD
181183
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
182184
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
183185
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
186+
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
187+
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
184188
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
185189
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
186190
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=

internal/grpc/services/gateway/ocmcore.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (s *svc) DeleteOCMCoreShare(ctx context.Context, req *ocmcore.DeleteOCMCore
6969

7070
res, err := c.DeleteOCMCoreShare(ctx, req)
7171
if err != nil {
72-
return nil, errors.Wrap(err, "gateway: error calling UpdateOCMCoreShare")
72+
return nil, errors.Wrap(err, "gateway: error calling DeleteOCMCoreShare")
7373
}
7474

7575
return res, nil

internal/grpc/services/ocmcore/ocmcore.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,22 @@ package ocmcore
2020

2121
import (
2222
"context"
23+
"errors"
2324
"fmt"
2425
"time"
2526

27+
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
2628
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
2729
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
2830
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
2931
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
3032
"github.com/cs3org/reva/v2/pkg/errtypes"
3133
"github.com/cs3org/reva/v2/pkg/ocm/share"
3234
"github.com/cs3org/reva/v2/pkg/ocm/share/repository/registry"
35+
ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user"
3336
"github.com/cs3org/reva/v2/pkg/rgrpc"
3437
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
38+
"github.com/cs3org/reva/v2/pkg/utils"
3539
"github.com/cs3org/reva/v2/pkg/utils/cfg"
3640
"github.com/rs/zerolog"
3741
"google.golang.org/grpc"
@@ -93,7 +97,11 @@ func (s *service) Close() error {
9397
}
9498

9599
func (s *service) UnprotectedEndpoints() []string {
96-
return []string{"/cs3.ocm.core.v1beta1.OcmCoreAPI/CreateOCMCoreShare"}
100+
return []string{
101+
ocmcore.OcmCoreAPI_CreateOCMCoreShare_FullMethodName,
102+
ocmcore.OcmCoreAPI_UpdateOCMCoreShare_FullMethodName,
103+
ocmcore.OcmCoreAPI_DeleteOCMCoreShare_FullMethodName,
104+
}
97105
}
98106

99107
// CreateOCMCoreShare is called when an OCM request comes into this reva instance from.
@@ -144,5 +152,30 @@ func (s *service) UpdateOCMCoreShare(ctx context.Context, req *ocmcore.UpdateOCM
144152
}
145153

146154
func (s *service) DeleteOCMCoreShare(ctx context.Context, req *ocmcore.DeleteOCMCoreShareRequest) (*ocmcore.DeleteOCMCoreShareResponse, error) {
147-
return nil, errtypes.NotSupported("not implemented")
155+
grantee := utils.ReadPlainFromOpaque(req.GetOpaque(), "grantee")
156+
if grantee == "" {
157+
return nil, errtypes.UserRequired("missing remote user id in a metadata")
158+
}
159+
160+
user := &userpb.User{Id: ocmuser.RemoteID(&userpb.UserId{OpaqueId: grantee})}
161+
162+
err := s.repo.DeleteReceivedShare(ctx, user, &ocm.ShareReference{
163+
Spec: &ocm.ShareReference_Id{
164+
Id: &ocm.ShareId{
165+
OpaqueId: req.GetId(),
166+
},
167+
},
168+
})
169+
res := &ocmcore.DeleteOCMCoreShareResponse{}
170+
if err == nil {
171+
res.Status = status.NewOK(ctx)
172+
} else {
173+
var notFound errtypes.NotFound
174+
if errors.As(err, &notFound) {
175+
res.Status = status.NewNotFound(ctx, "remote ocm share not found")
176+
} else {
177+
res.Status = status.NewInternal(ctx, "error deleting remote ocm share")
178+
}
179+
}
180+
return res, nil
148181
}

internal/grpc/services/ocmshareprovider/ocmshareprovider.go

+61-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
3939
"github.com/cs3org/reva/v2/pkg/errtypes"
4040
"github.com/cs3org/reva/v2/pkg/ocm/client"
41+
"github.com/cs3org/reva/v2/pkg/ocm/payload"
4142
"github.com/cs3org/reva/v2/pkg/ocm/share"
4243
"github.com/cs3org/reva/v2/pkg/ocm/share/repository/registry"
4344
ocmuser "github.com/cs3org/reva/v2/pkg/ocm/user"
@@ -379,20 +380,77 @@ func (s *service) CreateOCMShare(ctx context.Context, req *ocm.CreateOCMShareReq
379380
}
380381

381382
func (s *service) RemoveOCMShare(ctx context.Context, req *ocm.RemoveOCMShareRequest) (*ocm.RemoveOCMShareResponse, error) {
382-
// TODO (gdelmont): notify the remote provider using the /notification ocm endpoint
383-
// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post
384383
user := ctxpkg.ContextMustGetUser(ctx)
384+
getShareRes, err := s.GetOCMShare(ctx, &ocm.GetOCMShareRequest{
385+
Ref: req.Ref,
386+
})
387+
if err != nil {
388+
return &ocm.RemoveOCMShareResponse{
389+
Status: status.NewInternal(ctx, "error getting ocm share"),
390+
}, nil
391+
}
392+
if getShareRes.Status.Code != rpc.Code_CODE_OK {
393+
res := &ocm.RemoveOCMShareResponse{
394+
Status: getShareRes.GetStatus(),
395+
}
396+
return res, nil
397+
}
398+
385399
if err := s.repo.DeleteShare(ctx, user, req.Ref); err != nil {
386400
if errors.Is(err, share.ErrShareNotFound) {
387401
return &ocm.RemoveOCMShareResponse{
388402
Status: status.NewNotFound(ctx, "share does not exist"),
389403
}, nil
390404
}
391405
return &ocm.RemoveOCMShareResponse{
392-
Status: status.NewInternal(ctx, "error removing share"),
406+
Status: status.NewInternal(ctx, "error deleting share"),
393407
}, nil
394408
}
395409

410+
// TODO: We should not fail the whole operation if the notification fails
411+
gatewayClient, err := s.gatewaySelector.Next()
412+
if err != nil {
413+
return &ocm.RemoveOCMShareResponse{
414+
Status: status.NewInternal(ctx, "error getting gateway client"),
415+
}, nil
416+
}
417+
418+
providerInfoResp, err := gatewayClient.GetInfoByDomain(ctx, &ocmprovider.GetInfoByDomainRequest{
419+
Domain: getShareRes.GetShare().GetGrantee().GetUserId().GetIdp(),
420+
})
421+
if err != nil {
422+
return &ocm.RemoveOCMShareResponse{
423+
Status: status.NewInternal(ctx, "error getting provider info"),
424+
}, nil
425+
}
426+
427+
if providerInfoResp.Status.Code != rpc.Code_CODE_OK {
428+
return &ocm.RemoveOCMShareResponse{
429+
Status: providerInfoResp.Status,
430+
}, nil
431+
}
432+
433+
ocmEndpoint, err := getOCMEndpoint(providerInfoResp.GetProviderInfo())
434+
if err != nil {
435+
return &ocm.RemoveOCMShareResponse{
436+
Status: status.NewInternal(ctx, "the selected provider does not have an OCM endpoint"),
437+
}, nil
438+
}
439+
newShareReq := &payload.NotificationRequest{
440+
NotificationType: payload.SHARE_UNSHARED,
441+
ResourceType: "file", // use type "file" for shared files or folders
442+
ProviderId: getShareRes.GetShare().GetId().GetOpaqueId(),
443+
Notification: &payload.Notification{
444+
Grantee: getShareRes.GetShare().GetGrantee().GetUserId().GetOpaqueId(),
445+
},
446+
}
447+
// https://cs3org.github.io/OCM-API/docs.html?branch=develop&repo=OCM-API&user=cs3org#/paths/~1notifications/post
448+
err = s.client.NotifyRemote(ctx, ocmEndpoint, newShareReq)
449+
if err != nil {
450+
// Continue even if the notification fails
451+
appctx.GetLogger(ctx).Err(err).Msg("error notifying ocm remote provider")
452+
}
453+
396454
return &ocm.RemoveOCMShareResponse{
397455
Status: status.NewOK(ctx),
398456
}, nil

internal/http/services/ocmd/notifications.go

+102-11
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,36 @@
1919
package ocmd
2020

2121
import (
22-
"io"
22+
"context"
23+
"encoding/json"
24+
"fmt"
2325
"mime"
2426
"net/http"
2527

26-
"github.com/cs3org/reva/v2/internal/http/services/reqres"
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"
2732
"github.com/cs3org/reva/v2/pkg/appctx"
33+
"github.com/cs3org/reva/v2/pkg/ocm/payload"
34+
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
35+
"github.com/cs3org/reva/v2/pkg/utils"
36+
"github.com/go-chi/render"
2837
)
2938

3039
// var validate = validator.New()
3140

3241
type notifHandler struct {
42+
gatewaySelector *pool.Selector[gateway.GatewayAPIClient]
3343
}
3444

3545
func (h *notifHandler) init(c *config) error {
46+
gatewaySelector, err := pool.GatewaySelector(c.GatewaySvc)
47+
if err != nil {
48+
return err
49+
}
50+
h.gatewaySelector = gatewaySelector
51+
3652
return nil
3753
}
3854

@@ -42,25 +58,100 @@ func (h *notifHandler) init(c *config) error {
4258
func (h *notifHandler) Notifications(w http.ResponseWriter, r *http.Request) {
4359
ctx := r.Context()
4460
log := appctx.GetLogger(ctx)
45-
req, err := getNotification(r)
61+
req, err := getNotification(w, r)
4662
if err != nil {
47-
reqres.WriteError(w, r, reqres.APIErrorInvalidParameter, err.Error(), nil)
63+
renderErrorBadRequest(w, r, http.StatusBadRequest, err.Error())
4864
return
4965
}
5066

5167
// TODO(lopresti) this is all to be implemented. For now we just log what we got
5268
log.Debug().Msgf("Received OCM notification: %+v", req)
5369

54-
// this is to please Nextcloud
55-
w.WriteHeader(http.StatusCreated)
70+
var status *rpc.Status
71+
switch req.NotificationType {
72+
case payload.SHARE_UNSHARED:
73+
if req.Notification.Grantee == "" {
74+
renderErrorBadRequest(w, r, http.StatusBadRequest, "grantee is required")
75+
}
76+
status, err = h.handleShareUnshared(ctx, req)
77+
if err != nil {
78+
log.Err(err).Any("NotificationRequest", req).Msg("error getting gateway client")
79+
renderErrorBadRequest(w, r, http.StatusInternalServerError, status.GetMessage())
80+
}
81+
case payload.SHARE_CHANGE_PERMISSION:
82+
// TODO implement the SHARE_CHANGE_PERMISSION
83+
w.WriteHeader(http.StatusNotImplemented)
84+
return
85+
default:
86+
renderErrorBadRequest(w, r, http.StatusBadRequest, "NotificationType "+req.NotificationType+" is not supported")
87+
return
88+
}
89+
// parse the response status
90+
switch status.GetCode() {
91+
case rpc.Code_CODE_OK:
92+
w.WriteHeader(http.StatusCreated)
93+
return
94+
case rpc.Code_CODE_INVALID_ARGUMENT:
95+
renderErrorBadRequest(w, r, http.StatusBadRequest, status.GetMessage())
96+
return
97+
case rpc.Code_CODE_UNAUTHENTICATED:
98+
w.WriteHeader(http.StatusUnauthorized)
99+
return
100+
case rpc.Code_CODE_PERMISSION_DENIED:
101+
w.WriteHeader(http.StatusForbidden)
102+
return
103+
default:
104+
log.Error().Str("code", status.GetCode().String()).Str("message", status.GetMessage()).Str("NotificationType", req.NotificationType).Msg("error handling notification")
105+
w.WriteHeader(http.StatusInternalServerError)
106+
}
107+
}
108+
109+
func (h *notifHandler) handleShareUnshared(ctx context.Context, req *payload.NotificationRequest) (*rpc.Status, error) {
110+
gatewayClient, err := h.gatewaySelector.Next()
111+
if err != nil {
112+
return nil, fmt.Errorf("error getting gateway client: %w", err)
113+
}
114+
115+
o := &typesv1beta1.Opaque{}
116+
utils.AppendPlainToOpaque(o, "grantee", req.Notification.Grantee)
117+
118+
res, err := gatewayClient.DeleteOCMCoreShare(ctx, &ocmcore.DeleteOCMCoreShareRequest{
119+
Id: req.ProviderId,
120+
Opaque: o,
121+
})
122+
if err != nil {
123+
return nil, fmt.Errorf("error calling DeleteOCMCoreShare: %w", err)
124+
}
125+
return res.GetStatus(), nil
56126
}
57127

58-
func getNotification(r *http.Request) (string, error) {
59-
// var req notificationRequest
128+
func getNotification(w http.ResponseWriter, r *http.Request) (*payload.NotificationRequest, error) {
60129
contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
61130
if err == nil && contentType == "application/json" {
62-
bytes, _ := io.ReadAll(r.Body)
63-
return string(bytes), nil
131+
n := &payload.NotificationRequest{}
132+
err := json.NewDecoder(r.Body).Decode(&n)
133+
if err != nil {
134+
return nil, err
135+
}
136+
return n, nil
137+
}
138+
return nil, err
139+
}
140+
141+
func renderJSON(w http.ResponseWriter, r *http.Request, statusCode int, resp any) {
142+
render.Status(r, statusCode)
143+
render.JSON(w, r, resp)
144+
}
145+
146+
func renderErrorBadRequest(w http.ResponseWriter, r *http.Request, statusCode int, message string) {
147+
resp := &payload.ErrorMessageResponse{
148+
Message: "BAD_REQUEST",
149+
ValidationErrors: []*payload.ValidationError{
150+
{
151+
Name: "Notification",
152+
Message: message,
153+
},
154+
},
64155
}
65-
return "", nil
156+
renderJSON(w, r, http.StatusBadRequest, resp)
66157
}

internal/http/services/ocmd/shares.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,13 @@ import (
2727
"strings"
2828

2929
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
30-
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
31-
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
32-
3330
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
3431
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
3532
ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"
36-
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
37-
3833
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"
3937
"github.com/cs3org/reva/v2/internal/http/services/reqres"
4038
"github.com/cs3org/reva/v2/pkg/appctx"
4139
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
@@ -78,7 +76,7 @@ type createShareRequest struct {
7876
Protocols Protocols `json:"protocol" validate:"required"`
7977
}
8078

81-
// CreateShare sends all the informations to the consumer needed to start
79+
// CreateShare sends all the information to the consumer needed to start
8280
// synchronization between the two services.
8381
func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
8482
ctx := r.Context()
@@ -180,7 +178,7 @@ func (h *sharesHandler) CreateShare(w http.ResponseWriter, r *http.Request) {
180178
return
181179
}
182180

183-
if userRes.Status.Code != rpc.Code_CODE_OK {
181+
if createShareResp.Status.Code != rpc.Code_CODE_OK {
184182
// TODO: define errors in the cs3apis
185183
reqres.WriteError(w, r, reqres.APIErrorServerError, "error creating ocm share", errors.New(createShareResp.Status.Message))
186184
return

0 commit comments

Comments
 (0)