1
0
mirror of https://github.com/owncloud/ocis.git synced 2025-04-18 23:44:07 +03:00

fix: ocm share notifications

eventsmiddleware OCMCoreShareCreated intercepted in ocis

wip: ocm share removed event

feat: userlog, ocm share handling

feat: formatted user notifications

feat: changelog

feat: resolve username of the sharer

fix: revert event field name change

feat: event config for reva omcore

fix: remove config for request interception

fix: getOCMUser

fix: config cleanup

feat: bump reva version

fix: go vendor

fix: remove unused
This commit is contained in:
Michal Klos 2025-03-21 10:53:30 +01:00
parent 64abd624a9
commit ba7987ff5b
10 changed files with 223 additions and 14 deletions

View File

@ -0,0 +1,6 @@
Bugfix: OCM Share Notifications
Fix no OCM sharing notifications, now share and unshare notifications are created
https://github.com/owncloud/ocis/pull/11162
https://github.com/owncloud/ocis/issues/11042

2
go.mod
View File

@ -67,7 +67,7 @@ require (
github.com/open-policy-agent/opa v0.70.0
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27
github.com/owncloud/reva/v2 v2.0.0-20250313103212-d8b4c329554a
github.com/owncloud/reva/v2 v2.0.0-20250331084351-00f64db78848
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.10
github.com/prometheus/client_golang v1.20.5

4
go.sum
View File

@ -881,8 +881,8 @@ github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CF
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27 h1:ID8s5lGBntmrlI6TbDAjTzRyHucn3bVM2wlW+HBplv4=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27/go.mod h1:+gT+x62AS9u2Farh9wE2uYmgdvTg0MQgsSI62D+xoRg=
github.com/owncloud/reva/v2 v2.0.0-20250313103212-d8b4c329554a h1:90QCmxi6n/GkcYbm+zw7Y4HrLcLzqN/ipFfNCOLw3TA=
github.com/owncloud/reva/v2 v2.0.0-20250313103212-d8b4c329554a/go.mod h1:1QUFTq8Q2tjzwY3g+Y1dHIO4tPYqTovV6ScacW3sTPs=
github.com/owncloud/reva/v2 v2.0.0-20250331084351-00f64db78848 h1:eWOevrc619bmhJwV9tzT3Ak1oFU9Nx9gufLFiCETN9Q=
github.com/owncloud/reva/v2 v2.0.0-20250331084351-00f64db78848/go.mod h1:1QUFTq8Q2tjzwY3g+Y1dHIO4tPYqTovV6ScacW3sTPs=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pablodz/inotifywaitgo v0.0.7 h1:1ii49dGBnRn0t1Sz7RGZS6/NberPEDQprwKHN49Bv6U=

View File

@ -168,6 +168,14 @@ func OCMConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]inter
"file": cfg.OCMCore.Drivers.JSON.File,
},
},
"events": map[string]interface{}{
"natsaddress": cfg.Events.Endpoint,
"natsclusterid": cfg.Events.Cluster,
"tlsinsecure": cfg.Events.TLSInsecure,
"tlsrootcacertificate": cfg.Events.TLSRootCACertificate,
"authusername": cfg.Events.AuthUsername,
"authpassword": cfg.Events.AuthPassword,
},
},
"storageprovider": map[string]interface{}{
"driver": "ocmreceived",

View File

@ -43,6 +43,8 @@ var _registeredEvents = []events.Unmarshaller{
events.ShareCreated{},
events.ShareRemoved{},
events.ShareExpired{},
events.OCMCoreShareCreated{},
events.OCMCoreShareDelete{},
}
// Server is the entrypoint for the server command.

View File

@ -12,6 +12,9 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
@ -28,7 +31,6 @@ var (
_resourceTypeResource = "resource"
_resourceTypeSpace = "storagespace"
_resourceTypeShare = "share"
_resourceTypeGlobal = "global"
_domain = "userlog"
)
@ -115,6 +117,10 @@ func (c *Converter) ConvertEvent(eventid string, event interface{}) (OC10Notific
return c.shareMessage(eventid, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt)
case events.ShareRemoved:
return c.shareMessage(eventid, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp)
case events.OCMCoreShareCreated:
return c.omcShareCreatedMessage(ev, eventid)
case events.OCMCoreShareDelete:
return c.omcShareDeleteMessage(ev, eventid)
}
}
@ -233,6 +239,95 @@ func (c *Converter) shareMessage(eventid string, nt NotificationTemplate, execut
}, nil
}
func (c *Converter) omcShareCreatedMessage(ev events.OCMCoreShareCreated, eventid string) (OC10Notification, error) {
sharerUser, err := c.getOCMUser(ev.Sharer, ev.GranteeUserID)
if err != nil {
return OC10Notification{}, err
}
shareid := &collaboration.ShareId{OpaqueId: ev.ShareID}
subj, subjraw, msg, msgraw, err := composeMessage(ShareCreated, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
"username": sharerUser.GetDisplayName(),
"resourcename": ev.ResourceName,
})
if err != nil {
return OC10Notification{}, err
}
return OC10Notification{
EventID: eventid,
Service: c.serviceName,
UserName: sharerUser.GetDisplayName(),
Timestamp: utils.TSToTime(ev.CTime).Format(time.RFC3339Nano),
ResourceID: ev.ItemID,
ResourceType: _resourceTypeShare,
Subject: subj,
SubjectRaw: subjraw,
Message: msg,
MessageRaw: msgraw,
MessageDetails: generateDetails(sharerUser, nil, nil, shareid),
}, nil
}
func (c *Converter) omcShareDeleteMessage(ev events.OCMCoreShareDelete, eventid string) (OC10Notification, error) {
sharerUser, err := c.getOCMUser(ev.Sharer, ev.Grantee)
if err != nil {
return OC10Notification{}, err
}
shareid := &collaboration.ShareId{OpaqueId: ev.ShareID}
subj, subjraw, msg, msgraw, err := composeMessage(ShareRemoved, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
"username": sharerUser.GetDisplayName(),
"resourcename": ev.ResourceName,
})
if err != nil {
return OC10Notification{}, err
}
return OC10Notification{
EventID: eventid,
Service: c.serviceName,
UserName: sharerUser.GetDisplayName(),
Timestamp: utils.TSToTime(ev.CTime).Format(time.RFC3339Nano),
ResourceID: ev.ShareID,
ResourceType: _resourceTypeShare,
Subject: subj,
SubjectRaw: subjraw,
Message: msg,
MessageRaw: msgraw,
MessageDetails: generateDetails(sharerUser, nil, nil, shareid),
}, nil
}
func (c *Converter) getOCMUser(sharer *user.UserId, grantee *user.UserId) (*user.User, error) {
gwc, err := c.gatewaySelector.Next()
if err != nil {
return nil, err
}
// Context c.serviceAccountContext will cause the user to be not found.
// GetAcceptedUser() calls getUserFilter() that gets the user from the context
// and use it as 'initiator' parameter in the GetRemoteUser() call.
// The 'initiator' should be ev.Grantee.
granteeJson, _ := json.Marshal(grantee)
rspSharer, err := gwc.GetAcceptedUser(context.Background(), &invitepb.GetAcceptedUserRequest{
RemoteUserId: sharer,
Opaque: &typespb.Opaque{
Map: map[string]*typespb.OpaqueEntry{
"user-filter": {
Decoder: "json",
Value: granteeJson,
},
},
},
})
if err != nil {
return nil, err
}
return rspSharer.GetRemoteUser(), nil
}
func (c *Converter) virusMessage(eventid string, nt NotificationTemplate, executant *user.User, rid *storageprovider.ResourceId, filename string, virus string, ts time.Time) (OC10Notification, error) {
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
"resourcename": filename,

View File

@ -183,8 +183,16 @@ func (ul *UserlogService) processEvent(event events.Event) {
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
case events.ShareExpired:
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
}
// ocmcore share related
case events.OCMCoreShareCreated:
executant = e.Sharer
users = append(users, e.GranteeUserID.GetOpaqueId())
case events.OCMCoreShareDelete:
fmt.Println("### userlog processEvent OCMCoreShareDelete", e.Sharer, e.Grantee)
executant = e.Sharer
users = append(users, e.Grantee.GetOpaqueId())
}
if err != nil {
// TODO: Find out why this errors on ci pipeline
ul.log.Debug().Err(err).Interface("event", event).Msg("error gathering members for event")

View File

@ -28,8 +28,10 @@ import (
ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
providerpb "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/owncloud/reva/v2/pkg/errtypes"
"github.com/owncloud/reva/v2/pkg/events"
"github.com/owncloud/reva/v2/pkg/events/stream"
"github.com/owncloud/reva/v2/pkg/ocm/share"
"github.com/owncloud/reva/v2/pkg/ocm/share/repository/registry"
ocmuser "github.com/owncloud/reva/v2/pkg/ocm/user"
@ -46,14 +48,28 @@ func init() {
rgrpc.Register("ocmcore", New)
}
// EventOptions are the configurable options for events
type EventOptions struct {
Endpoint string `mapstructure:"natsaddress"`
Cluster string `mapstructure:"natsclusterid"`
TLSInsecure bool `mapstructure:"tlsinsecure"`
TLSRootCACertificate string `mapstructure:"tlsrootcacertificate"`
EnableTLS bool `mapstructure:"enabletls"`
AuthUsername string `mapstructure:"authusername"`
AuthPassword string `mapstructure:"authpassword"`
}
type config struct {
Driver string `mapstructure:"driver"`
Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
Events EventOptions `mapstructure:"events"`
}
type service struct {
conf *config
repo share.Repository
conf *config
repo share.Repository
eventStream events.Stream
log *zerolog.Logger
}
func (c *config) ApplyDefaults() {
@ -74,7 +90,7 @@ func getShareRepository(c *config) (share.Repository, error) {
}
// New creates a new ocm core svc.
func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
func New(m map[string]interface{}, ss *grpc.Server, log *zerolog.Logger) (rgrpc.Service, error) {
var c config
if err := cfg.Decode(m, &c); err != nil {
return nil, err
@ -88,6 +104,15 @@ func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Se
service := &service{
conf: &c,
repo: repo,
log: log,
}
if c.Events.Endpoint != "" {
es, err := stream.NatsFromConfig("ocmcore-handler", false, stream.NatsConfig(c.Events))
if err != nil {
return nil, err
}
service.eventStream = es
}
return service, nil
@ -111,7 +136,7 @@ func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCM
return nil, errtypes.NotSupported("share type not supported")
}
now := &typesv1beta1.Timestamp{
now := &typespb.Timestamp{
Seconds: uint64(time.Now().Unix()),
}
@ -141,6 +166,30 @@ func (s *service) CreateOCMCoreShare(ctx context.Context, req *ocmcore.CreateOCM
}, nil
}
var permissions *providerpb.ResourcePermissions
for _, p := range req.GetProtocols() {
if p.GetWebdavOptions() != nil {
permissions = p.GetWebdavOptions().GetPermissions().GetPermissions()
break
}
}
if s.eventStream != nil {
if err := events.Publish(ctx, s.eventStream, events.OCMCoreShareCreated{
ShareID: share.Id.OpaqueId,
Executant: share.Creator,
Sharer: share.Creator,
GranteeUserID: share.Grantee.GetUserId(),
ItemID: share.RemoteShareId,
ResourceName: share.Name,
CTime: share.Ctime,
Permissions: permissions,
}); err != nil {
s.log.Error().Err(err).
Msg("failed to publish the ocmcore share created event")
}
}
return &ocmcore.CreateOCMCoreShareResponse{
Status: status.NewOK(ctx),
Id: share.Id.OpaqueId,
@ -185,18 +234,43 @@ func (s *service) DeleteOCMCoreShare(ctx context.Context, req *ocmcore.DeleteOCM
return nil, errtypes.UserRequired("missing remote user id in a metadata")
}
user := &userpb.User{Id: ocmuser.RemoteID(&userpb.UserId{OpaqueId: grantee})}
err := s.repo.DeleteReceivedShare(ctx, user, &ocm.ShareReference{
share, err := s.repo.GetReceivedShare(ctx, &userpb.User{Id: ocmuser.RemoteID(&userpb.UserId{OpaqueId: grantee})}, &ocm.ShareReference{
Spec: &ocm.ShareReference_Id{
Id: &ocm.ShareId{
OpaqueId: req.GetId(),
},
},
})
if err != nil {
return nil, errtypes.InternalError("unable to get share details")
}
granteeUser := &userpb.User{Id: ocmuser.RemoteID(&userpb.UserId{OpaqueId: grantee})}
err = s.repo.DeleteReceivedShare(ctx, granteeUser, &ocm.ShareReference{
Spec: &ocm.ShareReference_Id{
Id: &ocm.ShareId{
OpaqueId: req.GetId(),
},
},
})
res := &ocmcore.DeleteOCMCoreShareResponse{}
if err == nil {
res.Status = status.NewOK(ctx)
if s.eventStream != nil {
if err := events.Publish(ctx, s.eventStream, events.OCMCoreShareDelete{
ShareID: share.Id.OpaqueId,
Sharer: share.GetOwner(),
Grantee: ocmuser.RemoteID(&userpb.UserId{OpaqueId: grantee}),
ResourceName: share.Name,
CTime: &typespb.Timestamp{Seconds: uint64(time.Now().Unix())},
}); err != nil {
s.log.Error().Err(err).
Msg("failed to publish the ocmcore share deleted event")
}
}
} else {
var notFound errtypes.NotFound
if errors.As(err, &notFound) {

View File

@ -44,3 +44,19 @@ func (OCMCoreShareCreated) Unmarshal(v []byte) (interface{}, error) {
err := json.Unmarshal(v, &e)
return e, err
}
// OCMCoreShareDelete is emitted when an ocm share is requested for delete
type OCMCoreShareDelete struct {
ShareID string
Sharer *user.UserId
Grantee *user.UserId
ResourceName string
CTime *types.Timestamp
}
// Unmarshal to fulfill umarshaller interface
func (OCMCoreShareDelete) Unmarshal(v []byte) (interface{}, error) {
e := OCMCoreShareDelete{}
err := json.Unmarshal(v, &e)
return e, err
}

2
vendor/modules.txt vendored
View File

@ -1219,7 +1219,7 @@ github.com/orcaman/concurrent-map
# github.com/owncloud/libre-graph-api-go v1.0.5-0.20250217093259-fa3804be6c27
## explicit; go 1.18
github.com/owncloud/libre-graph-api-go
# github.com/owncloud/reva/v2 v2.0.0-20250313103212-d8b4c329554a
# github.com/owncloud/reva/v2 v2.0.0-20250331084351-00f64db78848
## explicit; go 1.22.7
github.com/owncloud/reva/v2/cmd/revad/internal/grace
github.com/owncloud/reva/v2/cmd/revad/runtime