oef/handlers/handlers.go

559 lines
14 KiB
Go
Raw Normal View History

2019-11-04 15:00:46 +01:00
package handlers
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
2019-11-04 15:00:46 +01:00
"path/filepath"
"runtime/debug"
"strconv"
"strings"
2019-12-04 10:11:18 +01:00
"git.andreafazzi.eu/andrea/oef/config"
2020-01-23 12:50:50 +01:00
"git.andreafazzi.eu/andrea/oef/errors"
2019-11-04 15:00:46 +01:00
"git.andreafazzi.eu/andrea/oef/orm"
2020-01-17 07:59:57 +01:00
"git.andreafazzi.eu/andrea/oef/reflect"
2019-11-04 15:00:46 +01:00
"git.andreafazzi.eu/andrea/oef/renderer"
2020-01-14 16:28:27 +01:00
jwtmiddleware "github.com/auth0/go-jwt-middleware"
2019-11-04 15:00:46 +01:00
jwt "github.com/dgrijalva/jwt-go"
"github.com/gorilla/mux"
2020-01-14 16:28:27 +01:00
"github.com/gorilla/sessions"
"github.com/dchest/captcha"
2019-11-04 15:00:46 +01:00
)
2020-01-23 12:50:50 +01:00
type handlerFuncWithError func(http.ResponseWriter, *http.Request) error
type rootMiddleware struct {
h *Handlers
fn handlerFuncWithError
}
func newRootMiddleware(h *Handlers, fn func(http.ResponseWriter, *http.Request) error) *rootMiddleware {
return &rootMiddleware{h, fn}
}
// type rootHandler func(http.ResponseWriter, *http.Request) error
2020-01-14 09:43:27 +01:00
type Handlers struct {
2020-01-14 16:28:27 +01:00
Config *config.ConfigT
Database *orm.Database
Models []interface{}
2020-01-14 09:43:27 +01:00
Renderer map[string]renderer.Renderer
Login func(db *orm.Database, store *sessions.CookieStore, signingKey []byte) http.Handler
2020-01-14 16:28:27 +01:00
Logout func(store *sessions.CookieStore) http.Handler
2020-01-14 09:43:27 +01:00
Home func() http.Handler
GetToken func(db *orm.Database, signingKey []byte) http.Handler
2020-01-14 09:43:27 +01:00
Static func() http.Handler
Recover func(next http.Handler) http.Handler
2020-01-14 16:28:27 +01:00
CookieStore *sessions.CookieStore
JWTSigningKey []byte
JWTCookieMiddleware *jwtmiddleware.JWTMiddleware
JWTHeaderMiddleware *jwtmiddleware.JWTMiddleware
2020-01-14 09:43:27 +01:00
Router *mux.Router
2019-12-04 12:20:34 +01:00
permissions map[string]map[string]bool
2019-11-04 15:00:46 +01:00
}
// Generate CRUD handlers for models
2020-01-14 09:43:27 +01:00
func (h *Handlers) generateModelHandlers(r *mux.Router, model interface{}) {
2019-11-04 15:00:46 +01:00
// Install standard paths
2020-01-16 12:47:35 +01:00
for _, pattern := range DefaultPathPatterns {
2019-11-22 11:16:27 +01:00
r.Handle(
pattern.Path(
2020-01-17 07:59:57 +01:00
reflect.ModelNameLowerPlural(model),
2019-11-22 11:16:27 +01:00
),
2020-01-14 16:28:27 +01:00
h.JWTCookieMiddleware.Handler(
2020-01-14 09:43:27 +01:00
h.Recover(
2020-01-23 12:50:50 +01:00
newRootMiddleware(
h,
h.modelHandler(
reflect.ModelNameLowerPlural(model),
pattern,
))))).Methods(pattern.Methods...)
2019-11-04 15:00:46 +01:00
}
// Install API paths
2020-01-17 07:59:57 +01:00
for _, pattern := range h.Config.Handlers.APIPathPatterns {
2019-11-22 11:16:27 +01:00
r.Handle(pattern.Path(
2020-01-17 07:59:57 +01:00
reflect.ModelNameLowerPlural(model),
2019-11-22 11:16:27 +01:00
),
2020-01-14 16:28:27 +01:00
h.JWTHeaderMiddleware.Handler(
2020-01-14 09:43:27 +01:00
h.Recover(
2020-01-23 12:50:50 +01:00
newRootMiddleware(
h,
h.modelHandler(
reflect.ModelNameLowerPlural(model),
pattern,
))))).Methods(pattern.Methods...)
2019-11-04 15:00:46 +01:00
}
2020-01-17 11:06:28 +01:00
// Set permissions
2019-12-04 12:20:34 +01:00
2020-01-17 07:59:57 +01:00
for role, modelPermissions := range h.Config.Handlers.Permissions {
2019-12-04 12:20:34 +01:00
for m, perm := range modelPermissions {
2020-01-17 07:59:57 +01:00
if m == reflect.ModelName(model) {
2019-12-04 12:20:34 +01:00
for _, p := range perm {
2020-01-17 07:59:57 +01:00
for _, pattern := range h.Config.Handlers.PathPatterns {
2019-12-04 12:20:34 +01:00
if pattern.Permission == p {
2020-01-17 11:06:28 +01:00
if h.permissions[role] == nil {
h.permissions[role] = make(map[string]bool)
2019-12-04 12:20:34 +01:00
}
2020-01-17 11:06:28 +01:00
h.permissions[role][pattern.Path(reflect.ModelNameLowerPlural(model))] = true
2019-12-04 12:20:34 +01:00
}
}
2020-01-02 09:48:20 +01:00
2020-01-16 12:47:35 +01:00
for _, pattern := range DefaultAPIPathPatterns {
2020-01-02 09:48:20 +01:00
if pattern.Permission == p {
2020-01-17 11:06:28 +01:00
if h.permissions[role] == nil {
h.permissions[role] = make(map[string]bool)
2020-01-02 09:48:20 +01:00
}
2020-01-17 11:06:28 +01:00
h.permissions[role][pattern.Path(reflect.ModelNameLowerPlural(model))] = true
2020-01-02 09:48:20 +01:00
}
}
2019-12-04 12:20:34 +01:00
}
}
}
}
2019-11-04 15:00:46 +01:00
}
2020-01-17 07:59:57 +01:00
func NewHandlers(config *config.ConfigT, renderer map[string]renderer.Renderer, db *orm.Database, models []interface{}) *Handlers {
2020-01-14 09:43:27 +01:00
handlers := new(Handlers)
2020-01-14 16:28:27 +01:00
handlers.Config = config
handlers.Renderer = renderer
handlers.Database = db
2020-01-14 16:28:27 +01:00
handlers.CookieStore = sessions.NewCookieStore([]byte(config.Keys.CookieStoreKey))
handlers.Login = handlers.DefaultLoginHandler
2020-01-14 09:43:27 +01:00
handlers.Logout = DefaultLogoutHandler
handlers.Recover = DefaultRecoverHandler
handlers.Home = DefaultHomeHandler
handlers.GetToken = DefaultGetTokenHandler
2020-01-14 16:28:27 +01:00
handlers.JWTCookieMiddleware = jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte(config.Keys.JWTSigningKey), nil
},
SigningMethod: jwt.SigningMethodHS256,
Extractor: handlers.cookieExtractor,
ErrorHandler: handlers.onError,
})
handlers.JWTHeaderMiddleware = jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte(config.Keys.JWTSigningKey), nil
2020-01-14 16:28:27 +01:00
},
SigningMethod: jwt.SigningMethodHS256,
})
2020-01-17 11:06:28 +01:00
handlers.permissions = make(map[string]map[string]bool)
2019-11-04 15:00:46 +01:00
r := mux.NewRouter()
// Authentication
r.Handle("/login", handlers.Login(handlers.Database, handlers.CookieStore, []byte(config.Keys.JWTSigningKey)))
2020-01-14 16:28:27 +01:00
r.Handle("/logout", handlers.Logout(handlers.CookieStore))
2019-11-04 15:00:46 +01:00
2019-12-03 15:24:01 +01:00
// School subscription
r.Handle("/subscribe", handlers.Login(handlers.Database, handlers.CookieStore, []byte(config.Keys.JWTSigningKey)))
2019-12-03 15:24:01 +01:00
// Captcha
r.Handle("/captcha/{img}", captcha.Server(captcha.StdWidth, captcha.StdHeight))
2020-01-14 09:43:27 +01:00
// Home
2019-11-04 15:00:46 +01:00
2020-01-14 16:28:27 +01:00
r.Handle("/", handlers.JWTCookieMiddleware.Handler(handlers.Recover(handlers.Home())))
2019-11-04 15:00:46 +01:00
// Generate CRUD handlers
for _, model := range models {
2020-01-14 09:43:27 +01:00
handlers.generateModelHandlers(r, model)
2019-11-04 15:00:46 +01:00
}
// Token handling
r.Handle("/get_token", handlers.GetToken(handlers.Database, []byte(config.Keys.JWTSigningKey)))
2019-11-04 15:00:46 +01:00
// Static file server
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))
2020-01-14 16:28:27 +01:00
handlers.Router = r
return handlers
2019-11-04 15:00:46 +01:00
}
2020-01-17 12:53:35 +01:00
func (h *Handlers) NewReadAllRequest(model interface{}, path string, format string) (*http.Request, error) {
return http.NewRequest("GET", h.Config.ReadAllPath(model, path, format), nil)
}
func (h *Handlers) NewReadRequest(model interface{}, path string, format string) (*http.Request, error) {
return http.NewRequest("GET", h.Config.ReadPath(model, path, format), nil)
}
2020-02-05 10:10:27 +01:00
func (h *Handlers) NewUpdateRequest(model interface{}, path string, format string, method string, form url.Values) (*http.Request, error) {
var (
request *http.Request
err error
)
switch method {
case "GET":
request, err = http.NewRequest("GET", h.Config.UpdatePath(model, path, format), nil)
case "POST":
request, err = http.NewRequest("POST", h.Config.UpdatePath(model, path, format), strings.NewReader(form.Encode()))
}
return request, err
}
func (h *Handlers) NewCreateRequest(model interface{}, path string, format string, method string, form url.Values) (*http.Request, error) {
2020-01-17 12:53:35 +01:00
var (
request *http.Request
err error
)
switch method {
case "GET":
request, err = http.NewRequest("GET", h.Config.CreatePath(model, path, format), nil)
case "POST":
request, err = http.NewRequest("POST", h.Config.CreatePath(model, path, format), strings.NewReader(form.Encode()))
2020-01-17 12:53:35 +01:00
}
return request, err
2020-01-16 12:47:35 +01:00
}
2020-01-23 17:54:41 +01:00
func (h *Handlers) NewDeleteRequest(model interface{}, path string, format string) (*http.Request, error) {
return http.NewRequest("DELETE", h.Config.DeletePath(model, path, format), nil)
}
2020-01-14 16:28:27 +01:00
func (h *Handlers) onError(w http.ResponseWriter, r *http.Request, err string) {
log.Println("Authorization error:", err)
2019-11-04 15:00:46 +01:00
http.Redirect(w, r, "/login?tpl_layout=login&tpl_content=login", http.StatusTemporaryRedirect)
}
func respondWithStaticFile(w http.ResponseWriter, filename string) error {
f, err := ioutil.ReadFile(filepath.Join("public/html", filename))
if err != nil {
return err
}
w.Write(f)
return nil
}
2020-01-14 16:28:27 +01:00
func (h *Handlers) cookieExtractor(r *http.Request) (string, error) {
if session := r.URL.Query().Get("login_session"); h.Config.Handlers.AllowSessionURLQuery && session != "" {
return session, nil
}
2020-01-14 16:28:27 +01:00
session, err := h.CookieStore.Get(r, "login-session")
2019-11-04 15:00:46 +01:00
if err != nil {
return "", nil
}
if session.Values["token"] == nil {
return "", nil
}
token := session.Values["token"].([]uint8)
return string(token), nil
}
2020-01-24 07:15:23 +01:00
func getClaims(r *http.Request) jwt.MapClaims {
return r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
}
2020-01-14 09:43:27 +01:00
func DefaultRecoverHandler(next http.Handler) http.Handler {
2019-11-04 15:00:46 +01:00
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
panicMsg := fmt.Sprintf("PANIC: %v\n\n== STACKTRACE ==\n%s", err, debug.Stack())
log.Print(panicMsg)
http.Error(w, panicMsg, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// func (h *Handlers) setFlashMessage(w http.ResponseWriter, r *http.Request, key string) error {
// session, err := h.CookieStore.Get(r, "flash-session")
// if err != nil {
// return err
// }
// session.AddFlash(i18n.FlashMessages[key][h.Config.Language])
// err = session.Save(r, w)
// if err != nil {
// return err
// }
// return nil
// }
2019-12-04 10:11:18 +01:00
2020-01-24 07:15:23 +01:00
func (h *Handlers) hasPermission(r *http.Request, path string) bool {
claims := getClaims(r)
role := claims["role"].(string)
2020-01-17 11:06:28 +01:00
if h.permissions[role] == nil {
2019-12-04 12:20:34 +01:00
return false
}
2020-01-17 11:06:28 +01:00
return h.permissions[role][path]
2019-12-04 12:20:34 +01:00
}
2020-01-24 07:15:23 +01:00
func (h *Handlers) callModelFunc(w http.ResponseWriter, r *http.Request, model string, pattern config.PathPattern) (interface{}, error) {
fn, err := h.Database.GetFunc(pattern.Path(model))
if err != nil {
return nil, err
}
if !h.hasPermission(r, pattern.Path(model)) {
return nil, errors.NotAuthorized
}
data, err := fn(h.Database, mux.Vars(r), w, r)
if err != nil {
return nil, err
}
return data, nil
}
2020-01-23 12:50:50 +01:00
func (h *Handlers) get(w http.ResponseWriter, r *http.Request, model string, pattern config.PathPattern) error {
2020-01-24 07:15:23 +01:00
data, err := h.callModelFunc(w, r, model, pattern)
2019-11-04 15:00:46 +01:00
if err != nil {
2020-01-23 12:50:50 +01:00
return err
2019-11-04 15:00:46 +01:00
}
2020-01-24 07:15:23 +01:00
format := r.URL.Query().Get("format")
h.Renderer[format].Render(w, r, h.CookieStore, data, r.URL.Query())
2019-11-04 15:00:46 +01:00
2020-01-23 12:50:50 +01:00
return nil
2019-11-04 15:00:46 +01:00
}
func (h *Handlers) post(w http.ResponseWriter, r *http.Request, model string, pattern config.PathPattern) error {
2020-01-24 07:15:23 +01:00
data, err := h.callModelFunc(w, r, model, pattern)
2019-11-04 15:00:46 +01:00
if err != nil {
2020-01-23 12:50:50 +01:00
return err
2020-01-24 07:15:23 +01:00
}
if pattern.RedirectPattern != "" {
if id := mux.Vars(r)["id"]; id != "" {
modelId, _ := strconv.Atoi(id)
http.Redirect(w, r, pattern.RedirectPath(model, uint(modelId)), http.StatusSeeOther)
2019-12-19 13:56:54 +01:00
} else {
2020-01-24 07:15:23 +01:00
http.Redirect(w, r, pattern.RedirectPath(model, data.(orm.IDer).GetID()), http.StatusSeeOther)
2019-11-04 15:00:46 +01:00
}
2020-01-24 07:15:23 +01:00
} else {
format := renderer.GetContentFormat(r)
h.Renderer[format].Render(w, r, h.CookieStore, data.(orm.IDer).GetID())
2019-11-04 15:00:46 +01:00
}
return nil
2019-11-04 15:00:46 +01:00
}
2020-01-23 12:50:50 +01:00
func (h *Handlers) delete(w http.ResponseWriter, r *http.Request, model string, pattern config.PathPattern) error {
2020-01-24 07:15:23 +01:00
data, err := h.callModelFunc(w, r, model, pattern)
if err != nil {
return err
}
if pattern.RedirectPattern != "" {
var data struct {
RedirectUrl string `json:"redirect_url"`
2019-12-19 13:56:54 +01:00
}
2020-01-24 07:15:23 +01:00
data.RedirectUrl = pattern.RedirectPath(model)
2019-12-19 13:56:54 +01:00
2020-01-24 07:15:23 +01:00
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
} else {
format := renderer.GetContentFormat(r)
h.Renderer[format].Render(w, r, h.CookieStore, data.(orm.IDer).GetID())
2019-11-04 15:00:46 +01:00
}
2020-01-23 12:50:50 +01:00
return nil
2019-11-04 15:00:46 +01:00
}
func respondWithError(h *Handlers, w http.ResponseWriter, r *http.Request, err error) {
2020-01-23 12:50:50 +01:00
var format string
format = r.URL.Query().Get("format")
if format == "" {
format = renderer.GetContentFormat(r)
}
2020-01-31 15:03:26 +01:00
if h.Config.LogLevel > config.LOG_LEVEL_OFF {
log.Println(err)
}
2019-11-04 15:00:46 +01:00
w.WriteHeader(http.StatusInternalServerError)
2020-01-23 12:50:50 +01:00
h.Renderer[format].Render(w, r, h.CookieStore, err)
2019-11-04 15:00:46 +01:00
}
func (h *Handlers) Create(model interface{}) http.Handler {
2020-01-27 13:15:51 +01:00
fn := func(w http.ResponseWriter, r *http.Request) error {
2020-01-24 12:04:12 +01:00
switch r.Method {
case "GET":
2020-01-27 13:15:51 +01:00
err := h.get(w, r, reflect.ModelNameLowerPlural(model), h.Config.CreatePattern())
if err != nil {
return err
}
2020-01-24 12:04:12 +01:00
case "POST":
2020-01-27 13:15:51 +01:00
err := h.post(w, r, reflect.ModelNameLowerPlural(model), h.Config.CreatePattern())
if err != nil {
return err
}
2020-01-24 12:04:12 +01:00
}
2020-01-27 13:15:51 +01:00
return nil
}
2020-01-27 13:15:51 +01:00
return newRootMiddleware(h, fn)
}
2020-02-05 10:10:27 +01:00
func (h *Handlers) Update(model interface{}) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case "GET":
err := h.get(w, r, reflect.ModelNameLowerPlural(model), h.Config.UpdatePattern())
if err != nil {
return err
}
case "POST":
err := h.post(w, r, reflect.ModelNameLowerPlural(model), h.Config.UpdatePattern())
if err != nil {
return err
}
}
return nil
}
return newRootMiddleware(h, fn)
}
2020-01-17 07:59:57 +01:00
func (h *Handlers) ReadAll(model interface{}) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
2020-01-17 11:06:28 +01:00
h.get(w, r, reflect.ModelNameLowerPlural(model), h.Config.ReadAllPattern())
2020-01-17 07:59:57 +01:00
}
return http.HandlerFunc(fn)
}
2020-01-17 12:11:13 +01:00
func (h *Handlers) Read(model interface{}) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
h.get(w, r, reflect.ModelNameLowerPlural(model), h.Config.ReadPattern())
}
return http.HandlerFunc(fn)
}
2020-01-23 17:54:41 +01:00
func (h *Handlers) Delete(model interface{}) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
h.delete(w, r, reflect.ModelNameLowerPlural(model), h.Config.DeletePattern())
}
return http.HandlerFunc(fn)
}
2020-01-23 12:50:50 +01:00
func (h *Handlers) modelHandler(model string, pattern config.PathPattern) handlerFuncWithError {
fn := func(w http.ResponseWriter, r *http.Request) error {
var err error
2019-11-04 15:00:46 +01:00
2020-01-20 12:54:57 +01:00
// Replace the "api" prefix.
2019-11-04 15:00:46 +01:00
pattern.PathPattern = strings.Replace(pattern.PathPattern, "/api", "", -1)
switch r.Method {
case "GET":
2020-01-23 12:50:50 +01:00
err = h.get(w, r, model, pattern)
2019-11-04 15:00:46 +01:00
case "POST":
2020-01-23 12:50:50 +01:00
err = h.post(w, r, model, pattern)
2019-11-04 15:00:46 +01:00
case "DELETE":
2020-01-23 12:50:50 +01:00
err = h.delete(w, r, model, pattern)
2019-11-04 15:00:46 +01:00
}
2020-01-23 12:50:50 +01:00
return err
2019-11-04 15:00:46 +01:00
}
2020-01-23 12:50:50 +01:00
return handlerFuncWithError(fn)
2019-11-04 15:00:46 +01:00
}
2020-01-14 09:43:27 +01:00
func DefaultHomeHandler() http.Handler {
2019-11-04 15:00:46 +01:00
fn := func(w http.ResponseWriter, r *http.Request) {
2020-01-24 07:15:23 +01:00
claims := getClaims(r)
2019-11-22 11:16:27 +01:00
switch claims["role"] {
2019-12-03 15:24:01 +01:00
case "subscriber":
http.Redirect(
w,
r,
fmt.Sprintf(
"/schools/create/?format=html&tpl_layout=base&tpl_content=schools_add_update",
),
http.StatusSeeOther,
)
2019-11-22 11:16:27 +01:00
case "participant":
http.Redirect(
w,
r,
fmt.Sprintf(
"/participants/%s?format=html&tpl_layout=base&tpl_content=participants_show",
claims["model_id"].(string)),
2019-11-22 11:16:27 +01:00
http.StatusSeeOther,
)
case "school":
2019-12-03 15:24:01 +01:00
http.Redirect(
w,
r,
fmt.Sprintf(
"/schools/%s?format=html&tpl_layout=base&tpl_content=schools_show",
claims["model_id"].(string)),
2019-12-03 15:24:01 +01:00
http.StatusSeeOther,
)
2019-11-22 11:16:27 +01:00
default:
http.Redirect(
w,
r,
"/contests?format=html&tpl_layout=base&tpl_content=contests",
http.StatusSeeOther,
)
}
2019-11-04 15:00:46 +01:00
}
return http.HandlerFunc(fn)
}
2020-01-23 12:50:50 +01:00
func (m *rootMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2020-01-28 14:36:31 +01:00
err := m.fn(w, r) // Call handler function
if err == nil {
2020-01-23 12:50:50 +01:00
return
}
// This is where our error handling logic starts.
2020-01-27 13:15:51 +01:00
if m.h.Config.LogLevel > config.LOG_LEVEL_OFF {
2020-01-28 14:36:31 +01:00
log.Printf("An error accured: %v", err) // Log the error.
}
if err, ok := err.(*errors.Error); ok {
err.Referer = r.Header.Get("Referer")
2020-01-27 13:15:51 +01:00
}
2020-01-23 12:50:50 +01:00
2020-01-28 14:36:31 +01:00
respondWithError(m.h, w, r, err)
2020-01-23 12:50:50 +01:00
}