diff options
Diffstat (limited to 'boardvoting.go')
-rw-r--r-- | boardvoting.go | 188 |
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)) |