Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Userlog Service Improvements #5699

Merged
merged 11 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions changelog/unreleased/userlog-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Userlog

Enhane userlog service with proper api and messages

https://github.com/owncloud/ocis/pull/5699
3 changes: 3 additions & 0 deletions services/frontend/pkg/revaconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 1 addition & 20 deletions services/userlog/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
249 changes: 249 additions & 0 deletions services/userlog/pkg/service/conversion.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading