From 1ca02559f08bcd1f7957ffca40af0238476d51c4 Mon Sep 17 00:00:00 2001 From: Gianmaria Del Monte <39946305+gmgigi96@users.noreply.github.com> Date: Fri, 24 Feb 2023 22:19:51 +0100 Subject: [PATCH] Specify recipient as a query param when sending OCM token by email (#3687) --- changelog/unreleased/ocm-token-email.md | 7 ++ internal/http/services/sciencemesh/email.go | 118 ++++++++++++++++++ .../http/services/sciencemesh/sciencemesh.go | 3 + internal/http/services/sciencemesh/token.go | 40 +++--- pkg/smtpclient/smtpclient.go | 6 +- 5 files changed, 155 insertions(+), 19 deletions(-) create mode 100644 changelog/unreleased/ocm-token-email.md create mode 100644 internal/http/services/sciencemesh/email.go diff --git a/changelog/unreleased/ocm-token-email.md b/changelog/unreleased/ocm-token-email.md new file mode 100644 index 0000000000..c44258d671 --- /dev/null +++ b/changelog/unreleased/ocm-token-email.md @@ -0,0 +1,7 @@ +Enhancement: Specify recipient as a query param when sending OCM token by email + +Before the email recipient when sending the OCM token was specified as a form parameter. +Now as a query parameter, as some clients does not allow in a GET request to set form values. +It also add the possibility to specify a template for the subject and the body for the token email. + +https://github.com/cs3org/reva/pull/3687 \ No newline at end of file diff --git a/internal/http/services/sciencemesh/email.go b/internal/http/services/sciencemesh/email.go new file mode 100644 index 0000000000..fa7792e220 --- /dev/null +++ b/internal/http/services/sciencemesh/email.go @@ -0,0 +1,118 @@ +// Copyright 2018-2023 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sciencemesh + +import ( + "bytes" + "html/template" + "io" + "os" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" +) + +type emailParams struct { + User *userpb.User + Token string + MeshDirectoryURL string +} + +const defaultSubject = `ScienceMesh: {{.User.DisplayName}} wants to collaborate with you` + +const defaultBody = `Hi + +{{.User.DisplayName}} ({{.User.Mail}}) wants to start sharing OCM resources with you. +To accept the invite, please visit the following URL: +{{.MeshDirectoryURL}}?token={{.Token}}&providerDomain={{.User.Id.Idp}} + +Alternatively, you can visit your mesh provider and use the following details: +Token: {{.Token}} +ProviderDomain: {{.User.Id.Idp}} + +Best, +The ScienceMesh team` + +func (h *tokenHandler) sendEmail(recipient string, obj *emailParams) error { + subj, err := h.generateEmailSubject(obj) + if err != nil { + return err + } + + body, err := h.generateEmailBody(obj) + if err != nil { + return err + } + + return h.smtpCredentials.SendMail(recipient, subj, body) +} + +func (h *tokenHandler) generateEmailSubject(obj *emailParams) (string, error) { + var buf bytes.Buffer + err := h.tplSubj.Execute(&buf, obj) + return buf.String(), err +} + +func (h *tokenHandler) generateEmailBody(obj *emailParams) (string, error) { + var buf bytes.Buffer + err := h.tplBody.Execute(&buf, obj) + return buf.String(), err +} + +func (h *tokenHandler) initBodyTemplate(bodyTemplPath string) error { + var body string + if bodyTemplPath == "" { + body = defaultBody + } else { + f, err := os.Open(bodyTemplPath) + if err != nil { + return err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return err + } + body = string(data) + } + + tpl, err := template.New("tpl_body").Parse(body) + if err != nil { + return err + } + + h.tplBody = tpl + return nil +} + +func (h *tokenHandler) initSubjectTemplate(subjTempl string) error { + var subj string + if subjTempl == "" { + subj = defaultSubject + } else { + subj = subjTempl + } + + tpl, err := template.New("tpl_subj").Parse(subj) + if err != nil { + return err + } + h.tplSubj = tpl + return nil +} diff --git a/internal/http/services/sciencemesh/sciencemesh.go b/internal/http/services/sciencemesh/sciencemesh.go index 5d7bc989d4..ec9a235519 100644 --- a/internal/http/services/sciencemesh/sciencemesh.go +++ b/internal/http/services/sciencemesh/sciencemesh.go @@ -66,6 +66,9 @@ type config struct { SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials"` GatewaySvc string `mapstructure:"gatewaysvc"` MeshDirectoryURL string `mapstructure:"mesh_directory_url"` + ProviderDomain string `mapstructure:"provider_domain"` + SubjectTemplate string `mapstructure:"subject_template"` + BodyTemplatePath string `mapstructure:"body_template_path"` } func (c *config) init() { diff --git a/internal/http/services/sciencemesh/token.go b/internal/http/services/sciencemesh/token.go index e0f61d295e..bfe26efd9c 100644 --- a/internal/http/services/sciencemesh/token.go +++ b/internal/http/services/sciencemesh/token.go @@ -21,7 +21,7 @@ package sciencemesh import ( "encoding/json" "errors" - "fmt" + "html/template" "mime" "net/http" @@ -40,6 +40,9 @@ type tokenHandler struct { gatewayClient gateway.GatewayAPIClient smtpCredentials *smtpclient.SMTPCredentials meshDirectoryURL string + + tplSubj *template.Template + tplBody *template.Template } func (h *tokenHandler) init(c *config) error { @@ -54,6 +57,14 @@ func (h *tokenHandler) init(c *config) error { } h.meshDirectoryURL = c.MeshDirectoryURL + + if err := h.initSubjectTemplate(c.SubjectTemplate); err != nil { + return err + } + + if err := h.initBodyTemplate(c.BodyTemplatePath); err != nil { + return err + } return nil } @@ -63,30 +74,23 @@ func (h *tokenHandler) init(c *config) error { func (h *tokenHandler) Generate(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + query := r.URL.Query() token, err := h.gatewayClient.GenerateInviteToken(ctx, &invitepb.GenerateInviteTokenRequest{ - Description: r.URL.Query().Get("description"), + Description: query.Get("description"), }) if err != nil { reqres.WriteError(w, r, reqres.APIErrorServerError, "error generating token", err) return } - if r.FormValue("recipient") != "" && h.smtpCredentials != nil { - usr := ctxpkg.ContextMustGetUser(ctx) - - // TODO: the message body needs to point to the meshdirectory service - subject := fmt.Sprintf("ScienceMesh: %s wants to collaborate with you", usr.DisplayName) - body := "Hi,\n\n" + - usr.DisplayName + " (" + usr.Mail + ") wants to start sharing OCM resources with you. " + - "To accept the invite, please visit the following URL:\n" + - h.meshDirectoryURL + "?token=" + token.InviteToken.Token + "&providerDomain=" + usr.Id.Idp + "\n\n" + - "Alternatively, you can visit your mesh provider and use the following details:\n" + - "Token: " + token.InviteToken.Token + "\n" + - "ProviderDomain: " + usr.Id.Idp + "\n\n" + - "Best,\nThe ScienceMesh team" - - err = h.smtpCredentials.SendMail(r.FormValue("recipient"), subject, body) - if err != nil { + recipient := query.Get("recipient") + if recipient != "" && h.smtpCredentials != nil { + templObj := &emailParams{ + User: ctxpkg.ContextMustGetUser(ctx), + Token: token.InviteToken.Token, + MeshDirectoryURL: h.meshDirectoryURL, + } + if err := h.sendEmail(recipient, templObj); err != nil { reqres.WriteError(w, r, reqres.APIErrorServerError, "error sending token by mail", err) return } diff --git a/pkg/smtpclient/smtpclient.go b/pkg/smtpclient/smtpclient.go index 77546089d4..3d143b1874 100644 --- a/pkg/smtpclient/smtpclient.go +++ b/pkg/smtpclient/smtpclient.go @@ -22,6 +22,7 @@ import ( "bytes" "encoding/base64" "fmt" + "net/http" "net/smtp" "os" "strings" @@ -64,6 +65,9 @@ func NewSMTPCredentials(c *SMTPCredentials) *SMTPCredentials { // SendMail allows sending mails using a set of client credentials. func (creds *SMTPCredentials) SendMail(recipient, subject, body string) error { + // try to detect the content type from the subject + mime := http.DetectContentType([]byte(body)) + headers := map[string]string{ "From": creds.SenderMail, "To": recipient, @@ -71,7 +75,7 @@ func (creds *SMTPCredentials) SendMail(recipient, subject, body string) error { "Date": time.Now().Format(time.RFC1123Z), "Message-ID": uuid.New().String(), "MIME-Version": "1.0", - "Content-Type": "text/plain; charset=\"utf-8\"", + "Content-Type": mime + "; charset=\"utf-8\"", "Content-Transfer-Encoding": "base64", }