From acdfbabd4789053c9e0282fd390708550d89aa0f Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 12 Sep 2022 16:59:49 +0200 Subject: [PATCH] add email templating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian Richter Co-authored-by: Jörn Dreyer --- services/notifications/pkg/command/server.go | 8 +- services/notifications/pkg/email/email.go | 25 +++ .../email/templates/shareCreated.email.tmpl | 1 + .../email/templates/sharedSpace.email.tmpl | 0 services/notifications/pkg/service/service.go | 148 +++++++++++++++--- 5 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 services/notifications/pkg/email/email.go create mode 100644 services/notifications/pkg/email/templates/shareCreated.email.tmpl create mode 100644 services/notifications/pkg/email/templates/sharedSpace.email.tmpl diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index 8685b4e429..8c8849611f 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -5,6 +5,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/events/server" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/go-micro/plugins/v4/events/natsjs" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" @@ -47,7 +48,12 @@ func Server(cfg *config.Config) *cli.Command { if err != nil { return err } - svc := service.NewEventsNotifier(evts, channel, logger) + gwclient, err := pool.GetGatewayServiceClient(cfg.Notifications.RevaGateway) + if err != nil { + logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva client") + } + + svc := service.NewEventsNotifier(evts, channel, logger, gwclient, cfg.Commons.MachineAuthAPIKey) return svc.Run() }, } diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go new file mode 100644 index 0000000000..bdc3ddf808 --- /dev/null +++ b/services/notifications/pkg/email/email.go @@ -0,0 +1,25 @@ +package email + +import ( + "bytes" + "html/template" + "os" + "path/filepath" +) + +const templatePath string = "../../email/templates" + +// RenderEmailTemplate renders the email template for a new share +func RenderEmailTemplate(templateName string, templateVariables map[string]string) (string, error) { + content, err := os.ReadFile(filepath.Join(templatePath, templateName)) + if err != nil { + return "", err + } + tpl := template.Must(template.New("").Parse(string(content))) + writer := bytes.NewBufferString("") + err = tpl.Execute(writer, templateVariables) + if err != nil { + return "", err + } + return writer.String(), nil +} diff --git a/services/notifications/pkg/email/templates/shareCreated.email.tmpl b/services/notifications/pkg/email/templates/shareCreated.email.tmpl new file mode 100644 index 0000000000..408297ea3d --- /dev/null +++ b/services/notifications/pkg/email/templates/shareCreated.email.tmpl @@ -0,0 +1 @@ +{{ShareSharer}} has shared {{ShareFolder}} with you. \ No newline at end of file diff --git a/services/notifications/pkg/email/templates/sharedSpace.email.tmpl b/services/notifications/pkg/email/templates/sharedSpace.email.tmpl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index d6c8f97051..b386ae2de6 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -1,33 +1,47 @@ package service import ( + "context" "os" "os/signal" "syscall" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" + "github.com/owncloud/ocis/v2/services/notifications/pkg/email" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/fieldmaskpb" ) type Service interface { Run() error } -func NewEventsNotifier(events <-chan interface{}, channel channels.Channel, logger log.Logger) Service { +func NewEventsNotifier(events <-chan interface{}, channel channels.Channel, logger log.Logger, gwClient gateway.GatewayAPIClient, machineAuthAPIKey string) Service { return eventsNotifier{ - logger: logger, - channel: channel, - events: events, - signals: make(chan os.Signal, 1), + logger: logger, + channel: channel, + events: events, + signals: make(chan os.Signal, 1), + gwClient: gwClient, + machineAuthAPIKey: machineAuthAPIKey, } } type eventsNotifier struct { - logger log.Logger - channel channels.Channel - events <-chan interface{} - signals chan os.Signal + logger log.Logger + channel channels.Channel + events <-chan interface{} + signals chan os.Signal + gwClient gateway.GatewayAPIClient + machineAuthAPIKey string } func (s eventsNotifier) Run() error { @@ -39,20 +53,10 @@ func (s eventsNotifier) Run() error { case evt := <-s.events: go func() { switch e := evt.(type) { + case events.SpaceCreated: + s.handleSpaceCreated(e) case events.ShareCreated: - msg := "You got a share!" - var err error - if e.GranteeUserID != nil { - err = s.channel.SendMessage([]string{e.GranteeUserID.OpaqueId}, msg) - } else if e.GranteeGroupID != nil { - err = s.channel.SendMessageToGroup(e.GranteeGroupID, msg) - } - if err != nil { - s.logger.Error(). - Err(err). - Str("event", "ShareCreated"). - Msg("failed to send a message") - } + s.handleShareCreated(e) } }() case <-s.signals: @@ -62,3 +66,103 @@ func (s eventsNotifier) Run() error { } } } + +func (s eventsNotifier) handleSpaceCreated(e events.SpaceCreated) { + // TODO: implement me +} + +func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { + userResponse, err := s.gwClient.GetUser(context.Background(), &userv1beta1.GetUserRequest{ + UserId: e.Sharer, + }) + if err != nil || userResponse.Status.Code != rpcv1beta1.Code_CODE_OK { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("Could not get user response from gatway client") + return + } + + // Get auth context + ownerCtx := ctxpkg.ContextSetUser(context.Background(), userResponse.User) + authRes, err := s.gwClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{ + Type: "machine", + ClientId: "userid:" + e.Sharer.OpaqueId, + ClientSecret: s.machineAuthAPIKey, + }) + if err != nil || authRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("Could not impersonate sharer") + return + } + + if authRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("could not get authenticated context for user") + return + } + ownerCtx = metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token) + + resourceID, err := storagespace.ParseID(e.ItemID.OpaqueId) + if err != nil { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Str("itemid", e.ItemID.OpaqueId). + Msg("could not parse resourceid from ItemID ") + return + } + // TODO: maybe cache this stat to reduce storage iops + md, err := s.gwClient.Stat(ownerCtx, &providerv1beta1.StatRequest{ + Ref: &providerv1beta1.Reference{ + ResourceId: &resourceID, + }, + FieldMask: &fieldmaskpb.FieldMask{Paths: []string{"name"}}, + }) + + if err != nil || md.Status.Code != rpcv1beta1.Code_CODE_OK { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Str("itemid", e.ItemID.OpaqueId). + Msg("could not stat resource") + return + } + + if md.Status.Code != rpcv1beta1.Code_CODE_OK { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Str("itemid", e.ItemID.OpaqueId). + Str("rpc status", md.Status.Code.String()). + Msg("could not stat resource") + return + } + + msg, err := email.RenderEmailTemplate("shareCreated.email.tmpl", map[string]string{ + "ShareSharer": userResponse.User.DisplayName, + "ShareFolder": md.Info.Name, + }) + + if err != nil { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("Could not render E-Mail template for shares") + } + if e.GranteeUserID != nil { + err = s.channel.SendMessage([]string{e.GranteeUserID.OpaqueId}, msg) + } else if e.GranteeGroupID != nil { + err = s.channel.SendMessageToGroup(e.GranteeGroupID, msg) + } + if err != nil { + s.logger.Error(). + Err(err). + Str("event", "ShareCreated"). + Msg("failed to send a message") + } +}