|
|
@ -1,18 +1,20 @@
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
Copyright 2017-2020 Jan Dittberner
|
|
|
|
Copyright 2017-2021 Jan Dittberner
|
|
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this program except in compliance with the License.
|
|
|
|
you may not use this program except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The CAcert board voting software.
|
|
|
|
package main
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
@ -51,12 +53,21 @@ var csrfKey []byte
|
|
|
|
var version = "undefined"
|
|
|
|
var version = "undefined"
|
|
|
|
var build = "undefined"
|
|
|
|
var build = "undefined"
|
|
|
|
|
|
|
|
|
|
|
|
const sessionCookieName = "votesession"
|
|
|
|
const (
|
|
|
|
|
|
|
|
cookieSecretMinLen = 32
|
|
|
|
|
|
|
|
csrfKeyLength = 32
|
|
|
|
|
|
|
|
httpIdleTimeout = 5
|
|
|
|
|
|
|
|
httpReadHeaderTimeout = 10
|
|
|
|
|
|
|
|
httpReadTimeout = 10
|
|
|
|
|
|
|
|
httpWriteTimeout = 60
|
|
|
|
|
|
|
|
sessionCookieName = "votesession"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func renderTemplate(w http.ResponseWriter, r *http.Request, templates []string, context interface{}) {
|
|
|
|
func renderTemplate(w http.ResponseWriter, r *http.Request, templates []string, context interface{}) {
|
|
|
|
funcMaps := sprig.FuncMap()
|
|
|
|
funcMaps := sprig.FuncMap()
|
|
|
|
funcMaps["nl2br"] = func(text string) template.HTML {
|
|
|
|
funcMaps["nl2br"] = func(text string) template.HTML {
|
|
|
|
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
|
|
|
|
// #nosec G203 input is sanitized
|
|
|
|
|
|
|
|
return template.HTML(strings.ReplaceAll(template.HTMLEscapeString(text), "\n", "<br>"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
funcMaps[csrf.TemplateTag] = func() template.HTML {
|
|
|
|
funcMaps[csrf.TemplateTag] = func() template.HTML {
|
|
|
|
return csrf.TemplateField(r)
|
|
|
|
return csrf.TemplateField(r)
|
|
|
@ -65,20 +76,24 @@ func renderTemplate(w http.ResponseWriter, r *http.Request, templates []string,
|
|
|
|
var baseTemplate *template.Template
|
|
|
|
var baseTemplate *template.Template
|
|
|
|
|
|
|
|
|
|
|
|
for count, t := range templates {
|
|
|
|
for count, t := range templates {
|
|
|
|
var err error
|
|
|
|
var (
|
|
|
|
var assetBytes []byte
|
|
|
|
err error
|
|
|
|
if //noinspection GoUnresolvedReference
|
|
|
|
assetBytes []byte
|
|
|
|
assetBytes, err = boardvoting.Asset(fmt.Sprintf("templates/%s", t)); err != nil {
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if assetBytes, err = boardvoting.Asset(fmt.Sprintf("templates/%s", t)); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if count == 0 {
|
|
|
|
|
|
|
|
if baseTemplate, err = template.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
|
|
|
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if count == 0 {
|
|
|
|
if _, err := baseTemplate.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
|
|
|
|
if baseTemplate, err = template.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if _, err := baseTemplate.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil {
|
|
|
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -100,27 +115,35 @@ const (
|
|
|
|
|
|
|
|
|
|
|
|
func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
|
|
|
|
func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
|
|
|
|
emailsTried := make(map[string]bool)
|
|
|
|
emailsTried := make(map[string]bool)
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
for _, emailAddress := range cert.EmailAddresses {
|
|
|
|
continue
|
|
|
|
emailLower := strings.ToLower(emailAddress)
|
|
|
|
}
|
|
|
|
emailsTried[emailLower] = true
|
|
|
|
|
|
|
|
voter, err := FindVoterByAddress(emailLower)
|
|
|
|
for _, emailAddress := range cert.EmailAddresses {
|
|
|
|
if err != nil {
|
|
|
|
emailLower := strings.ToLower(emailAddress)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
emailsTried[emailLower] = true
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
voter, err := FindVoterByAddress(emailLower)
|
|
|
|
if voter != nil {
|
|
|
|
if err != nil {
|
|
|
|
requestContext := context.WithValue(r.Context(), ctxVoter, voter)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
requestContext = context.WithValue(requestContext, ctxAuthenticatedCert, cert)
|
|
|
|
|
|
|
|
handler(w, r.WithContext(requestContext))
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if voter != nil {
|
|
|
|
|
|
|
|
requestContext := context.WithValue(r.Context(), ctxVoter, voter)
|
|
|
|
|
|
|
|
requestContext = context.WithValue(requestContext, ctxAuthenticatedCert, cert)
|
|
|
|
|
|
|
|
handler(w, r.WithContext(requestContext))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
needsAuth, ok := r.Context().Value(ctxNeedsAuth).(bool)
|
|
|
|
needsAuth, ok := r.Context().Value(ctxNeedsAuth).(bool)
|
|
|
|
if ok && needsAuth {
|
|
|
|
if ok && needsAuth {
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
@ -129,14 +152,18 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(ht
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
Emails []string
|
|
|
|
Emails []string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for k := range emailsTried {
|
|
|
|
for k := range emailsTried {
|
|
|
|
templateContext.Emails = append(templateContext.Emails, k)
|
|
|
|
templateContext.Emails = append(templateContext.Emails, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sort.Strings(templateContext.Emails)
|
|
|
|
sort.Strings(templateContext.Emails)
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
renderTemplate(w, r, []string{"denied.html", "header.html", "footer.html"}, templateContext)
|
|
|
|
renderTemplate(w, r, []string{"denied.html", "header.html", "footer.html"}, templateContext)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handler(w, r)
|
|
|
|
handler(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -154,6 +181,7 @@ type motionListParameters struct {
|
|
|
|
func parseMotionParameters(r *http.Request) motionParameters {
|
|
|
|
func parseMotionParameters(r *http.Request) motionParameters {
|
|
|
|
var m = motionParameters{}
|
|
|
|
var m = motionParameters{}
|
|
|
|
m.ShowVotes, _ = strconv.ParseBool(r.URL.Query().Get("showvotes"))
|
|
|
|
m.ShowVotes, _ = strconv.ParseBool(r.URL.Query().Get("showvotes"))
|
|
|
|
|
|
|
|
|
|
|
|
return m
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -164,20 +192,24 @@ func parseMotionListParameters(r *http.Request) motionListParameters {
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
m.Page = page
|
|
|
|
m.Page = page
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m.Flags.Withdraw, _ = strconv.ParseBool(r.URL.Query().Get("withdraw"))
|
|
|
|
m.Flags.Withdraw, _ = strconv.ParseBool(r.URL.Query().Get("withdraw"))
|
|
|
|
m.Flags.Unvoted, _ = strconv.ParseBool(r.URL.Query().Get("unvoted"))
|
|
|
|
m.Flags.Unvoted, _ = strconv.ParseBool(r.URL.Query().Get("unvoted"))
|
|
|
|
|
|
|
|
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
m.Flags.Confirmed, _ = strconv.ParseBool(r.PostFormValue("confirm"))
|
|
|
|
m.Flags.Confirmed, _ = strconv.ParseBool(r.PostFormValue("confirm"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return m
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func motionListHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func motionListHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
params := parseMotionListParameters(r)
|
|
|
|
params := parseMotionListParameters(r)
|
|
|
|
|
|
|
|
|
|
|
|
session, err := store.Get(r, sessionCookieName)
|
|
|
|
session, err := store.Get(r, sessionCookieName)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -189,30 +221,42 @@ func motionListHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
PageTitle string
|
|
|
|
PageTitle string
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if voter, ok := r.Context().Value(ctxVoter).(*Voter); ok {
|
|
|
|
if voter, ok := r.Context().Value(ctxVoter).(*Voter); ok {
|
|
|
|
templateContext.Voter = voter
|
|
|
|
templateContext.Voter = voter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if flashes := session.Flashes(); len(flashes) > 0 {
|
|
|
|
if flashes := session.Flashes(); len(flashes) > 0 {
|
|
|
|
templateContext.Flashes = flashes
|
|
|
|
templateContext.Flashes = flashes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = session.Save(r, w)
|
|
|
|
err = session.Save(r, w)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Params = ¶ms
|
|
|
|
templateContext.Params = ¶ms
|
|
|
|
|
|
|
|
|
|
|
|
if templateContext.Decisions, err = FindDecisionsForDisplayOnPage(params.Page, params.Flags.Unvoted, templateContext.Voter); err != nil {
|
|
|
|
if templateContext.Decisions, err = FindDecisionsForDisplayOnPage(
|
|
|
|
|
|
|
|
params.Page, params.Flags.Unvoted, templateContext.Voter,
|
|
|
|
|
|
|
|
); err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if len(templateContext.Decisions) > 0 {
|
|
|
|
if len(templateContext.Decisions) > 0 {
|
|
|
|
olderExists, err := templateContext.Decisions[len(templateContext.Decisions)-1].OlderExists(params.Flags.Unvoted, templateContext.Voter)
|
|
|
|
olderExists, err := templateContext.Decisions[len(templateContext.Decisions)-1].OlderExists(
|
|
|
|
|
|
|
|
params.Flags.Unvoted, templateContext.Voter,
|
|
|
|
|
|
|
|
)
|
|
|
|
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 {
|
|
|
|
templateContext.NextPage = params.Page + 1
|
|
|
|
templateContext.NextPage = params.Page + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -233,6 +277,7 @@ func motionHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -244,32 +289,53 @@ func motionHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
PageTitle string
|
|
|
|
PageTitle string
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
if ok {
|
|
|
|
if ok {
|
|
|
|
templateContext.Voter = voter
|
|
|
|
templateContext.Voter = voter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Params = ¶ms
|
|
|
|
templateContext.Params = ¶ms
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title)
|
|
|
|
templateContext.PageTitle = fmt.Sprintf("Motion %s: %s", decision.Tag, decision.Title)
|
|
|
|
renderTemplate(w, r, []string{"motion.html", "motion_fragments.html", "page_fragments.html", "header.html", "footer.html"}, templateContext)
|
|
|
|
|
|
|
|
|
|
|
|
renderTemplate(w, r, []string{
|
|
|
|
|
|
|
|
"motion.html",
|
|
|
|
|
|
|
|
"motion_fragments.html",
|
|
|
|
|
|
|
|
"page_fragments.html",
|
|
|
|
|
|
|
|
"header.html",
|
|
|
|
|
|
|
|
"footer.html",
|
|
|
|
|
|
|
|
}, templateContext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func singleDecisionHandler(w http.ResponseWriter, r *http.Request, tag string, handler func(http.ResponseWriter, *http.Request)) {
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if decision == nil {
|
|
|
|
if decision == nil {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handler(w, r.WithContext(context.WithValue(r.Context(), ctxDecision, decision)))
|
|
|
|
handler(w, r.WithContext(context.WithValue(r.Context(), ctxDecision, decision)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -286,16 +352,19 @@ func (authenticationRequiredHandler) NeedsAuth() bool {
|
|
|
|
|
|
|
|
|
|
|
|
func getVoterFromRequest(r *http.Request) (voter *Voter, ok bool) {
|
|
|
|
func getVoterFromRequest(r *http.Request) (voter *Voter, ok bool) {
|
|
|
|
voter, ok = r.Context().Value(ctxVoter).(*Voter)
|
|
|
|
voter, ok = r.Context().Value(ctxVoter).(*Voter)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getDecisionFromRequest(r *http.Request) (decision *DecisionForDisplay, ok bool) {
|
|
|
|
func getDecisionFromRequest(r *http.Request) (decision *DecisionForDisplay, ok bool) {
|
|
|
|
decision, ok = r.Context().Value(ctxDecision).(*DecisionForDisplay)
|
|
|
|
decision, ok = r.Context().Value(ctxDecision).(*DecisionForDisplay)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getVoteFromRequest(r *http.Request) (vote VoteChoice, ok bool) {
|
|
|
|
func getVoteFromRequest(r *http.Request) (vote VoteChoice, ok bool) {
|
|
|
|
vote, ok = r.Context().Value(ctxVote).(VoteChoice)
|
|
|
|
vote, ok = r.Context().Value(ctxVote).(VoteChoice)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -305,12 +374,16 @@ func (a *FlashMessageAction) AddFlash(w http.ResponseWriter, r *http.Request, me
|
|
|
|
session, err := store.Get(r, sessionCookieName)
|
|
|
|
session, err := store.Get(r, sessionCookieName)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("could not get session cookie: %v", err)
|
|
|
|
log.Warnf("could not get session cookie: %v", err)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
session.AddFlash(message, tags...)
|
|
|
|
session.AddFlash(message, tags...)
|
|
|
|
|
|
|
|
|
|
|
|
err = session.Save(r, w)
|
|
|
|
err = session.Save(r, w)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("could not save flash message: %v", err)
|
|
|
|
log.Warnf("could not save flash message: %v", err)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -324,14 +397,25 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
if !ok || decision.Status != voteStatusPending {
|
|
|
|
if !ok || decision.Status != voteStatusPending {
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
templates := []string{"withdraw_motion_form.html", "header.html", "footer.html", "motion_fragments.html", "page_fragments.html"}
|
|
|
|
|
|
|
|
|
|
|
|
templates := []string{
|
|
|
|
|
|
|
|
"withdraw_motion_form.html",
|
|
|
|
|
|
|
|
"header.html",
|
|
|
|
|
|
|
|
"footer.html",
|
|
|
|
|
|
|
|
"motion_fragments.html",
|
|
|
|
|
|
|
|
"page_fragments.html",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
|
PageTitle string
|
|
|
|
PageTitle string
|
|
|
|
Decision *DecisionForDisplay
|
|
|
|
Decision *DecisionForDisplay
|
|
|
@ -343,9 +427,11 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
case http.MethodPost:
|
|
|
|
case http.MethodPost:
|
|
|
|
decision.Status = voteStatusWithdrawn
|
|
|
|
decision.Status = voteStatusWithdrawn
|
|
|
|
decision.Modified = time.Now().UTC()
|
|
|
|
decision.Modified = time.Now().UTC()
|
|
|
|
|
|
|
|
|
|
|
|
if err := decision.UpdateStatus(); err != nil {
|
|
|
|
if err := decision.UpdateStatus(); err != nil {
|
|
|
|
log.Errorf("withdrawing motion failed: %v", err)
|
|
|
|
log.Errorf("withdrawing motion failed: %v", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -372,12 +458,14 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templates := []string{"create_motion_form.html", "page_fragments.html", "header.html", "footer.html"}
|
|
|
|
templates := []string{"create_motion_form.html", "page_fragments.html", "header.html", "footer.html"}
|
|
|
|
|
|
|
|
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
|
Form NewDecisionForm
|
|
|
|
Form NewDecisionForm
|
|
|
|
PageTitle string
|
|
|
|
PageTitle string
|
|
|
|
Voter *Voter
|
|
|
|
Voter *Voter
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
case http.MethodPost:
|
|
|
|
form := NewDecisionForm{
|
|
|
|
form := NewDecisionForm{
|
|
|
@ -393,10 +481,11 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
data.Proposed = time.Now().UTC()
|
|
|
|
data.Proposed = time.Now().UTC()
|
|
|
|
data.ProponentId = voter.Id
|
|
|
|
data.ProponentID = voter.ID
|
|
|
|
if err := data.Create(); err != nil {
|
|
|
|
if err := data.Create(); err != nil {
|
|
|
|
log.Errorf("saving motion failed: %v", err)
|
|
|
|
log.Errorf("saving motion failed: %v", err)
|
|
|
|
http.Error(w, "Saving motion failed", http.StatusInternalServerError)
|
|
|
|
http.Error(w, "Saving motion failed", http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -426,20 +515,26 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
if !ok || decision.Status != voteStatusPending {
|
|
|
|
if !ok || decision.Status != voteStatusPending {
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
http.Error(w, http.StatusText(http.StatusPreconditionFailed), http.StatusPreconditionFailed)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templates := []string{"edit_motion_form.html", "page_fragments.html", "header.html", "footer.html"}
|
|
|
|
templates := []string{"edit_motion_form.html", "page_fragments.html", "header.html", "footer.html"}
|
|
|
|
|
|
|
|
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
|
Form EditDecisionForm
|
|
|
|
Form EditDecisionForm
|
|
|
|
PageTitle string
|
|
|
|
PageTitle string
|
|
|
|
Voter *Voter
|
|
|
|
Voter *Voter
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
case http.MethodPost:
|
|
|
|
form := EditDecisionForm{
|
|
|
|
form := EditDecisionForm{
|
|
|
@ -459,6 +554,7 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if err := data.Update(); err != nil {
|
|
|
|
if err := data.Update(); err != nil {
|
|
|
|
log.Errorf("updating motion failed: %v", err)
|
|
|
|
log.Errorf("updating motion failed: %v", err)
|
|
|
|
http.Error(w, "Updating the motion failed.", http.StatusInternalServerError)
|
|
|
|
http.Error(w, "Updating the motion failed.", http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -468,6 +564,7 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
templateContext.Voter = voter
|
|
|
|
templateContext.Voter = voter
|
|
|
@ -494,35 +591,43 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
switch {
|
|
|
|
switch {
|
|
|
|
case subURL == "":
|
|
|
|
case subURL == "":
|
|
|
|
authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)), motionListHandler)
|
|
|
|
authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)), motionListHandler)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
case subURL == "/newmotion/":
|
|
|
|
case subURL == "/newmotion/":
|
|
|
|
handler := &newMotionHandler{}
|
|
|
|
handler := &newMotionHandler{}
|
|
|
|
authenticateRequest(
|
|
|
|
authenticateRequest(
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
handler.Handle)
|
|
|
|
handler.Handle)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
case strings.Count(subURL, "/") == 1:
|
|
|
|
case strings.Count(subURL, "/") == 1:
|
|
|
|
parts := strings.Split(subURL, "/")
|
|
|
|
parts := strings.Split(subURL, "/")
|
|
|
|
motionTag := parts[0]
|
|
|
|
motionTag := parts[0]
|
|
|
|
|
|
|
|
|
|
|
|
action, ok := motionActionMap[parts[1]]
|
|
|
|
action, ok := motionActionMap[parts[1]]
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
authenticateRequest(
|
|
|
|
authenticateRequest(
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, action.NeedsAuth())),
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, action.NeedsAuth())),
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
singleDecisionHandler(w, r, motionTag, action.Handle)
|
|
|
|
singleDecisionHandler(w, r, motionTag, action.Handle)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
case strings.Count(subURL, "/") == 0:
|
|
|
|
case strings.Count(subURL, "/") == 0:
|
|
|
|
authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)),
|
|
|
|
authenticateRequest(w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, false)),
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
singleDecisionHandler(w, r, subURL, motionHandler)
|
|
|
|
singleDecisionHandler(w, r, subURL, motionHandler)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
http.NotFound(w, r)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -536,26 +641,33 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
voter, ok := getVoterFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
vote, ok := getVoteFromRequest(r)
|
|
|
|
vote, ok := getVoteFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
case http.MethodPost:
|
|
|
|
voteResult := &Vote{
|
|
|
|
voteResult := &Vote{
|
|
|
|
VoterId: voter.Id, Vote: vote, DecisionId: decision.Id, Voted: time.Now().UTC(),
|
|
|
|
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 {
|
|
|
|
if err := voteResult.Save(); err != nil {
|
|
|
|
log.Errorf("Problem saving vote: %v", err)
|
|
|
|
log.Errorf("Problem saving vote: %v", err)
|
|
|
|
http.Error(w, "Problem saving vote", http.StatusInternalServerError)
|
|
|
|
http.Error(w, "Problem saving vote", http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -565,7 +677,14 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
templates := []string{"direct_vote_form.html", "header.html", "footer.html", "motion_fragments.html", "page_fragments.html"}
|
|
|
|
templates := []string{
|
|
|
|
|
|
|
|
"direct_vote_form.html",
|
|
|
|
|
|
|
|
"header.html",
|
|
|
|
|
|
|
|
"footer.html",
|
|
|
|
|
|
|
|
"motion_fragments.html",
|
|
|
|
|
|
|
|
"page_fragments.html",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
|
Decision *DecisionForDisplay
|
|
|
|
Decision *DecisionForDisplay
|
|
|
|
VoteChoice VoteChoice
|
|
|
|
VoteChoice VoteChoice
|
|
|
@ -573,9 +692,11 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
Voter *Voter
|
|
|
|
Voter *Voter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.VoteChoice = vote
|
|
|
|
templateContext.VoteChoice = vote
|
|
|
|
templateContext.Voter = voter
|
|
|
|
templateContext.Voter = voter
|
|
|
|
|
|
|
|
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -588,10 +709,12 @@ type proxyVoteHandler struct {
|
|
|
|
func getPEMClientCert(r *http.Request) string {
|
|
|
|
func getPEMClientCert(r *http.Request) string {
|
|
|
|
clientCertPEM := bytes.NewBufferString("")
|
|
|
|
clientCertPEM := bytes.NewBufferString("")
|
|
|
|
authenticatedCertificate := r.Context().Value(ctxAuthenticatedCert).(*x509.Certificate)
|
|
|
|
authenticatedCertificate := r.Context().Value(ctxAuthenticatedCert).(*x509.Certificate)
|
|
|
|
|
|
|
|
|
|
|
|
err := pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw})
|
|
|
|
err := pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw})
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error encoding client certificate: %v", err)
|
|
|
|
log.Errorf("error encoding client certificate: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return clientCertPEM.String()
|
|
|
|
return clientCertPEM.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -599,14 +722,25 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
decision, ok := getDecisionFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
proxy, ok := getVoterFromRequest(r)
|
|
|
|
proxy, ok := getVoterFromRequest(r)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
templates := []string{"proxy_vote_form.html", "header.html", "footer.html", "motion_fragments.html", "page_fragments.html"}
|
|
|
|
|
|
|
|
|
|
|
|
templates := []string{
|
|
|
|
|
|
|
|
"proxy_vote_form.html",
|
|
|
|
|
|
|
|
"header.html",
|
|
|
|
|
|
|
|
"footer.html",
|
|
|
|
|
|
|
|
"motion_fragments.html",
|
|
|
|
|
|
|
|
"page_fragments.html",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var templateContext struct {
|
|
|
|
var templateContext struct {
|
|
|
|
Form ProxyVoteForm
|
|
|
|
Form ProxyVoteForm
|
|
|
|
Decision *DecisionForDisplay
|
|
|
|
Decision *DecisionForDisplay
|
|
|
@ -615,7 +749,9 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
Flashes interface{}
|
|
|
|
Flashes interface{}
|
|
|
|
Voter *Voter
|
|
|
|
Voter *Voter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Voter = proxy
|
|
|
|
templateContext.Voter = proxy
|
|
|
|
|
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
switch r.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
case http.MethodPost:
|
|
|
|
form := ProxyVoteForm{
|
|
|
|
form := ProxyVoteForm{
|
|
|
@ -627,15 +763,19 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if valid, voter, data, justification := form.Validate(); !valid {
|
|
|
|
if valid, voter, data, justification := form.Validate(); !valid {
|
|
|
|
templateContext.Form = form
|
|
|
|
templateContext.Form = form
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.Decision = decision
|
|
|
|
if voters, err := GetVotersForProxy(proxy); err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
voters, err := GetVotersForProxy(proxy)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
|
|
|
|
templateContext.Voters = voters
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Voters = voters
|
|
|
|
|
|
|
|
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
data.DecisionId = decision.Id
|
|
|
|
data.DecisionID = decision.ID
|
|
|
|
data.Voted = time.Now().UTC()
|
|
|
|
data.Voted = time.Now().UTC()
|
|
|
|
data.Notes = fmt.Sprintf(
|
|
|
|
data.Notes = fmt.Sprintf(
|
|
|
|
"Proxy-Vote by %s\n\n%s\n\n%s",
|
|
|
|
"Proxy-Vote by %s\n\n%s\n\n%s",
|
|
|
@ -644,6 +784,7 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if err := data.Save(); err != nil {
|
|
|
|
if err := data.Save(); err != nil {
|
|
|
|
log.Errorf("Error saving vote: %s", err)
|
|
|
|
log.Errorf("Error saving vote: %s", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -653,16 +794,21 @@ func (h *proxyVoteHandler) Handle(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
http.Redirect(w, r, "/motions/", http.StatusMovedPermanently)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
templateContext.Form = ProxyVoteForm{}
|
|
|
|
templateContext.Form = ProxyVoteForm{}
|
|
|
|
templateContext.Decision = decision
|
|
|
|
templateContext.Decision = decision
|
|
|
|
if voters, err := GetVotersForProxy(proxy); err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
voters, err := GetVotersForProxy(proxy)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
|
|
|
|
templateContext.Voters = voters
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templateContext.Voters = voters
|
|
|
|
|
|
|
|
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
renderTemplate(w, r, templates, templateContext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -674,24 +820,33 @@ func (h *decisionVoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/proxy/"):
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/proxy/"):
|
|
|
|
motionTag := r.URL.Path[len("/proxy/"):]
|
|
|
|
motionTag := r.URL.Path[len("/proxy/"):]
|
|
|
|
handler := &proxyVoteHandler{}
|
|
|
|
handler := &proxyVoteHandler{}
|
|
|
|
|
|
|
|
|
|
|
|
authenticateRequest(
|
|
|
|
authenticateRequest(
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
singleDecisionHandler(w, r, motionTag, handler.Handle)
|
|
|
|
singleDecisionHandler(w, r, motionTag, handler.Handle)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/vote/"):
|
|
|
|
case strings.HasPrefix(r.URL.Path, "/vote/"):
|
|
|
|
|
|
|
|
const expectedParts = 2
|
|
|
|
|
|
|
|
|
|
|
|
parts := strings.Split(r.URL.Path[len("/vote/"):], "/")
|
|
|
|
parts := strings.Split(r.URL.Path[len("/vote/"):], "/")
|
|
|
|
if len(parts) != 2 {
|
|
|
|
if len(parts) != expectedParts {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
motionTag := parts[0]
|
|
|
|
motionTag := parts[0]
|
|
|
|
|
|
|
|
|
|
|
|
voteValue, ok := VoteValues[parts[1]]
|
|
|
|
voteValue, ok := VoteValues[parts[1]]
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handler := &directVoteHandler{}
|
|
|
|
handler := &directVoteHandler{}
|
|
|
|
|
|
|
|
|
|
|
|
authenticateRequest(
|
|
|
|
authenticateRequest(
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)),
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
|
@ -699,6 +854,7 @@ func (h *decisionVoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxVote, voteValue)),
|
|
|
|
w, r.WithContext(context.WithValue(r.Context(), ctxVote, voteValue)),
|
|
|
|
motionTag, handler.Handle)
|
|
|
|
motionTag, handler.Handle)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -714,8 +870,8 @@ type Config struct {
|
|
|
|
CookieSecret string `yaml:"cookie_secret"`
|
|
|
|
CookieSecret string `yaml:"cookie_secret"`
|
|
|
|
CsrfKey string `yaml:"csrf_key"`
|
|
|
|
CsrfKey string `yaml:"csrf_key"`
|
|
|
|
BaseURL string `yaml:"base_url"`
|
|
|
|
BaseURL string `yaml:"base_url"`
|
|
|
|
HttpAddress string `yaml:"http_address"`
|
|
|
|
HTTPAddress string `yaml:"http_address"`
|
|
|
|
HttpsAddress string `yaml:"https_address"`
|
|
|
|
HTTPSAddress string `yaml:"https_address"`
|
|
|
|
MailServer struct {
|
|
|
|
MailServer struct {
|
|
|
|
Host string `yaml:"host"`
|
|
|
|
Host string `yaml:"host"`
|
|
|
|
Port int `yaml:"port"`
|
|
|
|
Port int `yaml:"port"`
|
|
|
@ -727,15 +883,17 @@ func readConfig() {
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf("Opening configuration file failed: %v", err)
|
|
|
|
log.Panicf("Opening configuration file failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := yaml.Unmarshal(source, &config); err != nil {
|
|
|
|
if err := yaml.Unmarshal(source, &config); err != nil {
|
|
|
|
log.Panicf("Loading configuration failed: %v", err)
|
|
|
|
log.Panicf("Loading configuration failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if config.HttpsAddress == "" {
|
|
|
|
if config.HTTPSAddress == "" {
|
|
|
|
config.HttpsAddress = "127.0.0.1:8443"
|
|
|
|
config.HTTPSAddress = "127.0.0.1:8443"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if config.HttpAddress == "" {
|
|
|
|
|
|
|
|
config.HttpAddress = "127.0.0.1:8080"
|
|
|
|
if config.HTTPAddress == "" {
|
|
|
|
|
|
|
|
config.HTTPAddress = "127.0.0.1:8080"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cookieSecret, err := base64.StdEncoding.DecodeString(config.CookieSecret)
|
|
|
|
cookieSecret, err := base64.StdEncoding.DecodeString(config.CookieSecret)
|
|
|
@ -743,17 +901,26 @@ func readConfig() {
|
|
|
|
log.Panicf("Decoding cookie secret failed: %v", err)
|
|
|
|
log.Panicf("Decoding cookie secret failed: %v", err)
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(cookieSecret) < 32 {
|
|
|
|
|
|
|
|
log.Panic("Cookie secret is less than 32 bytes long")
|
|
|
|
if len(cookieSecret) < cookieSecretMinLen {
|
|
|
|
|
|
|
|
log.Panicf("Cookie secret is less than %d bytes long", cookieSecretMinLen)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
csrfKey, err = base64.StdEncoding.DecodeString(config.CsrfKey)
|
|
|
|
csrfKey, err = base64.StdEncoding.DecodeString(config.CsrfKey)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf("Decoding csrf key failed: %v", err)
|
|
|
|
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))
|
|
|
|
if len(csrfKey) != csrfKeyLength {
|
|
|
|
|
|
|
|
log.Panicf(
|
|
|
|
|
|
|
|
"CSRF key must be exactly %d bytes long but is %d bytes long",
|
|
|
|
|
|
|
|
csrfKeyLength,
|
|
|
|
|
|
|
|
len(csrfKey),
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
store = sessions.NewCookieStore(cookieSecret)
|
|
|
|
store = sessions.NewCookieStore(cookieSecret)
|
|
|
|
|
|
|
|
|
|
|
|
log.Info("Read configuration")
|
|
|
|
log.Info("Read configuration")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -762,6 +929,7 @@ func setupDbConfig(ctx context.Context) {
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf("Opening database failed: %v", err)
|
|
|
|
log.Panicf("Opening database failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
db = NewDB(database)
|
|
|
|
db = NewDB(database)
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
go func() {
|
|
|
@ -812,6 +980,7 @@ func setupTLSConfig() (tlsConfig *tls.Config) {
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf("Error reading client certificate CAs %v", err)
|
|
|
|
log.Panicf("Error reading client certificate CAs %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
caCertPool := x509.NewCertPool()
|
|
|
|
caCertPool := x509.NewCertPool()
|
|
|
|
if !caCertPool.AppendCertsFromPEM(caCert) {
|
|
|
|
if !caCertPool.AppendCertsFromPEM(caCert) {
|
|
|
|
log.Panic("could not initialize client CA certificate pool")
|
|
|
|
log.Panic("could not initialize client CA certificate pool")
|
|
|
@ -819,9 +988,11 @@ func setupTLSConfig() (tlsConfig *tls.Config) {
|
|
|
|
|
|
|
|
|
|
|
|
// setup HTTPS server
|
|
|
|
// setup HTTPS server
|
|
|
|
tlsConfig = &tls.Config{
|
|
|
|
tlsConfig = &tls.Config{
|
|
|
|
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
ClientCAs: caCertPool,
|
|
|
|
ClientCAs: caCertPool,
|
|
|
|
ClientAuth: tls.VerifyClientCertIfGiven,
|
|
|
|
ClientAuth: tls.VerifyClientCertIfGiven,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -835,23 +1006,26 @@ func main() {
|
|
|
|
flag.Parse()
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
|
|
var stopAll func()
|
|
|
|
var stopAll func()
|
|
|
|
|
|
|
|
|
|
|
|
executionContext, stopAll := context.WithCancel(context.Background())
|
|
|
|
executionContext, stopAll := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
|
|
readConfig()
|
|
|
|
readConfig()
|
|
|
|
setupDbConfig(executionContext)
|
|
|
|
setupDbConfig(executionContext)
|
|
|
|
setupNotifications(executionContext)
|
|
|
|
setupNotifications(executionContext)
|
|
|
|
setupJobs(executionContext)
|
|
|
|
setupJobs(executionContext)
|
|
|
|
setupHandlers()
|
|
|
|
setupHandlers()
|
|
|
|
|
|
|
|
|
|
|
|
tlsConfig := setupTLSConfig()
|
|
|
|
tlsConfig := setupTLSConfig()
|
|
|
|
|
|
|
|
|
|
|
|
defer stopAll()
|
|
|
|
defer stopAll()
|
|
|
|
|
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
server := &http.Server{
|
|
|
|
Addr: config.HttpsAddress,
|
|
|
|
Addr: config.HTTPSAddress,
|
|
|
|
TLSConfig: tlsConfig,
|
|
|
|
TLSConfig: tlsConfig,
|
|
|
|
IdleTimeout: time.Second * 120,
|
|
|
|
IdleTimeout: time.Second * httpIdleTimeout,
|
|
|
|
ReadHeaderTimeout: time.Second * 10,
|
|
|
|
ReadHeaderTimeout: time.Second * httpReadHeaderTimeout,
|
|
|
|
ReadTimeout: time.Second * 20,
|
|
|
|
ReadTimeout: time.Second * httpReadTimeout,
|
|
|
|
WriteTimeout: time.Second * 60,
|
|
|
|
WriteTimeout: time.Second * httpWriteTimeout,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
server.Handler = csrf.Protect(csrfKey)(http.DefaultServeMux)
|
|
|
|
server.Handler = csrf.Protect(csrfKey)(http.DefaultServeMux)
|
|
|
@ -859,24 +1033,27 @@ func main() {
|
|
|
|
log.Infof("Launching application on https://%s/", server.Addr)
|
|
|
|
log.Infof("Launching application on https://%s/", server.Addr)
|
|
|
|
|
|
|
|
|
|
|
|
errs := make(chan error, 1)
|
|
|
|
errs := make(chan error, 1)
|
|
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
go func() {
|
|
|
|
httpRedirector := &http.Server{
|
|
|
|
httpRedirector := &http.Server{
|
|
|
|
Addr: config.HttpAddress,
|
|
|
|
Addr: config.HTTPAddress,
|
|
|
|
Handler: http.RedirectHandler(config.BaseURL, http.StatusMovedPermanently),
|
|
|
|
Handler: http.RedirectHandler(config.BaseURL, http.StatusMovedPermanently),
|
|
|
|
IdleTimeout: time.Second * 5,
|
|
|
|
IdleTimeout: time.Second * httpIdleTimeout,
|
|
|
|
ReadHeaderTimeout: time.Second * 10,
|
|
|
|
ReadHeaderTimeout: time.Second * httpReadHeaderTimeout,
|
|
|
|
ReadTimeout: time.Second * 10,
|
|
|
|
ReadTimeout: time.Second * httpReadTimeout,
|
|
|
|
WriteTimeout: time.Second * 60,
|
|
|
|
WriteTimeout: time.Second * httpWriteTimeout,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := httpRedirector.ListenAndServe(); err != nil {
|
|
|
|
if err := httpRedirector.ListenAndServe(); err != nil {
|
|
|
|
errs <- err
|
|
|
|
errs <- err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
close(errs)
|
|
|
|
close(errs)
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
if err := server.ListenAndServeTLS(config.ServerCert, config.ServerKey); err != nil {
|
|
|
|
if err := server.ListenAndServeTLS(config.ServerCert, config.ServerKey); err != nil {
|
|
|
|
log.Panicf("ListenAndServerTLS failed: %v", err)
|
|
|
|
log.Panicf("ListenAndServerTLS failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := <-errs; err != nil {
|
|
|
|
if err := <-errs; err != nil {
|
|
|
|
log.Panicf("ListenAndServe failed: %v", err)
|
|
|
|
log.Panicf("ListenAndServe failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|