diff --git a/Gopkg.lock b/Gopkg.lock
index d28aa84..41e1cfc 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -25,6 +25,12 @@
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
+[[projects]]
+ name = "github.com/gorilla/csrf"
+ packages = ["."]
+ revision = "69581736821c33d85bbf378f42f6ad864dbd85de"
+ version = "v1.5"
+
[[projects]]
name = "github.com/gorilla/securecookie"
packages = ["."]
@@ -70,6 +76,12 @@
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
version = "v1"
+[[projects]]
+ name = "github.com/pkg/errors"
+ packages = ["."]
+ revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
+ version = "v0.8.0"
+
[[projects]]
branch = "master"
name = "github.com/rubenv/sql-migrate"
@@ -121,6 +133,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "c6097211865a7f2c761915a6166499eccdb244c65adf046866ac3baed8f6a838"
+ inputs-digest = "c886392b015af75b1a55b8f7ed686da847be1a2d340aa14c4183e6d273f94c65"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/boardvoting.go b/boardvoting.go
index a0d6359..e8a83e4 100644
--- a/boardvoting.go
+++ b/boardvoting.go
@@ -10,12 +10,6 @@ import (
"encoding/pem"
"flag"
"fmt"
- "git.cacert.org/cacert-boardvoting/boardvoting"
- "github.com/Masterminds/sprig"
- "github.com/gorilla/sessions"
- _ "github.com/mattn/go-sqlite3"
- "github.com/op/go-logging"
- "gopkg.in/yaml.v2"
"html/template"
"io/ioutil"
"net/http"
@@ -24,22 +18,35 @@ import (
"strconv"
"strings"
"time"
+
+ "github.com/Masterminds/sprig"
+ "github.com/gorilla/csrf"
+ "github.com/gorilla/sessions"
+ _ "github.com/mattn/go-sqlite3"
+ "github.com/op/go-logging"
+ "gopkg.in/yaml.v2"
+
+ "git.cacert.org/cacert-boardvoting/boardvoting"
)
var configFile string
var config *Config
var store *sessions.CookieStore
+var csrfKey []byte
var version = "undefined"
var build = "undefined"
var log *logging.Logger
const sessionCookieName = "votesession"
-func renderTemplate(w http.ResponseWriter, templates []string, context interface{}) {
+func renderTemplate(w http.ResponseWriter, r *http.Request, templates []string, context interface{}) {
funcMaps := sprig.FuncMap()
funcMaps["nl2br"] = func(text string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1))
}
+ funcMaps[csrf.TemplateTag] = func() template.HTML {
+ return csrf.TemplateField(r)
+ }
var baseTemplate *template.Template
@@ -67,7 +74,7 @@ func renderTemplate(w http.ResponseWriter, templates []string, context interface
type contextKey int
const (
- ctxNeedsAuth contextKey = iota
+ ctxNeedsAuth contextKey = iota
ctxVoter
ctxDecision
ctxVote
@@ -110,7 +117,7 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(ht
}
sort.Strings(templateContext.Emails)
w.WriteHeader(http.StatusForbidden)
- renderTemplate(w, []string{"denied.html", "header.html", "footer.html"}, templateContext)
+ renderTemplate(w, r, []string{"denied.html", "header.html", "footer.html"}, templateContext)
return
}
handler(w, r)
@@ -121,7 +128,7 @@ type motionParameters struct {
}
type motionListParameters struct {
- Page int64
+ Page int64
Flags struct {
Confirmed, Withdraw, Unvoted bool
}
@@ -194,7 +201,7 @@ func motionListHandler(w http.ResponseWriter, r *http.Request) {
templateContext.PrevPage = params.Page - 1
}
- renderTemplate(w, []string{"motions.html", "motion_fragments.html", "header.html", "footer.html"}, templateContext)
+ renderTemplate(w, r, []string{"motions.html", "motion_fragments.html", "header.html", "footer.html"}, templateContext)
}
func motionHandler(w http.ResponseWriter, r *http.Request) {
@@ -227,7 +234,7 @@ func motionHandler(w http.ResponseWriter, r *http.Request) {
}
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)
+ renderTemplate(w, r, []string{"motion.html", "motion_fragments.html", "header.html", "footer.html"}, templateContext)
}
func singleDecisionHandler(w http.ResponseWriter, r *http.Request, tag string, handler func(http.ResponseWriter, *http.Request)) {
@@ -307,6 +314,7 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
PageTitle string
Decision *DecisionForDisplay
Flashes interface{}
+ Voter *Voter
}
switch r.Method {
@@ -326,7 +334,8 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/motions/", http.StatusTemporaryRedirect)
default:
templateContext.Decision = decision
- renderTemplate(w, templates, templateContext)
+ templateContext.Voter = voter
+ renderTemplate(w, r, templates, templateContext)
}
}
@@ -359,7 +368,7 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
if valid, data := form.Validate(); !valid {
templateContext.Voter = voter
templateContext.Form = form
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
} else {
data.Proposed = time.Now().UTC()
data.ProponentId = voter.Id
@@ -382,7 +391,7 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
templateContext.Form = NewDecisionForm{
VoteType: strconv.FormatInt(voteTypeMotion, 10),
}
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
}
}
@@ -422,7 +431,7 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
if valid, data := form.Validate(); !valid {
templateContext.Voter = voter
templateContext.Form = form
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
} else {
data.Modified = time.Now().UTC()
if err := data.Update(); err != nil {
@@ -446,7 +455,7 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
VoteType: fmt.Sprintf("%d", decision.VoteType),
Decision: &decision.Decision,
}
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
}
}
@@ -521,7 +530,7 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
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))}
+ Notes: fmt.Sprintf("Direct Vote\n\n%s", getPEMClientCert(r))}
if err := voteResult.Save(); err != nil {
log.Errorf("Problem saving vote: %v", err)
http.Error(w, "Problem saving vote", http.StatusInternalServerError)
@@ -540,10 +549,12 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
VoteChoice VoteChoice
PageTitle string
Flashes interface{}
+ Voter *Voter
}
templateContext.Decision = decision
templateContext.VoteChoice = vote
- renderTemplate(w, templates, templateContext)
+ templateContext.Voter = voter
+ renderTemplate(w, r, templates, templateContext)
}
}
@@ -577,7 +588,9 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
Voters *[]Voter
PageTitle string
Flashes interface{}
+ Voter *Voter
}
+ templateContext.Voter = proxy
switch r.Method {
case http.MethodPost:
form := ProxyVoteForm{
@@ -595,7 +608,7 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
} else {
templateContext.Voters = voters
}
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
} else {
data.DecisionId = decision.Id
data.Voted = time.Now().UTC()
@@ -625,7 +638,7 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
} else {
templateContext.Voters = voters
}
- renderTemplate(w, templates, templateContext)
+ renderTemplate(w, r, templates, templateContext)
}
}
@@ -674,11 +687,12 @@ type Config struct {
ServerCert string `yaml:"server_certificate"`
ServerKey string `yaml:"server_key"`
CookieSecret string `yaml:"cookie_secret"`
+ CsrfKey string `yaml:"csrf_key"`
BaseURL string `yaml:"base_url"`
MigrationsPath string `yaml:"migrations_path"`
HttpAddress string `yaml:"http_address"`
HttpsAddress string `yaml:"https_address"`
- MailServer struct {
+ MailServer struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"mail_server"`
@@ -724,10 +738,10 @@ func readConfig() {
}
if config.HttpsAddress == "" {
- config.HttpsAddress = ":8443"
+ config.HttpsAddress = "127.0.0.1:8443"
}
if config.HttpAddress == "" {
- config.HttpAddress = ":8080"
+ config.HttpAddress = "127.0.0.1:8080"
}
cookieSecret, err := base64.StdEncoding.DecodeString(config.CookieSecret)
@@ -738,6 +752,13 @@ func readConfig() {
if len(cookieSecret) < 32 {
log.Panic("Cookie secret is less than 32 bytes long")
}
+ csrfKey, err = base64.StdEncoding.DecodeString(config.CsrfKey)
+ if err != nil {
+ log.Panicf("Decoding csrf key failed: %v", err)
+ }
+ if len(csrfKey) != 32 {
+ log.Panicf("CSRF key must be exactly 32 bytes long but is %d bytes long", len(csrfKey))
+ }
store = sessions.NewCookieStore(cookieSecret)
log.Info("Read configuration")
}
@@ -838,6 +859,8 @@ func main() {
TLSConfig: tlsConfig,
}
+ server.Handler = csrf.Protect(csrfKey)(http.DefaultServeMux)
+
log.Infof("Launching application on https://%s/", server.Addr)
errs := make(chan error, 1)
diff --git a/boardvoting/templates/create_motion_form.html b/boardvoting/templates/create_motion_form.html
index 5958aa7..ef72ec6 100644
--- a/boardvoting/templates/create_motion_form.html
+++ b/boardvoting/templates/create_motion_form.html
@@ -9,6 +9,7 @@