Switch to context API

debian
Jan Dittberner 8 years ago
parent 6fe515ea52
commit e0be1a6aa5

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "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 _, cert := range r.TLS.PeerCertificates {
for _, extKeyUsage := range cert.ExtKeyUsage { for _, extKeyUsage := range cert.ExtKeyUsage {
if extKeyUsage == x509.ExtKeyUsageClientAuth { if extKeyUsage == x509.ExtKeyUsageClientAuth {
@ -46,19 +55,20 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, authRequired bo
return return
} }
if voter != nil { if voter != nil {
handler(w, r, voter) handler(w, r.WithContext(context.WithValue(r.Context(), ctxVoter, voter)))
return return
} }
} }
} }
} }
} }
if authRequired { needsAuth, ok := r.Context().Value(ctxNeedsAuth).(bool)
if ok && needsAuth {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
renderTemplate(w, []string{"denied.html"}, nil) renderTemplate(w, []string{"denied.html"}, nil)
return return
} }
handler(w, r, nil) handler(w, r)
} }
type motionParameters struct { type motionParameters struct {
@ -96,74 +106,86 @@ func parseMotionListParameters(r *http.Request) motionListParameters {
return m return m
} }
func motionListHandler(w http.ResponseWriter, r *http.Request, voter *Voter) { func motionListHandler(w http.ResponseWriter, r *http.Request) {
params := parseMotionListParameters(r) params := parseMotionListParameters(r)
var context struct { var templateContext struct {
Decisions []*DecisionForDisplay Decisions []*DecisionForDisplay
Voter *Voter Voter *Voter
Params *motionListParameters Params *motionListParameters
PrevPage, NextPage int64 PrevPage, NextPage int64
PageTitle string PageTitle string
} }
context.Voter = voter if voter, ok := r.Context().Value(ctxVoter).(*Voter); ok {
context.Params = &params templateContext.Voter = voter
}
templateContext.Params = &params
var err error var err error
if params.Flags.Unvoted { if params.Flags.Unvoted && templateContext.Voter != nil {
if context.Decisions, err = FindVotersUnvotedDecisionsForDisplayOnPage(params.Page, voter); err != nil { if templateContext.Decisions, err = FindVotersUnvotedDecisionsForDisplayOnPage(
params.Page, templateContext.Voter); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
} else { } 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) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
} }
if len(context.Decisions) > 0 { if len(templateContext.Decisions) > 0 {
olderExists, err := context.Decisions[len(context.Decisions)-1].OlderExists() olderExists, err := templateContext.Decisions[len(templateContext.Decisions)-1].OlderExists()
if err != nil { if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
if olderExists { if olderExists {
context.NextPage = params.Page + 1 templateContext.NextPage = params.Page + 1
} }
} }
if 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) 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 Decision *DecisionForDisplay
Voter *Voter Voter *Voter
Params *motionParameters Params *motionParameters
PrevPage, NextPage int64 PrevPage, NextPage int64
PageTitle string PageTitle string
} }
context.Voter = voter voter, ok := getVoterFromRequest(r)
context.Params = &params if ok {
templateContext.Voter = voter
}
templateContext.Params = &params
if params.ShowVotes { if params.ShowVotes {
if err := decision.LoadVotes(); err != nil { if err := decision.LoadVotes(); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
} }
context.Decision = decision templateContext.Decision = decision
context.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title) templateContext.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title)
renderTemplate(w, []string{"motion.html", "motion_fragments.html", "header.html", "footer.html"}, context) 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) decision, err := FindDecisionForDisplayByTag(tag)
if err != nil { if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 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) http.NotFound(w, r)
return return
} }
handler(w, r, v, decision) handler(w, r.WithContext(context.WithValue(r.Context(), ctxDecision, decision)))
} }
type motionsHandler struct{}
type motionActionHandler interface { type motionActionHandler interface {
Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) Handle(w http.ResponseWriter, r *http.Request)
NeedsAuth() bool NeedsAuth() bool
} }
type withDrawMotionAction struct{} type authenticationRequiredHandler struct{}
func (withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) { func (authenticationRequiredHandler) NeedsAuth() bool {
fmt.Fprintln(w, "Withdraw motion", decision.Tag) return true
// TODO: implement }
if r.Method == http.MethodPost {
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 { if confirm, err := strconv.ParseBool(r.PostFormValue("confirm")); err != nil {
log.Println("could not parse confirm parameter:", err) log.Println("could not parse confirm parameter:", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
@ -197,23 +242,28 @@ func (withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter
} else { } else {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 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 { type editMotionAction struct {
return true authenticationRequiredHandler
} }
type editMotionAction struct{} func (editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
decision, ok := getDecisionFromRequest(r)
func (editMotionAction) Handle(w http.ResponseWriter, r *http.Request, voter *Voter, decision *DecisionForDisplay) { if !ok || decision.Status != voteStatusPending {
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
return
}
fmt.Fprintln(w, "Edit motion", decision.Tag) fmt.Fprintln(w, "Edit motion", decision.Tag)
// TODO: implement // TODO: implement
} }
func (editMotionAction) NeedsAuth() bool { type motionsHandler struct{}
return true
}
func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := db.Ping(); err != nil { if err := db.Ping(); err != nil {
@ -229,7 +279,7 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch { switch {
case subURL == "": case subURL == "":
authenticateRequest(w, r, false, motionListHandler) authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)), motionListHandler)
return return
case strings.Count(subURL, "/") == 1: case strings.Count(subURL, "/") == 1:
parts := strings.Split(subURL, "/") parts := strings.Split(subURL, "/")
@ -240,15 +290,17 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
authenticateRequest(w, r, action.NeedsAuth(), func(w http.ResponseWriter, r *http.Request, v *Voter) { authenticateRequest(
singleDecisionHandler(w, r, v, motionTag, action.Handle) 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) logger.Printf("motion: %s, action: %s\n", motionTag, action)
return return
case strings.Count(subURL, "/") == 0: case strings.Count(subURL, "/") == 0:
authenticateRequest(w, r, false, func(w http.ResponseWriter, r *http.Request, v *Voter) { authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)),
singleDecisionHandler(w, r, v, subURL, motionHandler) func(w http.ResponseWriter, r *http.Request) {
singleDecisionHandler(w, r, subURL, motionHandler)
}) })
return return
default: default:
@ -257,8 +309,10 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
func newMotionHandler(w http.ResponseWriter, _ *http.Request, _ *Voter) { func newMotionHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w,"New motion") fmt.Fprintln(w, "New motion")
voter, _ := getVoterFromRequest(r)
fmt.Fprintf(w, "%+v\n", voter)
// TODO: implement // TODO: implement
} }
@ -301,7 +355,7 @@ func main() {
http.Handle("/motions/", http.StripPrefix("/motions/", motionsHandler{})) http.Handle("/motions/", http.StripPrefix("/motions/", motionsHandler{}))
http.HandleFunc("/newmotion/", func(w http.ResponseWriter, r *http.Request) { 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("/static/", http.FileServer(http.Dir(".")))
http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently)) http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently))
@ -319,7 +373,7 @@ func main() {
// setup HTTPS server // setup HTTPS server
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
ClientCAs: caCertPool, ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert, ClientAuth: tls.VerifyClientCertIfGiven,
} }
tlsConfig.BuildNameToCertificate() tlsConfig.BuildNameToCertificate()

@ -25,7 +25,7 @@ FROM decisions
JOIN voters ON decisions.proponent=voters.id JOIN voters ON decisions.proponent=voters.id
WHERE decisions.tag=$1;` WHERE decisions.tag=$1;`
sqlGetVoter = ` sqlGetVoter = `
SELECT voters.id, voters.name SELECT voters.id, voters.name, voters.enabled, voters.reminder
FROM voters FROM voters
JOIN emails ON voters.id=emails.voter JOIN emails ON voters.id=emails.voter
WHERE emails.address=$1 AND voters.enabled=1` 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 { func (v VoteStatus) String() string {
switch v { switch v {
case -1: case voteStatusDeclined:
return "declined" return "declined"
case 0: case voteStatusPending:
return "pending" return "pending"
case 1: case voteStatusApproved:
return "approved" return "approved"
case -2: case voteStatusWithdrawn:
return "withdrawn" return "withdrawn"
default: default:
return "unknown" return "unknown"
@ -240,7 +247,7 @@ func FindVotersUnvotedDecisionsForDisplayOnPage(page int64, voter *Voter) (decis
} }
defer decisionsStmt.Close() defer decisionsStmt.Close()
rows, err := decisionsStmt.Queryx(page - 1, voter.Id) rows, err := decisionsStmt.Queryx(page-1, voter.Id)
if err != nil { if err != nil {
logger.Printf("Error loading motions for page %d: %v\n", page, err) logger.Printf("Error loading motions for page %d: %v\n", page, err)
return return

Loading…
Cancel
Save