summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Dittberner <jan@dittberner.info>2017-04-17 22:56:20 +0200
committerJan Dittberner <jan@dittberner.info>2017-04-22 00:12:24 +0200
commite0be1a6aa5fe580794e1f967f1417e3878d17321 (patch)
treedef1487e2e4adc2d369e2b19932e6eacee278fdd
parent6fe515ea52493ea79a07efe9e1fc652dea272e32 (diff)
downloadcacert-boardvoting-e0be1a6aa5fe580794e1f967f1417e3878d17321.tar.gz
cacert-boardvoting-e0be1a6aa5fe580794e1f967f1417e3878d17321.tar.xz
cacert-boardvoting-e0be1a6aa5fe580794e1f967f1417e3878d17321.zip
Switch to context API
-rw-r--r--actions.go4
-rw-r--r--boardvoting.go164
-rw-r--r--models.go19
3 files changed, 124 insertions, 63 deletions
diff --git a/actions.go b/actions.go
index fde767c..f41e1ff 100644
--- a/actions.go
+++ b/actions.go
@@ -10,8 +10,8 @@ func WithdrawMotion(decision *Decision, voter *Voter) (err error) {
// load template, fill name, tag, title, content
type mailContext struct {
*Decision
- Name string
- Sender string
+ Name string
+ Sender string
Recipient string
}
context := mailContext{decision, voter.Name, config.NoticeSenderAddress, config.BoardMailAddress}
diff --git a/boardvoting.go b/boardvoting.go
index 77499a2..1fe031e 100644
--- a/boardvoting.go
+++ b/boardvoting.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"crypto/tls"
"crypto/x509"
"fmt"
@@ -35,7 +36,15 @@ func renderTemplate(w http.ResponseWriter, tmpl []string, context interface{}) {
}
}
-func authenticateRequest(w http.ResponseWriter, r *http.Request, authRequired bool, handler func(http.ResponseWriter, *http.Request, *Voter)) {
+type contextKey int
+
+const (
+ ctxNeedsAuth contextKey = iota
+ ctxVoter contextKey = iota
+ ctxDecision contextKey = iota
+)
+
+func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
for _, cert := range r.TLS.PeerCertificates {
for _, extKeyUsage := range cert.ExtKeyUsage {
if extKeyUsage == x509.ExtKeyUsageClientAuth {
@@ -46,19 +55,20 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, authRequired bo
return
}
if voter != nil {
- handler(w, r, voter)
+ handler(w, r.WithContext(context.WithValue(r.Context(), ctxVoter, voter)))
return
}
}
}
}
}
- if authRequired {
+ needsAuth, ok := r.Context().Value(ctxNeedsAuth).(bool)
+ if ok && needsAuth {
w.WriteHeader(http.StatusForbidden)
renderTemplate(w, []string{"denied.html"}, nil)
return
}
- handler(w, r, nil)
+ handler(w, r)
}
type motionParameters struct {
@@ -66,7 +76,7 @@ type motionParameters struct {
}
type motionListParameters struct {
- Page int64
+ Page int64
Flags struct {
Confirmed, Withdraw, Unvoted bool
}
@@ -96,74 +106,86 @@ func parseMotionListParameters(r *http.Request) motionListParameters {
return m
}
-func motionListHandler(w http.ResponseWriter, r *http.Request, voter *Voter) {
+func motionListHandler(w http.ResponseWriter, r *http.Request) {
params := parseMotionListParameters(r)
- var context struct {
+ var templateContext struct {
Decisions []*DecisionForDisplay
Voter *Voter
Params *motionListParameters
PrevPage, NextPage int64
PageTitle string
}
- context.Voter = voter
- context.Params = &params
+ if voter, ok := r.Context().Value(ctxVoter).(*Voter); ok {
+ templateContext.Voter = voter
+ }
+ templateContext.Params = &params
var err error
- if params.Flags.Unvoted {
- if context.Decisions, err = FindVotersUnvotedDecisionsForDisplayOnPage(params.Page, voter); err != nil {
+ if params.Flags.Unvoted && templateContext.Voter != nil {
+ if templateContext.Decisions, err = FindVotersUnvotedDecisionsForDisplayOnPage(
+ params.Page, templateContext.Voter); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
} else {
- if context.Decisions, err = FindDecisionsForDisplayOnPage(params.Page); err != nil {
+ if templateContext.Decisions, err = FindDecisionsForDisplayOnPage(params.Page); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
- if len(context.Decisions) > 0 {
- olderExists, err := context.Decisions[len(context.Decisions)-1].OlderExists()
+ if len(templateContext.Decisions) > 0 {
+ olderExists, err := templateContext.Decisions[len(templateContext.Decisions)-1].OlderExists()
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if olderExists {
- context.NextPage = params.Page + 1
+ templateContext.NextPage = params.Page + 1
}
}
if params.Page > 1 {
- context.PrevPage = params.Page - 1
+ templateContext.PrevPage = params.Page - 1
}
- renderTemplate(w, []string{"motions.html", "motion_fragments.html", "header.html", "footer.html"}, context)
+ renderTemplate(w, []string{"motions.html", "motion_fragments.html", "header.html", "footer.html"}, templateContext)
}
-func motionHandler(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) {
+func motionHandler(w http.ResponseWriter, r *http.Request) {
params := parseMotionParameters(r)
- var context struct {
+ decision, ok := getDecisionFromRequest(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
+ return
+ }
+
+ var templateContext struct {
Decision *DecisionForDisplay
Voter *Voter
Params *motionParameters
PrevPage, NextPage int64
PageTitle string
}
- context.Voter = voter
- context.Params = &params
+ voter, ok := getVoterFromRequest(r)
+ if ok {
+ templateContext.Voter = voter
+ }
+ templateContext.Params = &params
if params.ShowVotes {
if err := decision.LoadVotes(); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
- context.Decision = decision
- context.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title)
- renderTemplate(w, []string{"motion.html", "motion_fragments.html", "header.html", "footer.html"}, context)
+ templateContext.Decision = decision
+ templateContext.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title)
+ renderTemplate(w, []string{"motion.html", "motion_fragments.html", "header.html", "footer.html"}, templateContext)
}
-func singleDecisionHandler(w http.ResponseWriter, r *http.Request, v *Voter, tag string, handler func(http.ResponseWriter, *http.Request, *Voter, *DecisionForDisplay)) {
+func singleDecisionHandler(w http.ResponseWriter, r *http.Request, tag string, handler func(http.ResponseWriter, *http.Request)) {
decision, err := FindDecisionForDisplayByTag(tag)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -173,22 +195,45 @@ func singleDecisionHandler(w http.ResponseWriter, r *http.Request, v *Voter, tag
http.NotFound(w, r)
return
}
- handler(w, r, v, decision)
+ handler(w, r.WithContext(context.WithValue(r.Context(), ctxDecision, decision)))
}
-type motionsHandler struct{}
-
type motionActionHandler interface {
- Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay)
+ Handle(w http.ResponseWriter, r *http.Request)
NeedsAuth() bool
}
-type withDrawMotionAction struct{}
+type authenticationRequiredHandler struct{}
-func (withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) {
- fmt.Fprintln(w, "Withdraw motion", decision.Tag)
- // TODO: implement
- if r.Method == http.MethodPost {
+func (authenticationRequiredHandler) NeedsAuth() bool {
+ return true
+}
+
+type withDrawMotionAction struct {
+ authenticationRequiredHandler
+}
+
+func getVoterFromRequest(r *http.Request) (voter *Voter, ok bool) {
+ voter, ok = r.Context().Value(ctxVoter).(*Voter)
+ return
+}
+
+func getDecisionFromRequest(r *http.Request) (decision *DecisionForDisplay, ok bool) {
+ decision, ok = r.Context().Value(ctxDecision).(*DecisionForDisplay)
+ return
+}
+
+func (withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
+ voter, voter_ok := getVoterFromRequest(r)
+ decision, decision_ok := getDecisionFromRequest(r)
+
+ if !voter_ok || !decision_ok || decision.Status != voteStatusPending {
+ http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
+ return
+ }
+
+ switch r.Method {
+ case http.MethodPost:
if confirm, err := strconv.ParseBool(r.PostFormValue("confirm")); err != nil {
log.Println("could not parse confirm parameter:", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
@@ -197,23 +242,28 @@ func (withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter
} else {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
+ http.Redirect(w, r, "/motions/", http.StatusTemporaryRedirect)
+ return
+ default:
+ fmt.Fprintln(w, "Withdraw motion", decision.Tag)
}
}
-func (withDrawMotionAction) NeedsAuth() bool {
- return true
+type editMotionAction struct {
+ authenticationRequiredHandler
}
-type editMotionAction struct{}
-
-func (editMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) {
+func (editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
+ decision, ok := getDecisionFromRequest(r)
+ if !ok || decision.Status != voteStatusPending {
+ http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
+ return
+ }
fmt.Fprintln(w, "Edit motion", decision.Tag)
// TODO: implement
}
-func (editMotionAction) NeedsAuth() bool {
- return true
-}
+type motionsHandler struct{}
func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := db.Ping(); err != nil {
@@ -224,12 +274,12 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var motionActionMap = map[string]motionActionHandler{
"withdraw": withDrawMotionAction{},
- "edit": editMotionAction{},
+ "edit": editMotionAction{},
}
switch {
case subURL == "":
- authenticateRequest(w, r, false, motionListHandler)
+ authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)), motionListHandler)
return
case strings.Count(subURL, "/") == 1:
parts := strings.Split(subURL, "/")
@@ -240,16 +290,18 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
return
}
- authenticateRequest(w, r, action.NeedsAuth(), func(w http.ResponseWriter, r *http.Request, v *Voter) {
- singleDecisionHandler(w, r, v, motionTag, action.Handle)
- })
-
+ authenticateRequest(
+ w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, action.NeedsAuth())),
+ func(w http.ResponseWriter, r *http.Request) {
+ singleDecisionHandler(w, r, motionTag, action.Handle)
+ })
logger.Printf("motion: %s, action: %s\n", motionTag, action)
return
case strings.Count(subURL, "/") == 0:
- authenticateRequest(w, r, false, func(w http.ResponseWriter, r *http.Request, v *Voter) {
- singleDecisionHandler(w, r, v, subURL, motionHandler)
- })
+ authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)),
+ func(w http.ResponseWriter, r *http.Request) {
+ singleDecisionHandler(w, r, subURL, motionHandler)
+ })
return
default:
fmt.Fprintf(w, "No handler for '%s'", subURL)
@@ -257,8 +309,10 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
-func newMotionHandler(w http.ResponseWriter, _ *http.Request, _ *Voter) {
- fmt.Fprintln(w,"New motion")
+func newMotionHandler(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "New motion")
+ voter, _ := getVoterFromRequest(r)
+ fmt.Fprintf(w, "%+v\n", voter)
// TODO: implement
}
@@ -301,7 +355,7 @@ func main() {
http.Handle("/motions/", http.StripPrefix("/motions/", motionsHandler{}))
http.HandleFunc("/newmotion/", func(w http.ResponseWriter, r *http.Request) {
- authenticateRequest(w, r, true, newMotionHandler)
+ authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)), newMotionHandler)
})
http.Handle("/static/", http.FileServer(http.Dir(".")))
http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently))
@@ -319,7 +373,7 @@ func main() {
// setup HTTPS server
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
- ClientAuth: tls.RequireAndVerifyClientCert,
+ ClientAuth: tls.VerifyClientCertIfGiven,
}
tlsConfig.BuildNameToCertificate()
diff --git a/models.go b/models.go
index fe6cd23..46d669e 100644
--- a/models.go
+++ b/models.go
@@ -25,7 +25,7 @@ FROM decisions
JOIN voters ON decisions.proponent=voters.id
WHERE decisions.tag=$1;`
sqlGetVoter = `
-SELECT voters.id, voters.name
+SELECT voters.id, voters.name, voters.enabled, voters.reminder
FROM voters
JOIN emails ON voters.id=emails.voter
WHERE emails.address=$1 AND voters.enabled=1`
@@ -139,15 +139,22 @@ func (v VoteChoice) String() string {
}
}
+const (
+ voteStatusDeclined = -1
+ voteStatusPending = 0
+ voteStatusApproved = 1
+ voteStatusWithdrawn = -2
+)
+
func (v VoteStatus) String() string {
switch v {
- case -1:
+ case voteStatusDeclined:
return "declined"
- case 0:
+ case voteStatusPending:
return "pending"
- case 1:
+ case voteStatusApproved:
return "approved"
- case -2:
+ case voteStatusWithdrawn:
return "withdrawn"
default:
return "unknown"
@@ -240,7 +247,7 @@ func FindVotersUnvotedDecisionsForDisplayOnPage(page int64, voter *Voter) (decis
}
defer decisionsStmt.Close()
- rows, err := decisionsStmt.Queryx(page - 1, voter.Id)
+ rows, err := decisionsStmt.Queryx(page-1, voter.Id)
if err != nil {
logger.Printf("Error loading motions for page %d: %v\n", page, err)
return