Skip to content

Commit

Permalink
Merge pull request #5699 from kobergj/PolishUserlogService
Browse files Browse the repository at this point in the history
Userlog Service Improvements
  • Loading branch information
kobergj authored Mar 9, 2023
2 parents 7177c41 + c1d436a commit bc43c31
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 267 deletions.
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

0 comments on commit bc43c31

Please sign in to comment.