Implement direct voting
authorJan Dittberner <jandd@cacert.org>
Fri, 21 Apr 2017 09:31:32 +0000 (11:31 +0200)
committerJan Dittberner <jan@dittberner.info>
Fri, 21 Apr 2017 22:14:11 +0000 (00:14 +0200)
boardvoting.go
notifications.go
templates/direct_vote_form.html [new file with mode: 0644]
templates/direct_vote_mail.txt [new file with mode: 0644]

index d62b80e..e2e9838 100644 (file)
@@ -53,6 +53,7 @@ const (
        ctxVoter
        ctxDecision
        ctxVote
+       ctxAuthenticatedCert
 )
 
 func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
@@ -66,7 +67,9 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(ht
                                                return
                                        }
                                        if voter != nil {
-                                               handler(w, r.WithContext(context.WithValue(r.Context(), ctxVoter, voter)))
+                                               requestContext := context.WithValue(r.Context(), ctxVoter, voter)
+                                               requestContext = context.WithValue(requestContext, ctxAuthenticatedCert, cert)
+                                               handler(w, r.WithContext(requestContext))
                                                return
                                        }
                                }
@@ -475,12 +478,12 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        }
 }
 
-type voteHandler struct {
+type directVoteHandler struct {
        FlashMessageAction
        authenticationRequiredHandler
 }
 
-func (h *voteHandler) Handle(w http.ResponseWriter, r *http.Request) {
+func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
        decision, ok := getDecisionFromRequest(r)
        if !ok {
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -496,10 +499,37 @@ func (h *voteHandler) Handle(w http.ResponseWriter, r *http.Request) {
                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)
+       switch r.Method {
+       case http.MethodPost:
+               voteResult := &Vote{
+                       VoterId: voter.Id, Vote: vote, DecisionId: decision.Id, Voted: time.Now().UTC(),
+                       Notes: fmt.Sprintf("Direct Vote\n\n%s", getPEMClientCert(r))}
+               if err := voteResult.Save(); err != nil {
+                       logger.Println("ERROR", "Problem saving vote:", err)
+                       http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+                       return
+               }
+
+               NotifyMailChannel <- NewNotificationDirectVote(&decision.Decision, voter, voteResult)
+
+               if err := h.AddFlash(w, r, "Your vote has been registered."); err != nil {
+                       http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+                       return
+               }
+
+               http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
+       default:
+               templates := []string{"direct_vote_form.html", "header.html", "footer.html", "motion_fragments.html"}
+               var templateContext struct {
+                       Decision   *DecisionForDisplay
+                       VoteChoice VoteChoice
+                       PageTitle  string
+                       Flashes    interface{}
+               }
+               templateContext.Decision = decision
+               templateContext.VoteChoice = vote
+               renderTemplate(w, templates, templateContext)
+       }
 }
 
 type proxyVoteHandler struct {
@@ -509,7 +539,8 @@ type proxyVoteHandler struct {
 
 func getPEMClientCert(r *http.Request) string {
        clientCertPEM := bytes.NewBufferString("")
-       pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: r.TLS.PeerCertificates[0].Raw})
+       authenticatedCertificate := r.Context().Value(ctxAuthenticatedCert).(*x509.Certificate)
+       pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw})
        return clientCertPEM.String()
 }
 
@@ -604,13 +635,17 @@ func (h *decisionVoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
                        })
        case strings.HasPrefix(r.URL.Path, "/vote/"):
                parts := strings.Split(r.URL.Path[len("/vote/"):], "/")
+               if len(parts) != 2 {
+                       http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+                       return
+               }
                motionTag := parts[0]
                voteValue, ok := VoteValues[parts[1]]
                if !ok {
-                       http.NotFound(w, r)
+                       http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                        return
                }
-               handler := &voteHandler{}
+               handler := &directVoteHandler{}
                authenticateRequest(
                        w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
                        func(w http.ResponseWriter, r *http.Request) {
index 499a689..4a76710 100644 (file)
@@ -113,7 +113,7 @@ type NotificationCreateMotion struct {
 }
 
 func (n *NotificationCreateMotion) GetData() interface{} {
-       voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
+       voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
        return struct {
                *Decision
@@ -146,7 +146,7 @@ func NewNotificationUpdateMotion(decision Decision, voter Voter) *NotificationUp
 }
 
 func (n *NotificationUpdateMotion) GetData() interface{} {
-       voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
+       voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
        return struct {
                *Decision
@@ -250,3 +250,30 @@ func (n *NotificationProxyVote) GetTemplate() string { return "proxy_vote_mail.t
 func (n *NotificationProxyVote) GetSubject() string {
        return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
 }
+
+type NotificationDirectVote struct {
+       voteNotificationBase
+       decisionReplyBase
+       voter Voter
+       vote  Vote
+}
+
+func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) *NotificationDirectVote {
+       notification := &NotificationDirectVote{voter: *voter, vote: *vote}
+       notification.decision = *decision
+       return notification
+}
+
+func (n *NotificationDirectVote) GetData() interface{} {
+       return struct {
+               Vote     VoteChoice
+               Voter    string
+               Decision *Decision
+       }{n.vote.Vote, n.voter.Name, &n.decision}
+}
+
+func (n *NotificationDirectVote) GetTemplate() string { return "direct_vote_mail.txt" }
+
+func (n *NotificationDirectVote) GetSubject() string {
+       return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
+}
diff --git a/templates/direct_vote_form.html b/templates/direct_vote_form.html
new file mode 100644 (file)
index 0000000..3b656f2
--- /dev/null
@@ -0,0 +1,22 @@
+{{ template "header" . }}
+<a href="/motions/">Show all votes</a>
+<table class="list">
+    <thead>
+    <tr>
+        <th>Status</th>
+        <th>Motion</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr>
+        {{ with .Decision }}
+        {{ template "motion_fragment" .}}
+        {{ end}}
+    </tr>
+    </tbody>
+</table>
+
+<form action="/vote/{{ .Decision.Tag }}/{{ .VoteChoice }}" method="post">
+    <input type="submit" value="Vote {{ .VoteChoice }}"/>
+</form>
+{{ template "footer" . }}
\ No newline at end of file
diff --git a/templates/direct_vote_mail.txt b/templates/direct_vote_mail.txt
new file mode 100644 (file)
index 0000000..95bacb8
--- /dev/null
@@ -0,0 +1,10 @@
+Dear Board,
+
+{{ .Voter }} has just voted {{ .Vote }} on motion {{ .Decision.Tag }}.
+
+Motion:
+    {{ .Decision.Title }}
+    {{ .Decision.Content }}
+
+Kind regards,
+the vote system
\ No newline at end of file