summaryrefslogtreecommitdiff
path: root/boardvoting.go
diff options
context:
space:
mode:
Diffstat (limited to 'boardvoting.go')
-rw-r--r--boardvoting.go188
1 files changed, 173 insertions, 15 deletions
diff --git a/boardvoting.go b/boardvoting.go
index 152d969..d62b80e 100644
--- a/boardvoting.go
+++ b/boardvoting.go
@@ -1,10 +1,12 @@
package main
import (
+ "bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
+ "encoding/pem"
"fmt"
"github.com/Masterminds/sprig"
"github.com/gorilla/sessions"
@@ -50,6 +52,7 @@ const (
ctxNeedsAuth contextKey = iota
ctxVoter
ctxDecision
+ ctxVote
)
func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
@@ -227,6 +230,11 @@ func getDecisionFromRequest(r *http.Request) (decision *DecisionForDisplay, ok b
return
}
+func getVoteFromRequest(r *http.Request) (vote VoteChoice, ok bool) {
+ vote, ok = r.Context().Value(ctxVote).(VoteChoice)
+ return
+}
+
type FlashMessageAction struct{}
func (a *FlashMessageAction) AddFlash(w http.ResponseWriter, r *http.Request, message interface{}, tags ...string) (err error) {
@@ -277,7 +285,7 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
return
}
- notifyMail <- &NotificationWithDrawMotion{decision: decision.Decision, voter: *voter}
+ NotifyMailChannel <- NewNotificationWithDrawMotion(&(decision.Decision), voter)
if err := a.AddFlash(w, r, fmt.Sprintf("Motion %s has been withdrawn!", decision.Tag)); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -330,7 +338,7 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
return
}
- notifyMail <- &NotificationCreateMotion{decision: *data, voter: *voter}
+ NotifyMailChannel <- &NotificationCreateMotion{decision: *data, voter: *voter}
if err := h.AddFlash(w, r, "The motion has been proposed!"); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -395,7 +403,7 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
return
}
- notifyMail <- &NotificationUpdateMotion{decision: *data, voter: *voter}
+ NotifyMailChannel <- NewNotificationUpdateMotion(*data, *voter)
if err := a.AddFlash(w, r, "The motion has been modified!"); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -467,17 +475,164 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
+type voteHandler struct {
+ FlashMessageAction
+ authenticationRequiredHandler
+}
+
+func (h *voteHandler) Handle(w http.ResponseWriter, r *http.Request) {
+ decision, ok := getDecisionFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ voter, ok := getVoterFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ vote, ok := getVoteFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ fmt.Fprintln(w, "to be implemented")
+ fmt.Fprintln(w, "Decision:", decision)
+ fmt.Fprintln(w, "Voter:", voter)
+ fmt.Fprintln(w, "Vote:", vote)
+}
+
+type proxyVoteHandler struct {
+ FlashMessageAction
+ authenticationRequiredHandler
+}
+
+func getPEMClientCert(r *http.Request) string {
+ clientCertPEM := bytes.NewBufferString("")
+ pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: r.TLS.PeerCertificates[0].Raw})
+ return clientCertPEM.String()
+}
+
+func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
+ decision, ok := getDecisionFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ proxy, ok := getVoterFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ templates := []string{"proxy_vote_form.html", "header.html", "footer.html", "motion_fragments.html"}
+ var templateContext struct {
+ Form ProxyVoteForm
+ Decision *DecisionForDisplay
+ Voters *[]Voter
+ PageTitle string
+ Flashes interface{}
+ }
+ switch r.Method {
+ case http.MethodPost:
+ form := ProxyVoteForm{
+ Voter: r.FormValue("Voter"),
+ Vote: r.FormValue("Vote"),
+ Justification: r.FormValue("Justification"),
+ }
+
+ if valid, voter, data, justification := form.Validate(); !valid {
+ templateContext.Form = form
+ templateContext.Decision = decision
+ if voters, err := GetVotersForProxy(proxy, &decision.Decision); err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ } else {
+ templateContext.Voters = voters
+ }
+ renderTemplate(w, templates, templateContext)
+ } else {
+ data.DecisionId = decision.Id
+ data.Voted = time.Now().UTC()
+ data.Notes = fmt.Sprintf(
+ "Proxy-Vote by %s\n\n%s\n\n%s",
+ proxy.Name, justification, getPEMClientCert(r))
+
+ if err := data.Save(); err != nil {
+ logger.Println("Error saving vote:", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+
+ NotifyMailChannel <- NewNotificationProxyVote(&decision.Decision, proxy, voter, data, justification)
+
+ if err := h.AddFlash(w, r, "The vote has been registered."); err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
+ }
+ return
+ default:
+ templateContext.Form = ProxyVoteForm{}
+ templateContext.Decision = decision
+ if voters, err := GetVotersForProxy(proxy, &decision.Decision); err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ } else {
+ templateContext.Voters = voters
+ }
+ renderTemplate(w, templates, templateContext)
+ }
+}
+
+type decisionVoteHandler struct{}
+
+func (h *decisionVoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if err := db.Ping(); err != nil {
+ logger.Fatal(err)
+ }
+
+ switch {
+ case strings.HasPrefix(r.URL.Path, "/proxy/"):
+ motionTag := r.URL.Path[len("/proxy/"):]
+ handler := &proxyVoteHandler{}
+ authenticateRequest(
+ w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
+ func(w http.ResponseWriter, r *http.Request) {
+ singleDecisionHandler(w, r, motionTag, handler.Handle)
+ })
+ case strings.HasPrefix(r.URL.Path, "/vote/"):
+ parts := strings.Split(r.URL.Path[len("/vote/"):], "/")
+ motionTag := parts[0]
+ voteValue, ok := VoteValues[parts[1]]
+ if !ok {
+ http.NotFound(w, r)
+ return
+ }
+ handler := &voteHandler{}
+ authenticateRequest(
+ w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
+ func(w http.ResponseWriter, r *http.Request) {
+ singleDecisionHandler(
+ w, r.WithContext(context.WithValue(r.Context(), ctxVote, voteValue)),
+ motionTag, handler.Handle)
+ })
+ return
+ }
+}
+
type Config struct {
- BoardMailAddress string `yaml:"board_mail_address"`
- NoticeSenderAddress string `yaml:"notice_sender_address"`
- ReminderSenderAddress string `yaml:"reminder_sender_address"`
- DatabaseFile string `yaml:"database_file"`
- ClientCACertificates string `yaml:"client_ca_certificates"`
- ServerCert string `yaml:"server_certificate"`
- ServerKey string `yaml:"server_key"`
- CookieSecret string `yaml:"cookie_secret"`
- BaseURL string `yaml:"base_url"`
- MailServer struct {
+ BoardMailAddress string `yaml:"board_mail_address"`
+ VoteNoticeAddress string `yaml:"notice_sender_address"`
+ NotificationSenderAddress string `yaml:"reminder_sender_address"`
+ DatabaseFile string `yaml:"database_file"`
+ ClientCACertificates string `yaml:"client_ca_certificates"`
+ ServerCert string `yaml:"server_certificate"`
+ ServerKey string `yaml:"server_key"`
+ CookieSecret string `yaml:"cookie_secret"`
+ BaseURL string `yaml:"base_url"`
+ MailServer struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"mail_server"`
@@ -516,8 +671,9 @@ func main() {
defer db.Close()
- go MailNotifier()
- defer CloseMailNotifier()
+ quitMailChannel := make(chan int)
+ go MailNotifier(quitMailChannel)
+ defer func() { quitMailChannel <- 1 }()
quitChannel := make(chan int)
go JobScheduler(quitChannel)
@@ -525,6 +681,8 @@ func main() {
http.Handle("/motions/", http.StripPrefix("/motions/", motionsHandler{}))
http.Handle("/newmotion/", motionsHandler{})
+ http.Handle("/proxy/", &decisionVoteHandler{})
+ http.Handle("/vote/", &decisionVoteHandler{})
http.Handle("/static/", http.FileServer(http.Dir(".")))
http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently))