mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 20:29:54 -06:00
Merge pull request #5699 from kobergj/PolishUserlogService
Userlog Service Improvements
This commit is contained in:
@@ -21,7 +21,7 @@ COPY ./ /ocis/
|
||||
WORKDIR /ocis/ocis
|
||||
RUN make ci-node-generate
|
||||
|
||||
FROM owncloudci/golang:1.18 as build
|
||||
FROM owncloudci/golang:1.19 as build
|
||||
|
||||
COPY --from=generate /ocis /ocis
|
||||
|
||||
|
||||
5
changelog/unreleased/userlog-improvements.md
Normal file
5
changelog/unreleased/userlog-improvements.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Userlog
|
||||
|
||||
Enhane userlog service with proper api and messages
|
||||
|
||||
https://github.com/owncloud/ocis/pull/5699
|
||||
@@ -267,6 +267,9 @@ func FrontendConfigFromStruct(cfg *config.Config) (map[string]interface{}, error
|
||||
"share_jail": cfg.EnableShareJail,
|
||||
"max_quota": cfg.MaxQuota,
|
||||
},
|
||||
"notifications": map[string]interface{}{
|
||||
"endpoints": []string{"list", "get", "delete"},
|
||||
},
|
||||
},
|
||||
"version": map[string]interface{}{
|
||||
"product": "Infinite Scale",
|
||||
|
||||
@@ -25,36 +25,17 @@ import (
|
||||
|
||||
// all events we care about
|
||||
var _registeredEvents = []events.Unmarshaller{
|
||||
// file related
|
||||
events.UploadReady{},
|
||||
events.ContainerCreated{},
|
||||
events.FileTouched{},
|
||||
events.FileDownloaded{},
|
||||
events.FileVersionRestored{},
|
||||
events.ItemMoved{},
|
||||
events.ItemTrashed{},
|
||||
events.ItemPurged{},
|
||||
events.ItemRestored{},
|
||||
|
||||
// space related
|
||||
events.SpaceCreated{},
|
||||
events.SpaceRenamed{},
|
||||
events.SpaceEnabled{},
|
||||
events.SpaceDisabled{},
|
||||
events.SpaceDeleted{},
|
||||
events.SpaceShared{},
|
||||
events.SpaceUnshared{},
|
||||
events.SpaceUpdated{},
|
||||
events.SpaceMembershipExpired{},
|
||||
|
||||
// share related
|
||||
events.ShareCreated{},
|
||||
// events.ShareRemoved{}, // TODO: ShareRemoved doesn't hold sharee information
|
||||
events.ShareUpdated{},
|
||||
events.ShareRemoved{},
|
||||
events.ShareExpired{},
|
||||
events.LinkCreated{},
|
||||
// events.LinkRemoved{}, // TODO: LinkRemoved doesn't hold sharee information
|
||||
events.LinkUpdated{},
|
||||
}
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
|
||||
249
services/userlog/pkg/service/conversion.go
Normal file
249
services/userlog/pkg/service/conversion.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/cs3org/reva/v2/pkg/storagespace"
|
||||
"github.com/cs3org/reva/v2/pkg/utils"
|
||||
ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0"
|
||||
)
|
||||
|
||||
var (
|
||||
_resourceTypeSpace = "storagespace"
|
||||
_resourceTypeShare = "share"
|
||||
)
|
||||
|
||||
// OC10Notification is the oc10 style representation of an event
|
||||
// some fields are left out for simplicity
|
||||
type OC10Notification struct {
|
||||
EventID string `json:"notification_id"`
|
||||
Service string `json:"app"`
|
||||
UserName string `json:"user"`
|
||||
Timestamp string `json:"datetime"`
|
||||
ResourceID string `json:"object_id"`
|
||||
ResourceType string `json:"object_type"`
|
||||
Subject string `json:"subject"`
|
||||
SubjectRaw string `json:"subjectRich"`
|
||||
Message string `json:"message"`
|
||||
MessageRaw string `json:"messageRich"`
|
||||
MessageDetails map[string]interface{} `json:"messageRichParameters"`
|
||||
}
|
||||
|
||||
// ConvertEvent converts an eventhistory event to an OC10Notification
|
||||
func (ul *UserlogService) ConvertEvent(event *ehmsg.Event) (OC10Notification, error) {
|
||||
etype, ok := ul.registeredEvents[event.Type]
|
||||
if !ok {
|
||||
// this should not happen
|
||||
return OC10Notification{}, errors.New("eventtype not registered")
|
||||
}
|
||||
|
||||
einterface, err := etype.Unmarshal(event.Event)
|
||||
if err != nil {
|
||||
// this shouldn't happen either
|
||||
return OC10Notification{}, errors.New("cant unmarshal event")
|
||||
}
|
||||
|
||||
switch ev := einterface.(type) {
|
||||
default:
|
||||
return OC10Notification{}, errors.New("unknown event type")
|
||||
// space related
|
||||
case events.SpaceDisabled:
|
||||
return ul.spaceMessage(event.Id, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceDeleted:
|
||||
return ul.spaceDeletedMessage(event.Id, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp)
|
||||
case events.SpaceShared:
|
||||
return ul.spaceMessage(event.Id, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceUnshared:
|
||||
return ul.spaceMessage(event.Id, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceMembershipExpired:
|
||||
return ul.spaceMessage(event.Id, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt)
|
||||
|
||||
// share related
|
||||
case events.ShareCreated:
|
||||
return ul.shareMessage(event.Id, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime))
|
||||
case events.ShareExpired:
|
||||
return ul.shareMessage(event.Id, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt)
|
||||
case events.ShareRemoved:
|
||||
return ul.shareMessage(event.Id, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func (ul *UserlogService) spaceDeletedMessage(eventid string, executant *user.UserId, spaceid string, spacename string, ts time.Time) (OC10Notification, error) {
|
||||
_, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := ul.composeMessage(SpaceDeleted, map[string]string{
|
||||
"username": user.GetDisplayName(),
|
||||
"spacename": spacename,
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
details := ul.getDetails(user, nil, nil, nil)
|
||||
details["space"] = map[string]string{
|
||||
"id": spaceid,
|
||||
"name": spacename,
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: ul.cfg.Service.Name,
|
||||
UserName: user.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: spaceid,
|
||||
ResourceType: _resourceTypeSpace,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: details,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) spaceMessage(eventid string, eventname string, executant *user.UserId, spaceid string, ts time.Time) (OC10Notification, error) {
|
||||
ctx, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
space, err := ul.getSpace(ctx, spaceid)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := ul.composeMessage(eventname, map[string]string{
|
||||
"username": user.GetDisplayName(),
|
||||
"spacename": space.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: ul.cfg.Service.Name,
|
||||
UserName: user.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: spaceid,
|
||||
ResourceType: _resourceTypeSpace,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: ul.getDetails(user, space, nil, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) shareMessage(eventid string, eventname string, executant *user.UserId, resourceid *storageprovider.ResourceId, shareid *collaboration.ShareId, ts time.Time) (OC10Notification, error) {
|
||||
ctx, user, err := utils.Impersonate(executant, ul.gwClient, ul.cfg.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
info, err := ul.getResource(ctx, resourceid)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := ul.composeMessage(eventname, map[string]string{
|
||||
"username": user.GetDisplayName(),
|
||||
"resourcename": info.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: ul.cfg.Service.Name,
|
||||
UserName: user.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: storagespace.FormatResourceID(*info.GetId()),
|
||||
ResourceType: _resourceTypeShare,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: ul.getDetails(user, nil, info, shareid),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) composeMessage(eventname string, vars map[string]string) (string, string, string, string, error) {
|
||||
tpl, ok := _templates[eventname]
|
||||
if !ok {
|
||||
return "", "", "", "", errors.New("unknown template name")
|
||||
}
|
||||
|
||||
subject := ul.executeTemplate(tpl.Subject, vars)
|
||||
|
||||
subjectraw := ul.executeTemplate(tpl.Subject, map[string]string{
|
||||
"username": "{user}",
|
||||
"spacename": "{space}",
|
||||
"resourcename": "{resource}",
|
||||
})
|
||||
|
||||
message := ul.executeTemplate(tpl.Message, vars)
|
||||
|
||||
messageraw := ul.executeTemplate(tpl.Message, map[string]string{
|
||||
"username": "{user}",
|
||||
"spacename": "{space}",
|
||||
"resourcename": "{resource}",
|
||||
})
|
||||
|
||||
return subject, subjectraw, message, messageraw, nil
|
||||
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getDetails(user *user.User, space *storageprovider.StorageSpace, item *storageprovider.ResourceInfo, shareid *collaboration.ShareId) map[string]interface{} {
|
||||
details := make(map[string]interface{})
|
||||
|
||||
if user != nil {
|
||||
details["user"] = map[string]string{
|
||||
"id": user.GetId().GetOpaqueId(),
|
||||
"name": user.GetUsername(),
|
||||
"displayname": user.GetDisplayName(),
|
||||
}
|
||||
}
|
||||
|
||||
if space != nil {
|
||||
details["space"] = map[string]string{
|
||||
"id": space.GetId().GetOpaqueId(),
|
||||
"name": space.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
details["resource"] = map[string]string{
|
||||
"id": storagespace.FormatResourceID(*item.GetId()),
|
||||
"name": item.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
if shareid != nil {
|
||||
details["share"] = map[string]string{
|
||||
"id": shareid.GetOpaqueId(),
|
||||
}
|
||||
}
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
func (ul *UserlogService) executeTemplate(tpl *template.Template, vars map[string]string) string {
|
||||
var writer bytes.Buffer
|
||||
if err := tpl.Execute(&writer, vars); err != nil {
|
||||
ul.log.Error().Err(err).Str("templateName", tpl.Name()).Msg("cannot execute template")
|
||||
return ""
|
||||
}
|
||||
|
||||
return writer.String()
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
ehmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/eventhistory/v0"
|
||||
)
|
||||
|
||||
// ServeHTTP fulfills Handler interface
|
||||
@@ -37,7 +30,7 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request
|
||||
|
||||
resp := GetEventResponseOC10{}
|
||||
for _, e := range evs {
|
||||
noti, err := ul.convertEvent(r.Context(), e)
|
||||
noti, err := ul.ConvertEvent(e)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to convert event")
|
||||
continue
|
||||
@@ -60,14 +53,14 @@ func (ul *UserlogService) HandleDeleteEvents(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
var ids []string
|
||||
if err := json.NewDecoder(r.Body).Decode(&ids); err != nil {
|
||||
var req DeleteEventsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusBadRequest).Msg("request body is malformed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ul.DeleteEvents(u.GetId().GetOpaqueId(), ids); err != nil {
|
||||
if err := ul.DeleteEvents(u.GetId().GetOpaqueId(), req.IDs); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("delete events failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@@ -76,140 +69,6 @@ func (ul *UserlogService) HandleDeleteEvents(w http.ResponseWriter, r *http.Requ
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (ul *UserlogService) convertEvent(ctx context.Context, event *ehmsg.Event) (OC10Notification, error) {
|
||||
etype, ok := ul.registeredEvents[event.Type]
|
||||
if !ok {
|
||||
// this should not happen
|
||||
return OC10Notification{}, errors.New("eventtype not registered")
|
||||
}
|
||||
|
||||
einterface, err := etype.Unmarshal(event.Event)
|
||||
if err != nil {
|
||||
// this shouldn't happen either
|
||||
return OC10Notification{}, errors.New("cant unmarshal event")
|
||||
}
|
||||
|
||||
noti := OC10Notification{
|
||||
EventID: event.Id,
|
||||
Service: "userlog",
|
||||
Timestamp: time.Now().Format(time.RFC3339Nano),
|
||||
}
|
||||
|
||||
// TODO: strange bug with getting space -> fix postponed to make master panic-free
|
||||
var space storageprovider.StorageSpace
|
||||
switch ev := einterface.(type) {
|
||||
// file related
|
||||
case events.UploadReady:
|
||||
noti.UserID = ev.ExecutingUser.GetId().GetOpaqueId()
|
||||
noti.Subject = "File uploaded"
|
||||
noti.Message = fmt.Sprintf("File '%s' was uploaded to space '%s' by user '%s'", ev.Filename, space.GetName(), ev.ExecutingUser.GetUsername())
|
||||
case events.ContainerCreated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Folder created"
|
||||
noti.Message = fmt.Sprintf("Folder '%s' was created", ev.Ref.GetPath())
|
||||
case events.FileTouched:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File touched"
|
||||
noti.Message = fmt.Sprintf("File '%s' was touched", ev.Ref.GetPath())
|
||||
case events.FileDownloaded:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File downloaded"
|
||||
noti.Message = fmt.Sprintf("File '%s' was downloaded", ev.Ref.GetPath())
|
||||
case events.FileVersionRestored:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File version restored"
|
||||
noti.Message = fmt.Sprintf("An older version of file '%s' was restored", ev.Ref.GetPath())
|
||||
case events.ItemMoved:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File moved"
|
||||
noti.Message = fmt.Sprintf("File '%s' was moved from '%s'", ev.Ref.GetPath(), ev.OldReference.GetPath())
|
||||
case events.ItemTrashed:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File trashed"
|
||||
noti.Message = fmt.Sprintf("File '%s' was trashed", ev.Ref.GetPath())
|
||||
case events.ItemPurged:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File purged"
|
||||
noti.Message = fmt.Sprintf("File '%s' was purged", ev.Ref.GetPath())
|
||||
case events.ItemRestored:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "File restored"
|
||||
noti.Message = fmt.Sprintf("File '%s' was restored", ev.Ref.GetPath())
|
||||
|
||||
// space related
|
||||
case events.SpaceCreated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space created"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was created", ev.Name)
|
||||
case events.SpaceRenamed:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space renamed"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was renamed", ev.Name)
|
||||
case events.SpaceEnabled:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space enabled"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was renamed", space.Name)
|
||||
case events.SpaceDisabled:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space disabled"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was disabled", space.Name)
|
||||
case events.SpaceDeleted:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space deleted"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was deleted", space.Name)
|
||||
case events.SpaceShared:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space shared"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was shared", space.Name)
|
||||
case events.SpaceUnshared:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space unshared"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was unshared", space.Name)
|
||||
case events.SpaceUpdated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Space updated"
|
||||
noti.Message = fmt.Sprintf("Space '%s' was updated", space.Name)
|
||||
case events.SpaceMembershipExpired:
|
||||
noti.UserID = ""
|
||||
noti.Subject = "Space membership expired"
|
||||
noti.Message = fmt.Sprintf("A spacemembership for space '%s' has expired", space.Name)
|
||||
|
||||
// share related
|
||||
case events.ShareCreated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Share received"
|
||||
noti.Message = fmt.Sprintf("A file was shared in space %s", space.Name)
|
||||
case events.ShareUpdated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Share updated"
|
||||
noti.Message = fmt.Sprintf("A share was updated in space %s", space.Name)
|
||||
case events.ShareExpired:
|
||||
noti.Subject = "Share expired"
|
||||
noti.Message = fmt.Sprintf("A share has expired in space %s", space.Name)
|
||||
case events.LinkCreated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Share received"
|
||||
noti.Message = fmt.Sprintf("A link was created in space %s", space.Name)
|
||||
case events.LinkUpdated:
|
||||
noti.UserID = ev.Executant.GetOpaqueId()
|
||||
noti.Subject = "Share received"
|
||||
noti.Message = fmt.Sprintf("A link was updated in space %s", space.Name)
|
||||
}
|
||||
|
||||
return noti, nil
|
||||
}
|
||||
|
||||
// OC10Notification is the oc10 style representation of an event
|
||||
// some fields are left out for simplicity
|
||||
type OC10Notification struct {
|
||||
EventID string `json:"notification_id"`
|
||||
Service string `json:"app"`
|
||||
Timestamp string `json:"datetime"`
|
||||
UserID string `json:"user"`
|
||||
Subject string `json:"subject"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// GetEventResponseOC10 is the response from GET events endpoint in oc10 style
|
||||
type GetEventResponseOC10 struct {
|
||||
OCS struct {
|
||||
@@ -221,3 +80,8 @@ type GetEventResponseOC10 struct {
|
||||
Data []OC10Notification `json:"data"`
|
||||
} `json:"ocs"`
|
||||
}
|
||||
|
||||
// DeleteEventsRequest is the expected body for the delete request
|
||||
type DeleteEventsRequest struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
// UserlogService is the service responsible for user activities
|
||||
type UserlogService struct {
|
||||
log log.Logger
|
||||
ch <-chan events.Event
|
||||
m *chi.Mux
|
||||
store store.Store
|
||||
cfg *config.Config
|
||||
@@ -52,7 +51,6 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) {
|
||||
|
||||
ul := &UserlogService{
|
||||
log: o.Logger,
|
||||
ch: ch,
|
||||
m: o.Mux,
|
||||
store: o.Store,
|
||||
cfg: o.Config,
|
||||
@@ -71,80 +69,50 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) {
|
||||
r.Delete("/*", ul.HandleDeleteEvents)
|
||||
})
|
||||
|
||||
go ul.MemorizeEvents()
|
||||
go ul.MemorizeEvents(ch)
|
||||
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
// MemorizeEvents stores eventIDs a user wants to receive
|
||||
func (ul *UserlogService) MemorizeEvents() {
|
||||
for event := range ul.ch {
|
||||
func (ul *UserlogService) MemorizeEvents(ch <-chan events.Event) {
|
||||
for event := range ch {
|
||||
// for each event we need to:
|
||||
// I) find users eligible to receive the event
|
||||
var (
|
||||
users []string
|
||||
err error
|
||||
)
|
||||
|
||||
switch e := event.Event.(type) {
|
||||
default:
|
||||
err = errors.New("unhandled event")
|
||||
|
||||
// file related
|
||||
case events.UploadReady:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.FileRef.GetResourceId().GetSpaceId(), viewer)
|
||||
case events.ContainerCreated:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), viewer)
|
||||
case events.FileTouched:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), viewer)
|
||||
case events.FileDownloaded:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.Ref.GetResourceId().GetSpaceId(), viewer) // no space owner in event
|
||||
case events.FileVersionRestored:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), editor)
|
||||
case events.ItemMoved:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), viewer)
|
||||
case events.ItemTrashed:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), viewer)
|
||||
case events.ItemPurged:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.Ref.GetResourceId().GetSpaceId(), editor) // no space owner in event
|
||||
case events.ItemRestored:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.SpaceOwner), e.Ref.GetResourceId().GetSpaceId(), viewer)
|
||||
|
||||
// space related // TODO: how to find spaceadmins?
|
||||
case events.SpaceCreated:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), viewer)
|
||||
case events.SpaceRenamed:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), viewer)
|
||||
case events.SpaceEnabled:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), viewer)
|
||||
case events.SpaceDisabled:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), manager)
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), viewer)
|
||||
case events.SpaceDeleted:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), manager)
|
||||
for u, _ := range e.FinalMembers {
|
||||
users = append(users, u)
|
||||
}
|
||||
case events.SpaceShared:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), manager)
|
||||
users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID)
|
||||
case events.SpaceUnshared:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), manager)
|
||||
case events.SpaceUpdated:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), manager)
|
||||
users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID)
|
||||
case events.SpaceMembershipExpired:
|
||||
users, err = ul.resolveShare(ul.impersonate(e.SpaceOwner), e.GranteeUserID, e.GranteeGroupID, e.SpaceID.GetOpaqueId())
|
||||
users, err = ul.resolveID(ul.impersonate(e.SpaceOwner), e.GranteeUserID, e.GranteeGroupID)
|
||||
|
||||
// share related
|
||||
case events.ShareCreated:
|
||||
users, err = ul.resolveShare(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID, e.ItemID.GetSpaceId())
|
||||
case events.ShareUpdated:
|
||||
users, err = ul.resolveShare(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID, e.ItemID.GetSpaceId())
|
||||
users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID)
|
||||
case events.ShareRemoved:
|
||||
users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID)
|
||||
case events.ShareExpired:
|
||||
users, err = ul.resolveShare(ul.impersonate(e.ShareOwner), e.GranteeUserID, e.GranteeGroupID, e.ItemID.GetSpaceId())
|
||||
case events.LinkCreated:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ItemID.GetOpaqueId(), editor)
|
||||
case events.LinkUpdated:
|
||||
users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ItemID.GetOpaqueId(), editor)
|
||||
|
||||
users, err = ul.resolveID(ul.impersonate(e.ShareOwner), e.GranteeUserID, e.GranteeGroupID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Interface("event", event).Msg("error gathering members for event")
|
||||
// TODO: Find out why this errors on ci pipeline
|
||||
ul.log.Debug().Err(err).Interface("event", event).Msg("error gathering members for event")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -218,20 +186,6 @@ func (ul *UserlogService) DeleteEvents(userid string, evids []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (ul *UserlogService) impersonate(u *user.UserId) context.Context {
|
||||
if u == nil {
|
||||
ul.log.Debug().Msg("cannot impersonate nil user")
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, _, err := utils.Impersonate(u, ul.gwClient, ul.cfg.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Str("userid", u.GetOpaqueId()).Msg("failed to impersonate user")
|
||||
return nil
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ul *UserlogService) addEventsToUser(userid string, eventids ...string) error {
|
||||
return ul.alterUserEventList(userid, func(ids []string) []string {
|
||||
return append(ids, eventids...)
|
||||
@@ -318,23 +272,6 @@ func (ul *UserlogService) findSpaceMembers(ctx context.Context, spaceID string,
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getSpace(ctx context.Context, spaceID string) (*storageprovider.StorageSpace, error) {
|
||||
res, err := ul.gwClient.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("Unexpected status code while getting space: %v", res.GetStatus().GetCode())
|
||||
}
|
||||
|
||||
if len(res.StorageSpaces) == 0 {
|
||||
return nil, fmt.Errorf("error getting storage space %s: no space returned", spaceID)
|
||||
}
|
||||
|
||||
return res.StorageSpaces[0], nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) gatherSpaceMembers(ctx context.Context, space *storageprovider.StorageSpace, hasRequiredRole permissionChecker) ([]string, error) {
|
||||
var permissionsMap map[string]*storageprovider.ResourcePermissions
|
||||
if err := utils.ReadJSONFromOpaque(space.GetOpaque(), "grants", &permissionsMap); err != nil {
|
||||
@@ -378,29 +315,6 @@ func (ul *UserlogService) gatherSpaceMembers(ctx context.Context, space *storage
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// resolves the users of a group
|
||||
func (ul *UserlogService) resolveGroup(ctx context.Context, groupID string) ([]string, error) {
|
||||
if ctx == nil {
|
||||
return nil, errors.New("need authenticated context to resolve groups")
|
||||
}
|
||||
|
||||
r, err := ul.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupID}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode())
|
||||
}
|
||||
|
||||
var userIDs []string
|
||||
for _, m := range r.GetGroup().GetMembers() {
|
||||
userIDs = append(userIDs, m.GetOpaqueId())
|
||||
}
|
||||
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) resolveID(ctx context.Context, userid *user.UserId, groupid *group.GroupId) ([]string, error) {
|
||||
if userid != nil {
|
||||
return []string{userid.GetOpaqueId()}, nil
|
||||
@@ -409,18 +323,91 @@ func (ul *UserlogService) resolveID(ctx context.Context, userid *user.UserId, gr
|
||||
return ul.resolveGroup(ctx, groupid.GetOpaqueId())
|
||||
}
|
||||
|
||||
func (ul *UserlogService) resolveShare(ctx context.Context, userid *user.UserId, groupid *group.GroupId, spaceid string) ([]string, error) {
|
||||
users, err := ul.resolveID(ctx, userid, groupid)
|
||||
// resolves the users of a group
|
||||
func (ul *UserlogService) resolveGroup(ctx context.Context, groupID string) ([]string, error) {
|
||||
grp, err := ul.getGroup(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usr, err := ul.findSpaceMembers(ctx, spaceid, editor)
|
||||
var userIDs []string
|
||||
for _, m := range grp.GetMembers() {
|
||||
userIDs = append(userIDs, m.GetOpaqueId())
|
||||
}
|
||||
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) impersonate(u *user.UserId) context.Context {
|
||||
if u == nil {
|
||||
ul.log.Debug().Msg("cannot impersonate nil user")
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, _, err := utils.Impersonate(u, ul.gwClient, ul.cfg.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Str("userid", u.GetOpaqueId()).Msg("failed to impersonate user")
|
||||
return nil
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getSpace(ctx context.Context, spaceID string) (*storageprovider.StorageSpace, error) {
|
||||
res, err := ul.gwClient.ListStorageSpaces(ctx, listStorageSpaceRequest(spaceID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(users, usr...), nil
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("Unexpected status code while getting space: %v", res.GetStatus().GetCode())
|
||||
}
|
||||
|
||||
if len(res.StorageSpaces) == 0 {
|
||||
return nil, fmt.Errorf("error getting storage space %s: no space returned", spaceID)
|
||||
}
|
||||
|
||||
return res.StorageSpaces[0], nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getUser(ctx context.Context, userid *user.UserId) (*user.User, error) {
|
||||
getUserResponse, err := ul.gwClient.GetUser(context.Background(), &user.GetUserRequest{
|
||||
UserId: userid,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if getUserResponse.Status.Code != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("error getting user: %s", getUserResponse.Status.Message)
|
||||
}
|
||||
|
||||
return getUserResponse.GetUser(), nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getGroup(ctx context.Context, groupid string) (*group.Group, error) {
|
||||
r, err := ul.gwClient.GetGroup(ctx, &group.GetGroupRequest{GroupId: &group.GroupId{OpaqueId: groupid}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("unexpected status code from gateway client: %d", r.GetStatus().GetCode())
|
||||
}
|
||||
|
||||
return r.GetGroup(), nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) getResource(ctx context.Context, resourceid *storageprovider.ResourceId) (*storageprovider.ResourceInfo, error) {
|
||||
res, err := ul.gwClient.Stat(ctx, &storageprovider.StatRequest{Ref: &storageprovider.Reference{ResourceId: resourceid}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("Unexpected status code while getting space: %v", res.GetStatus().GetCode())
|
||||
}
|
||||
|
||||
return res.GetInfo(), nil
|
||||
}
|
||||
|
||||
func listStorageSpaceRequest(spaceID string) *storageprovider.ListStorageSpacesRequest {
|
||||
|
||||
@@ -64,7 +64,7 @@ var _ = Describe("UserlogService", func() {
|
||||
service.GatewayClient(&gwc),
|
||||
service.HistoryClient(&ehc),
|
||||
service.RegisteredEvents([]events.Unmarshaller{
|
||||
events.UploadReady{},
|
||||
events.SpaceDisabled{},
|
||||
}),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -73,9 +73,11 @@ var _ = Describe("UserlogService", func() {
|
||||
|
||||
It("it stores, returns and deletes a couple of events", func() {
|
||||
ids := make(map[string]struct{})
|
||||
ids[bus.Publish(events.SpaceCreated{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
ids[bus.Publish(events.UploadReady{SpaceOwner: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
ids[bus.Publish(events.ContainerCreated{SpaceOwner: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
ids[bus.Publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
ids[bus.Publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
ids[bus.Publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
// ids[bus.Publish(events.SpaceMembershipExpired{SpaceOwner: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
// ids[bus.Publish(events.ShareCreated{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
|
||||
65
services/userlog/pkg/service/templates.go
Normal file
65
services/userlog/pkg/service/templates.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package service
|
||||
|
||||
import "text/template"
|
||||
|
||||
// the available templates
|
||||
var (
|
||||
SpaceShared = "space-shared"
|
||||
SpaceSharedSubject = "Space shared"
|
||||
SpaceSharedMessage = "{{ .username }} added you to Space {{ .spacename }}"
|
||||
|
||||
SpaceUnshared = "space-unshared"
|
||||
SpaceUnsharedSubject = "Removed from Space"
|
||||
SpaceUnsharedMessage = "{{ .username }} removed you from Space {{ .spacename }}"
|
||||
|
||||
SpaceDisabled = "space-disabled"
|
||||
SpaceDisabledSubject = "Space disabled"
|
||||
SpaceDisabledMessage = "{{ .username }} disabled Space {{ .spacename }}"
|
||||
|
||||
SpaceDeleted = "space-deleted"
|
||||
SpaceDeletedSubject = "Space deleted"
|
||||
SpaceDeletedMessage = "{{ .username }} deleted Space {{ .spacename }}"
|
||||
|
||||
SpaceMembershipExpired = "space-membership-expired"
|
||||
SpaceMembershipExpiredSubject = "Membership expired"
|
||||
SpaceMembershipExpiredMessage = "Access to Space {{ .spacename }} lost"
|
||||
|
||||
ShareCreated = "item-shared"
|
||||
ShareCreatedSubject = "Resource shared"
|
||||
ShareCreatedMessage = "{{ .username }} shared {{ .resourcename }} with you"
|
||||
|
||||
ShareRemoved = "item-unshared"
|
||||
ShareRemovedSubject = "Resource unshared"
|
||||
ShareRemovedMessage = "{{ .username }} unshared {{ .resourcename }} with you"
|
||||
|
||||
ShareExpired = "share-expired"
|
||||
ShareExpiredSubject = "Share expired"
|
||||
ShareExpiredMessage = "Access to {{ .resourcename }} expired"
|
||||
)
|
||||
|
||||
// rendered templates
|
||||
var (
|
||||
_templates = map[string]NotificationTemplate{
|
||||
SpaceShared: notiTmpl(SpaceSharedSubject, SpaceSharedMessage),
|
||||
SpaceUnshared: notiTmpl(SpaceUnsharedSubject, SpaceUnsharedMessage),
|
||||
SpaceDisabled: notiTmpl(SpaceDisabledSubject, SpaceDisabledMessage),
|
||||
SpaceDeleted: notiTmpl(SpaceDeletedSubject, SpaceDeletedMessage),
|
||||
SpaceMembershipExpired: notiTmpl(SpaceMembershipExpiredSubject, SpaceMembershipExpiredMessage),
|
||||
ShareCreated: notiTmpl(ShareCreatedSubject, ShareCreatedMessage),
|
||||
ShareRemoved: notiTmpl(ShareRemovedSubject, ShareRemovedMessage),
|
||||
ShareExpired: notiTmpl(ShareExpiredSubject, ShareExpiredMessage),
|
||||
}
|
||||
)
|
||||
|
||||
// NotificationTemplate is the data structure for the notifications
|
||||
type NotificationTemplate struct {
|
||||
Subject *template.Template
|
||||
Message *template.Template
|
||||
}
|
||||
|
||||
func notiTmpl(subjectname string, messagename string) NotificationTemplate {
|
||||
return NotificationTemplate{
|
||||
Subject: template.Must(template.New("").Parse(subjectname)),
|
||||
Message: template.Must(template.New("").Parse(messagename)),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user