cacert-boardvoting/notifications.go

347 lines
9 KiB
Go
Raw Normal View History

2019-07-31 15:30:58 +00:00
/*
Copyright 2017-2021 Jan Dittberner
2019-07-31 15:30:58 +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
http://www.apache.org/licenses/LICENSE-2.0
2019-07-31 15:30:58 +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
*/
package main
import (
"bytes"
"embed"
"fmt"
"text/template"
"github.com/Masterminds/sprig/v3"
"gopkg.in/mail.v2"
log "github.com/sirupsen/logrus"
)
type headerData struct {
name string
value []string
}
type headerList []headerData
type recipientData struct {
field, address, name string
}
type NotificationContent struct {
template string
data interface{}
subject string
headers headerList
recipients []recipientData
}
type NotificationMail interface {
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-21 00:25:49 +00:00
func MailNotifier(quitMailNotifier chan int) {
log.Info("Launched mail notifier")
for {
select {
2017-04-21 00:25:49 +00:00
case notification := <-NotifyMailChannel:
content := notification.GetNotificationContent()
mailText, err := buildMail(content.template, content.data)
if err != nil {
log.Errorf("building mail failed: %v", err)
continue
}
m := mail.NewMessage()
m.SetAddressHeader("From", config.NotificationSenderAddress, "CAcert board voting system")
for _, recipient := range content.recipients {
m.SetAddressHeader(recipient.field, recipient.address, recipient.name)
}
m.SetHeader("Subject", content.subject)
for _, header := range content.headers {
m.SetHeader(header.name, header.value...)
}
m.SetBody("text/plain", mailText.String())
d := mail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
log.Errorf("sending mail failed: %v", err)
}
case <-quitMailNotifier:
log.Info("Ending mail notifier")
return
}
}
}
//go:embed boardvoting/templates
var mailTemplates embed.FS
func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) {
b, err := mailTemplates.ReadFile(fmt.Sprintf("templates/%s", templateName))
2018-03-29 20:00:56 +00:00
if err != nil {
return
}
2018-03-29 20:00:56 +00:00
t, err := template.New(templateName).Funcs(sprig.GenericFuncMap()).Parse(string(b))
if err != nil {
return
}
mailText = bytes.NewBufferString("")
if err := t.Execute(mailText, context); err != nil {
return nil, fmt.Errorf(
"failed to execute template %s with context %+v: %w",
templateName, context, err,
)
}
return
}
2017-04-21 00:25:49 +00:00
type notificationBase struct{}
func (n *notificationBase) getRecipient() recipientData {
return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"}
2017-04-21 00:25:49 +00:00
}
type decisionReplyBase struct {
decision Decision
2017-04-21 00:25:49 +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)},
})
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
}
type notificationClosedDecision struct {
2017-04-21 00:25:49 +00:00
notificationBase
decisionReplyBase
voteSums VoteSums
reasoning string
}
func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail {
notification := &notificationClosedDecision{voteSums: *voteSums, reasoning: reasoning}
2017-04-21 00:25:49 +00:00
notification.decision = *decision
2017-04-21 00:25:49 +00:00
return notification
}
func (n *notificationClosedDecision) GetNotificationContent() *NotificationContent {
return &NotificationContent{
template: "closed_motion_mail.txt",
data: struct {
*Decision
*VoteSums
Reasoning string
}{&n.decision, &n.voteSums, n.reasoning},
subject: fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title),
headers: n.decisionReplyBase.getHeaders(),
recipients: []recipientData{n.notificationBase.getRecipient()},
}
}
type NotificationCreateMotion struct {
2017-04-21 00:25:49 +00:00
notificationBase
decision Decision
voter Voter
}
func (n *NotificationCreateMotion) GetNotificationContent() *NotificationContent {
2017-04-21 09:31:32 +00:00
voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
return &NotificationContent{
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()},
}
}
type notificationUpdateMotion struct {
2017-04-21 00:25:49 +00:00
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
notification := notificationUpdateMotion{voter: voter}
2017-04-21 00:25:49 +00:00
notification.decision = decision
2017-04-21 00:25:49 +00:00
return &notification
}
func (n *notificationUpdateMotion) GetNotificationContent() *NotificationContent {
2017-04-21 09:31:32 +00:00
voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
return &NotificationContent{
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()},
}
}
type notificationWithDrawMotion struct {
2017-04-21 00:25:49 +00:00
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
notification := &notificationWithDrawMotion{voter: *voter}
2017-04-21 00:25:49 +00:00
notification.decision = *decision
2017-04-21 00:25:49 +00:00
return notification
}
func (n *notificationWithDrawMotion) GetNotificationContent() *NotificationContent {
return &NotificationContent{
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-20 18:58:22 +00:00
type RemindVoterNotification struct {
voter Voter
decisions []Decision
}
func (n *RemindVoterNotification) GetNotificationContent() *NotificationContent {
return &NotificationContent{
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{}
func (n *voteNotificationBase) getRecipient() recipientData {
return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"}
2017-04-21 00:25:49 +00:00
}
type notificationProxyVote struct {
2017-04-21 00:25:49 +00:00
voteNotificationBase
decisionReplyBase
proxy Voter
voter Voter
vote Vote
justification string
}
func NewNotificationProxyVote(
decision *Decision,
proxy *Voter,
voter *Voter,
vote *Vote,
justification string,
) NotificationMail {
notification := &notificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
2017-04-21 00:25:49 +00:00
notification.decision = *decision
2017-04-21 00:25:49 +00:00
return notification
}
func (n *notificationProxyVote) GetNotificationContent() *NotificationContent {
return &NotificationContent{
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
type notificationDirectVote struct {
2017-04-21 09:31:32 +00:00
voteNotificationBase
decisionReplyBase
voter Voter
vote Vote
}
func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
notification := &notificationDirectVote{voter: *voter, vote: *vote}
2017-04-21 09:31:32 +00:00
notification.decision = *decision
2017-04-21 09:31:32 +00:00
return notification
}
func (n *notificationDirectVote) GetNotificationContent() *NotificationContent {
return &NotificationContent{
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
}