2019-07-31 15:30:58 +00:00
|
|
|
/*
|
2021-01-09 14:49:19 +00:00
|
|
|
Copyright 2017-2021 Jan Dittberner
|
2019-07-31 15:30:58 +00:00
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this program except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
2019-07-31 15:30:58 +00:00
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
2019-07-31 15:30:58 +00:00
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
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.
|
2019-07-31 15:30:58 +00:00
|
|
|
*/
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-02-28 17:25:17 +00:00
|
|
|
"embed"
|
2017-04-19 21:32:12 +00:00
|
|
|
"fmt"
|
2021-01-09 14:49:19 +00:00
|
|
|
"text/template"
|
|
|
|
|
2021-02-28 17:25:17 +00:00
|
|
|
"github.com/Masterminds/sprig/v3"
|
|
|
|
"github.com/go-mail/mail"
|
2019-07-31 12:14:21 +00:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2017-04-19 21:32:12 +00:00
|
|
|
)
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type headerData struct {
|
|
|
|
name string
|
|
|
|
value []string
|
|
|
|
}
|
|
|
|
|
|
|
|
type headerList []headerData
|
|
|
|
|
|
|
|
type recipientData struct {
|
|
|
|
field, address, name string
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
type NotificationContent struct {
|
2017-04-21 10:50:29 +00:00
|
|
|
template string
|
|
|
|
data interface{}
|
|
|
|
subject string
|
|
|
|
headers headerList
|
|
|
|
recipients []recipientData
|
|
|
|
}
|
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
type NotificationMail interface {
|
2021-01-09 14:49:19 +00:00
|
|
|
GetNotificationContent() *NotificationContent
|
2017-04-20 18:58:22 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
var NotifyMailChannel = make(chan NotificationMail, 1)
|
2017-04-19 21:32:12 +00:00
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
func MailNotifier(quitMailNotifier chan int) {
|
2017-04-22 18:07:39 +00:00
|
|
|
log.Info("Launched mail notifier")
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
for {
|
|
|
|
select {
|
2017-04-21 00:25:49 +00:00
|
|
|
case notification := <-NotifyMailChannel:
|
2017-04-21 10:50:29 +00:00
|
|
|
content := notification.GetNotificationContent()
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
mailText, err := buildMail(content.template, content.data)
|
2017-04-19 21:32:12 +00:00
|
|
|
if err != nil {
|
2017-04-22 18:07:39 +00:00
|
|
|
log.Errorf("building mail failed: %v", err)
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-02-28 17:25:17 +00:00
|
|
|
m := mail.NewMessage()
|
2017-04-21 10:50:29 +00:00
|
|
|
m.SetAddressHeader("From", config.NotificationSenderAddress, "CAcert board voting system")
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
for _, recipient := range content.recipients {
|
|
|
|
m.SetAddressHeader(recipient.field, recipient.address, recipient.name)
|
|
|
|
}
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
m.SetHeader("Subject", content.subject)
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
for _, header := range content.headers {
|
|
|
|
m.SetHeader(header.name, header.value...)
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
m.SetBody("text/plain", mailText.String())
|
|
|
|
|
2021-02-28 17:25:17 +00:00
|
|
|
d := mail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
|
2017-04-19 21:32:12 +00:00
|
|
|
if err := d.DialAndSend(m); err != nil {
|
2017-04-22 18:07:39 +00:00
|
|
|
log.Errorf("sending mail failed: %v", err)
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
case <-quitMailNotifier:
|
2017-04-22 18:07:39 +00:00
|
|
|
log.Info("Ending mail notifier")
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 17:25:17 +00:00
|
|
|
//go:embed boardvoting/templates
|
|
|
|
var mailTemplates embed.FS
|
|
|
|
|
2017-04-19 21:32:12 +00:00
|
|
|
func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) {
|
2021-02-28 17:25:17 +00:00
|
|
|
b, err := mailTemplates.ReadFile(fmt.Sprintf("templates/%s", templateName))
|
2018-03-29 20:00:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2018-03-29 20:00:56 +00:00
|
|
|
t, err := template.New(templateName).Funcs(sprig.GenericFuncMap()).Parse(string(b))
|
2017-04-19 21:32:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
mailText = bytes.NewBufferString("")
|
2017-04-22 18:07:39 +00:00
|
|
|
if err := t.Execute(mailText, context); err != nil {
|
2021-01-09 14:49:19 +00:00
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"failed to execute template %s with context %+v: %w",
|
|
|
|
templateName, context, err,
|
|
|
|
)
|
2017-04-22 18:07:39 +00:00
|
|
|
}
|
2017-04-19 21:32:12 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
type notificationBase struct{}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func (n *notificationBase) getRecipient() recipientData {
|
2017-04-21 19:41:25 +00:00
|
|
|
return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"}
|
2017-04-21 00:25:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type decisionReplyBase struct {
|
2017-04-19 21:32:12 +00:00
|
|
|
decision Decision
|
2017-04-21 00:25:49 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func (n *decisionReplyBase) getHeaders() headerList {
|
|
|
|
headers := make(headerList, 0)
|
|
|
|
headers = append(headers, headerData{
|
|
|
|
name: "References", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
|
|
|
|
})
|
|
|
|
headers = append(headers, headerData{
|
|
|
|
name: "In-Reply-To", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
|
|
|
|
})
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
return headers
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *decisionReplyBase) getSubject() string {
|
|
|
|
return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
|
2017-04-21 00:25:49 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type notificationClosedDecision struct {
|
2017-04-21 00:25:49 +00:00
|
|
|
notificationBase
|
|
|
|
decisionReplyBase
|
2017-04-22 18:07:39 +00:00
|
|
|
voteSums VoteSums
|
|
|
|
reasoning string
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-22 18:07:39 +00:00
|
|
|
func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail {
|
|
|
|
notification := ¬ificationClosedDecision{voteSums: *voteSums, reasoning: reasoning}
|
2017-04-21 00:25:49 +00:00
|
|
|
notification.decision = *decision
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *notificationClosedDecision) GetNotificationContent() *NotificationContent {
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "closed_motion_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
*Decision
|
|
|
|
*VoteSums
|
2017-04-22 18:07:39 +00:00
|
|
|
Reasoning string
|
|
|
|
}{&n.decision, &n.voteSums, n.reasoning},
|
2017-04-21 10:50:29 +00:00
|
|
|
subject: fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title),
|
|
|
|
headers: n.decisionReplyBase.getHeaders(),
|
|
|
|
recipients: []recipientData{n.notificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NotificationCreateMotion struct {
|
2017-04-21 00:25:49 +00:00
|
|
|
notificationBase
|
2017-04-19 21:32:12 +00:00
|
|
|
decision Decision
|
|
|
|
voter Voter
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *NotificationCreateMotion) GetNotificationContent() *NotificationContent {
|
2017-04-21 09:31:32 +00:00
|
|
|
voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
|
2017-04-19 21:32:12 +00:00
|
|
|
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
|
2021-01-09 14:49:19 +00:00
|
|
|
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "create_motion_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
*Decision
|
|
|
|
Name string
|
|
|
|
VoteURL string
|
|
|
|
UnvotedURL string
|
|
|
|
}{&n.decision, n.voter.Name, voteURL, unvotedURL},
|
|
|
|
subject: fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title),
|
|
|
|
headers: headerList{headerData{"Message-ID", []string{fmt.Sprintf("<%s>", n.decision.Tag)}}},
|
|
|
|
recipients: []recipientData{n.notificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type notificationUpdateMotion struct {
|
2017-04-21 00:25:49 +00:00
|
|
|
notificationBase
|
|
|
|
decisionReplyBase
|
|
|
|
voter Voter
|
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
|
|
|
|
notification := notificationUpdateMotion{voter: voter}
|
2017-04-21 00:25:49 +00:00
|
|
|
notification.decision = decision
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
return ¬ification
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *notificationUpdateMotion) GetNotificationContent() *NotificationContent {
|
2017-04-21 09:31:32 +00:00
|
|
|
voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
|
2017-04-19 21:32:12 +00:00
|
|
|
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
|
2021-01-09 14:49:19 +00:00
|
|
|
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "update_motion_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
*Decision
|
|
|
|
Name string
|
|
|
|
VoteURL string
|
|
|
|
UnvotedURL string
|
|
|
|
}{&n.decision, n.voter.Name, voteURL, unvotedURL},
|
|
|
|
subject: n.decisionReplyBase.getSubject(),
|
|
|
|
headers: n.decisionReplyBase.getHeaders(),
|
|
|
|
recipients: []recipientData{n.notificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type notificationWithDrawMotion struct {
|
2017-04-21 00:25:49 +00:00
|
|
|
notificationBase
|
|
|
|
decisionReplyBase
|
|
|
|
voter Voter
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
|
|
|
|
notification := ¬ificationWithDrawMotion{voter: *voter}
|
2017-04-21 00:25:49 +00:00
|
|
|
notification.decision = *decision
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
return notification
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *notificationWithDrawMotion) GetNotificationContent() *NotificationContent {
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "withdraw_motion_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
*Decision
|
|
|
|
Name string
|
|
|
|
}{&n.decision, n.voter.Name},
|
|
|
|
subject: fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title),
|
|
|
|
headers: n.decisionReplyBase.getHeaders(),
|
|
|
|
recipients: []recipientData{n.notificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-19 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 18:58:22 +00:00
|
|
|
type RemindVoterNotification struct {
|
|
|
|
voter Voter
|
|
|
|
decisions []Decision
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *RemindVoterNotification) GetNotificationContent() *NotificationContent {
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "remind_voter_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
Decisions []Decision
|
|
|
|
Name string
|
|
|
|
BaseURL string
|
|
|
|
}{n.decisions, n.voter.Name, config.BaseURL},
|
|
|
|
subject: "Outstanding CAcert board votes",
|
|
|
|
recipients: []recipientData{{"To", n.voter.Reminder, n.voter.Name}},
|
|
|
|
}
|
2017-04-20 18:58:22 +00:00
|
|
|
}
|
2017-04-21 00:25:49 +00:00
|
|
|
|
|
|
|
type voteNotificationBase struct{}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func (n *voteNotificationBase) getRecipient() recipientData {
|
2017-04-21 19:41:25 +00:00
|
|
|
return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"}
|
2017-04-21 00:25:49 +00:00
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type notificationProxyVote struct {
|
2017-04-21 00:25:49 +00:00
|
|
|
voteNotificationBase
|
|
|
|
decisionReplyBase
|
|
|
|
proxy Voter
|
|
|
|
voter Voter
|
|
|
|
vote Vote
|
|
|
|
justification string
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func NewNotificationProxyVote(
|
|
|
|
decision *Decision,
|
|
|
|
proxy *Voter,
|
|
|
|
voter *Voter,
|
|
|
|
vote *Vote,
|
|
|
|
justification string,
|
|
|
|
) NotificationMail {
|
2017-04-21 10:50:29 +00:00
|
|
|
notification := ¬ificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
|
2017-04-21 00:25:49 +00:00
|
|
|
notification.decision = *decision
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 00:25:49 +00:00
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *notificationProxyVote) GetNotificationContent() *NotificationContent {
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "proxy_vote_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
Proxy string
|
|
|
|
Vote VoteChoice
|
|
|
|
Voter string
|
|
|
|
Decision *Decision
|
|
|
|
Justification string
|
|
|
|
}{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification},
|
|
|
|
subject: n.decisionReplyBase.getSubject(),
|
|
|
|
headers: n.decisionReplyBase.getHeaders(),
|
|
|
|
recipients: []recipientData{n.voteNotificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-21 00:25:49 +00:00
|
|
|
}
|
2017-04-21 09:31:32 +00:00
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
type notificationDirectVote struct {
|
2017-04-21 09:31:32 +00:00
|
|
|
voteNotificationBase
|
|
|
|
decisionReplyBase
|
|
|
|
voter Voter
|
|
|
|
vote Vote
|
|
|
|
}
|
|
|
|
|
2017-04-21 10:50:29 +00:00
|
|
|
func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
|
|
|
|
notification := ¬ificationDirectVote{voter: *voter, vote: *vote}
|
2017-04-21 09:31:32 +00:00
|
|
|
notification.decision = *decision
|
2021-01-09 14:49:19 +00:00
|
|
|
|
2017-04-21 09:31:32 +00:00
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
2021-01-09 14:49:19 +00:00
|
|
|
func (n *notificationDirectVote) GetNotificationContent() *NotificationContent {
|
|
|
|
return &NotificationContent{
|
2017-04-21 10:50:29 +00:00
|
|
|
template: "direct_vote_mail.txt",
|
|
|
|
data: struct {
|
|
|
|
Vote VoteChoice
|
|
|
|
Voter string
|
|
|
|
Decision *Decision
|
|
|
|
}{n.vote.Vote, n.voter.Name, &n.decision},
|
|
|
|
subject: n.decisionReplyBase.getSubject(),
|
|
|
|
headers: n.decisionReplyBase.getHeaders(),
|
|
|
|
recipients: []recipientData{n.voteNotificationBase.getRecipient()},
|
|
|
|
}
|
2017-04-21 09:31:32 +00:00
|
|
|
}
|