|
|
|
@ -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", "<br>", -1))
|
|
|
|
|
}
|
|
|
|
|
funcMaps[csrf.TemplateTag] = func() template.HTML {
|
|
|
|
|
return csrf.TemplateField(r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var baseTemplate *template.Template
|
|
|
|
|
|
|
|
|
@ -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)
|
|
|
|
@ -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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -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,6 +687,7 @@ 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"`
|
|
|
|
@ -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)
|
|
|
|
|