From 5ceb08126296fb26e8f2966b1657df74a4b21f8d Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 3 Sep 2024 18:18:14 +0200 Subject: [PATCH] enhancement(notifications): process ocm invites and sent out a mail notification --- services/notifications/pkg/email/email.go | 2 +- services/notifications/pkg/email/templates.go | 37 +++++++--- .../notifications/pkg/service/sciencemesh.go | 70 ++++++++++++++++++- services/notifications/pkg/service/service.go | 8 +++ 4 files changed, 107 insertions(+), 10 deletions(-) diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go index b1b789314..7a3ff2051 100644 --- a/services/notifications/pkg/email/email.go +++ b/services/notifications/pkg/email/email.go @@ -25,7 +25,7 @@ var ( imgDir = filepath.Join("templates", "html", "img") ) -// RenderEmailTemplate renders the email template for a new share +// RenderEmailTemplate is responsible to prepare a message which than can be used to notify the user via email. func RenderEmailTemplate(mt MessageTemplate, locale, defaultLocale string, emailTemplatePath string, translationPath string, vars map[string]string) (*channels.Message, error) { textMt, err := NewTextTemplate(mt, locale, defaultLocale, translationPath, vars) if err != nil { diff --git a/services/notifications/pkg/email/templates.go b/services/notifications/pkg/email/templates.go index 615e8409b..66f93d9d2 100644 --- a/services/notifications/pkg/email/templates.go +++ b/services/notifications/pkg/email/templates.go @@ -72,18 +72,39 @@ You might still have access through your other groups or direct membership.`), Even though this membership has expired you still might have access through other shares and/or space memberships`), } + + ScienceMeshInviteTokenGenerated = MessageTemplate{ + textTemplate: "templates/text/email.text.tmpl", + htmlTemplate: "templates/html/email.html.tmpl", + // ScienceMeshInviteTokenGenerated email template, Subject field (resolves directly) + Subject: l10n.Template(`ScienceMesh: {InitiatorName} wants to collaborate with you`), + // ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting }} + Greeting: l10n.Template(`Hi,`), + // ScienceMeshInviteTokenGenerated email template, resolves via {{ .MessageBody }} + MessageBody: l10n.Template(`{ShareSharer} ({ShareSharerMail}) wants to start sharing OCM resources with you. +{{if .ShareLink }}To accept the invite, please visit the following URL: +{ShareLink} + +Alternatively, you can{{else}} +Please{{end}} visit your mesh provider and use the following details: + Token: {Token} + ProviderDomain: {ProviderDomain}`), + } ) // holds the information to turn the raw template into a parseable go template var _placeholders = map[string]string{ - "{ShareSharer}": "{{ .ShareSharer }}", - "{ShareFolder}": "{{ .ShareFolder }}", - "{ShareGrantee}": "{{ .ShareGrantee }}", - "{ShareLink}": "{{ .ShareLink }}", - "{SpaceName}": "{{ .SpaceName }}", - "{SpaceGrantee}": "{{ .SpaceGrantee }}", - "{SpaceSharer}": "{{ .SpaceSharer }}", - "{ExpiredAt}": "{{ .ExpiredAt }}", + "{ShareSharer}": "{{ .ShareSharer }}", + "{ShareFolder}": "{{ .ShareFolder }}", + "{ShareGrantee}": "{{ .ShareGrantee }}", + "{ShareLink}": "{{ .ShareLink }}", + "{SpaceName}": "{{ .SpaceName }}", + "{SpaceGrantee}": "{{ .SpaceGrantee }}", + "{SpaceSharer}": "{{ .SpaceSharer }}", + "{ExpiredAt}": "{{ .ExpiredAt }}", + "{ShareSharerMail}": "{{ .ShareSharerMail }}", + "{ProviderDomain}": "{{ .ProviderDomain }}", + "{Token}": "{{ .Token }}", } // MessageTemplate is the data structure for the email diff --git a/services/notifications/pkg/service/sciencemesh.go b/services/notifications/pkg/service/sciencemesh.go index 344a6bf22..356815837 100644 --- a/services/notifications/pkg/service/sciencemesh.go +++ b/services/notifications/pkg/service/sciencemesh.go @@ -1,9 +1,77 @@ package service import ( + "context" + "github.com/cs3org/reva/v2/pkg/events" + "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/services/notifications/pkg/channels" + "github.com/owncloud/ocis/v2/services/notifications/pkg/email" ) func (s eventsNotifier) handleScienceMeshInviteTokenGenerated(e events.ScienceMeshInviteTokenGenerated) { - // fixMe: add implementation + logger := s.logger.With(). + Str("event", "ScienceMeshInviteTokenGenerated"). + Logger() + + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + ctx, err := utils.GetServiceUserContextWithContext(context.Background(), gatewayClient, s.serviceAccountID, s.serviceAccountSecret) + if err != nil { + logger.Error().Err(err).Msg("Could not impersonate service user") + return + } + + owner, err := utils.GetUserWithContext(ctx, e.Sharer, gatewayClient) + if err != nil { + logger.Error().Err(err).Msg("unable to get user") + return + } + + msgENV := map[string]string{ + "ShareSharer": owner.GetDisplayName(), + "ShareSharerMail": owner.GetMail(), + "ShareLink": e.InviteLink, + "Token": e.Token, + "ProviderDomain": owner.GetId().GetIdp(), + "RecipientMail": e.RecipientMail, + } + + // validate the message, we only need recipient mail at the moment, + // event that is optional when the event got triggered... + // this means if we get a validation error, we can't send the message and skip it + { + validationEnv := make(map[string]interface{}, len(msgENV)) + for k, v := range msgENV { + validationEnv[k] = v + } + if errs := validate.ValidateMap(validationEnv, + map[string]interface{}{ + "RecipientMail": "required,email", // only recipient mail is required to send the message + }); len(errs) > 0 { + return // no mail, no message + } + } + + msg, err := email.RenderEmailTemplate( + email.ScienceMeshInviteTokenGenerated, + s.defaultLanguage, // fixMe: the recipient is unknown, should it be the defaultLocale?, + s.defaultLanguage, // fixMe: the defaultLocale is not set by default, shouldn't it be?, + s.emailTemplatePath, + s.translationPath, + msgENV, + ) + if err != nil { + s.logger.Error().Err(err).Msg("building the message has failed") + return + } + + msg.Sender = owner.GetDisplayName() + msg.Recipient = []string{e.RecipientMail} + + s.send(ctx, []*channels.Message{msg}) } diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index 0da76d217..1f39e663d 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -16,6 +16,7 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/go-playground/validator/v10" "go-micro.dev/v4/metadata" "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -30,6 +31,13 @@ import ( "github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults" ) +// validate is the package level validator instance +var validate *validator.Validate + +func init() { + validate = validator.New() +} + // Service should be named `Runner` type Service interface { Run() error