summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Dittberner <jandd@cacert.org>2017-04-21 11:31:32 +0200
committerJan Dittberner <jan@dittberner.info>2017-04-22 00:14:11 +0200
commit8d1f18e16dcf44001155e8dff273f82900899d76 (patch)
tree6a7ed46e5ffb339cc316f9d862f6584d2aa82776
parent2cac50ee86e70428aa06c99f5e009ceaaf1dbc13 (diff)
downloadcacert-boardvoting-8d1f18e16dcf44001155e8dff273f82900899d76.tar.gz
cacert-boardvoting-8d1f18e16dcf44001155e8dff273f82900899d76.tar.xz
cacert-boardvoting-8d1f18e16dcf44001155e8dff273f82900899d76.zip
Implement direct voting
-rw-r--r--boardvoting.go55
-rw-r--r--notifications.go31
-rw-r--r--templates/direct_vote_form.html22
-rw-r--r--templates/direct_vote_mail.txt10
4 files changed, 106 insertions, 12 deletions
diff --git a/boardvoting.go b/boardvoting.go
index d62b80e..e2e9838 100644
--- a/boardvoting.go
+++ b/boardvoting.go
@@ -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) {
diff --git a/notifications.go b/notifications.go
index 499a689..4a76710 100644
--- a/notifications.go
+++ b/notifications.go
@@ -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
index 0000000..3b656f2
--- /dev/null
+++ b/templates/direct_vote_form.html
@@ -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
index 0000000..95bacb8
--- /dev/null
+++ b/templates/direct_vote_mail.txt
@@ -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