You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cacert-boardvoting/notifications.go

317 lines
9.0 KiB
Go

/*
Copyright 2017-2019 Jan Dittberner
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
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.
*/
package main
import (
"bytes"
"fmt"
"git.cacert.org/cacert-boardvoting/boardvoting"
"github.com/Masterminds/sprig"
"gopkg.in/gomail.v2"
"text/template"
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
}
var NotifyMailChannel = make(chan NotificationMail, 1)
func MailNotifier(quitMailNotifier chan int) {
log.Info("Launched mail notifier")
for {
select {
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 := gomail.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 := gomail.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
}
}
}
func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) {
b, err := boardvoting.Asset(fmt.Sprintf("templates/%s", templateName))
if err != nil {
return
}
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 {
log.Errorf("Failed to execute template %s with context %+v: %v", templateName, context, err)
return nil, err
}
return
}
type notificationBase struct{}
func (n *notificationBase) getRecipient() recipientData {
return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"}
}
type decisionReplyBase struct {
decision Decision
}
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)
}
type notificationClosedDecision struct {
notificationBase
decisionReplyBase
voteSums VoteSums
reasoning string
}
func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail {
notification := &notificationClosedDecision{voteSums: *voteSums, reasoning: reasoning}
notification.decision = *decision
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 {
notificationBase
decision Decision
voter Voter
}
func (n *NotificationCreateMotion) GetNotificationContent() *notificationContent {
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 {
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
notification := notificationUpdateMotion{voter: voter}
notification.decision = decision
return &notification
}
func (n *notificationUpdateMotion) GetNotificationContent() *notificationContent {
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 {
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
notification := &notificationWithDrawMotion{voter: *voter}
notification.decision = *decision
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()},
}
}
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}},
}
}
type voteNotificationBase struct{}
func (n *voteNotificationBase) getRecipient() recipientData {
return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"}
}
type notificationProxyVote struct {
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}
notification.decision = *decision
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()},
}
}
type notificationDirectVote struct {
voteNotificationBase
decisionReplyBase
voter Voter
vote Vote
}
func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
notification := &notificationDirectVote{voter: *voter, vote: *vote}
notification.decision = *decision
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()},
}
}