split up admin / sub handler

This commit is contained in:
sentriz
2019-04-25 15:54:47 +01:00
parent 3bf8ac8a43
commit 8e480e104d
13 changed files with 280 additions and 239 deletions

35
handler/controller.go Normal file
View File

@@ -0,0 +1,35 @@
package handler
import (
"html/template"
"github.com/jinzhu/gorm"
"github.com/wader/gormstore"
"github.com/sentriz/gonic/db"
)
type Controller struct {
DB *gorm.DB // common
SStore *gormstore.Store // admin
Templates map[string]*template.Template // admin
}
func (c *Controller) GetSetting(key string) string {
var setting db.Setting
c.DB.Where("key = ?", key).First(&setting)
return setting.Value
}
func (c *Controller) SetSetting(key, value string) {
c.DB.
Where(db.Setting{Key: key}).
Assign(db.Setting{Value: value}).
FirstOrCreate(&db.Setting{})
}
func (c *Controller) GetUserFromName(name string) *db.User {
var user db.User
c.DB.Where("name = ?", name).First(&user)
return &user
}

View File

@@ -1,153 +0,0 @@
package handler
import (
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"strconv"
"github.com/gorilla/sessions"
"github.com/jinzhu/gorm"
"github.com/wader/gormstore"
"github.com/sentriz/gonic/db"
"github.com/sentriz/gonic/subsonic"
)
type Controller struct {
DB *gorm.DB
SStore *gormstore.Store
Templates map[string]*template.Template
}
func (c *Controller) GetSetting(key string) string {
var setting db.Setting
c.DB.Where("key = ?", key).First(&setting)
return setting.Value
}
func (c *Controller) SetSetting(key, value string) {
c.DB.
Where(db.Setting{Key: key}).
Assign(db.Setting{Value: value}).
FirstOrCreate(&db.Setting{})
}
func (c *Controller) GetUserFromName(name string) *db.User {
var user db.User
c.DB.Where("name = ?", name).First(&user)
return &user
}
type templateData struct {
Flashes []interface{}
User *db.User
SelectedUser *db.User
AllUsers []*db.User
ArtistCount int
AlbumCount int
TrackCount int
CurrentLastFMAPIKey string
CurrentLastFMAPISecret string
RequestRoot string
}
func getStrParam(r *http.Request, key string) string {
return r.URL.Query().Get(key)
}
func getStrParamOr(r *http.Request, key, or string) string {
val := getStrParam(r, key)
if val == "" {
return or
}
return val
}
func getIntParam(r *http.Request, key string) (int, error) {
strVal := r.URL.Query().Get(key)
if strVal == "" {
return 0, fmt.Errorf("no param with key `%s`", key)
}
val, err := strconv.Atoi(strVal)
if err != nil {
return 0, fmt.Errorf("not an int `%s`", strVal)
}
return val, nil
}
func getIntParamOr(r *http.Request, key string, or int) int {
val, err := getIntParam(r, key)
if err != nil {
return or
}
return val
}
func respondRaw(w http.ResponseWriter, r *http.Request,
code int, sub *subsonic.Response) {
res := subsonic.MetaResponse{
Response: sub,
}
switch r.URL.Query().Get("f") {
case "json":
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
w.Write(data)
case "jsonp":
w.Header().Set("Content-Type", "application/javascript")
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
callback := r.URL.Query().Get("callback")
w.Write([]byte(callback))
w.Write([]byte("("))
w.Write(data)
w.Write([]byte(");"))
default:
w.Header().Set("Content-Type", "application/xml")
data, err := xml.Marshal(res)
if err != nil {
log.Printf("could not marshall to xml: %v\n", err)
}
w.Write(data)
}
}
func respond(w http.ResponseWriter, r *http.Request,
sub *subsonic.Response) {
respondRaw(w, r, http.StatusOK, sub)
}
func respondError(w http.ResponseWriter, r *http.Request,
code int, message string) {
respondRaw(w, r, http.StatusBadRequest, subsonic.NewError(
code, message,
))
}
func renderTemplate(w http.ResponseWriter, r *http.Request,
tmpl *template.Template, data *templateData) {
session := r.Context().Value("session").(*sessions.Session)
if data == nil {
data = &templateData{}
}
data.Flashes = session.Flashes()
session.Save(r, w)
user, ok := r.Context().Value("user").(*db.User)
if ok {
data.User = user
}
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
return
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/db"
"github.com/sentriz/gonic/handler/utilities"
"github.com/sentriz/gonic/lastfm"
)
@@ -17,7 +16,7 @@ func (c *Controller) ServeLogin(w http.ResponseWriter, r *http.Request) {
}
func (c *Controller) ServeLoginDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
username := r.FormValue("username")
password := r.FormValue("password")
if username == "" || password == "" {
@@ -42,7 +41,7 @@ func (c *Controller) ServeLoginDo(w http.ResponseWriter, r *http.Request) {
}
func (c *Controller) ServeLogout(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
@@ -55,13 +54,13 @@ func (c *Controller) ServeHome(w http.ResponseWriter, r *http.Request) {
c.DB.Table("tracks").Count(&data.TrackCount)
c.DB.Find(&data.AllUsers)
data.CurrentLastFMAPIKey = c.GetSetting("lastfm_api_key")
scheme := utilities.FirstExisting(
scheme := firstExisting(
"http", // fallback
r.Header.Get("X-Forwarded-Proto"),
r.Header.Get("X-Forwarded-Scheme"),
r.URL.Scheme,
)
host := utilities.FirstExisting(
host := firstExisting(
"localhost:7373", // fallback
r.Header.Get("X-Forwarded-Host"),
r.Host,
@@ -75,17 +74,17 @@ func (c *Controller) ServeChangeOwnPassword(w http.ResponseWriter, r *http.Reque
}
func (c *Controller) ServeChangeOwnPasswordDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
passwordOne := r.FormValue("password_one")
passwordTwo := r.FormValue("password_two")
err := utilities.ValidatePasswords(passwordOne, passwordTwo)
err := validatePasswords(passwordOne, passwordTwo)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
return
}
user := r.Context().Value("user").(*db.User)
user := r.Context().Value(contextUserKey).(*db.User)
user.Password = passwordOne
c.DB.Save(user)
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
@@ -102,21 +101,21 @@ func (c *Controller) ServeLinkLastFMDo(w http.ResponseWriter, r *http.Request) {
c.GetSetting("lastfm_secret"),
token,
)
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
return
}
user := r.Context().Value("user").(*db.User)
user := r.Context().Value(contextUserKey).(*db.User)
user.LastFMSession = sessionKey
c.DB.Save(&user)
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
}
func (c *Controller) ServeUnlinkLastFMDo(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(*db.User)
user := r.Context().Value(contextUserKey).(*db.User)
user.LastFMSession = ""
c.DB.Save(&user)
http.Redirect(w, r, "/admin/home", http.StatusSeeOther)
@@ -140,13 +139,13 @@ func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request)
}
func (c *Controller) ServeChangePasswordDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
username := r.URL.Query().Get("user")
var user db.User
c.DB.Where("name = ?", username).First(&user)
passwordOne := r.FormValue("password_one")
passwordTwo := r.FormValue("password_two")
err := utilities.ValidatePasswords(passwordOne, passwordTwo)
err := validatePasswords(passwordOne, passwordTwo)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)
@@ -188,9 +187,9 @@ func (c *Controller) ServeCreateUser(w http.ResponseWriter, r *http.Request) {
}
func (c *Controller) ServeCreateUserDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
username := r.FormValue("username")
err := utilities.ValidateUsername(username)
err := validateUsername(username)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)
@@ -199,7 +198,7 @@ func (c *Controller) ServeCreateUserDo(w http.ResponseWriter, r *http.Request) {
}
passwordOne := r.FormValue("password_one")
passwordTwo := r.FormValue("password_two")
err = utilities.ValidatePasswords(passwordOne, passwordTwo)
err = validatePasswords(passwordOne, passwordTwo)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)
@@ -230,10 +229,10 @@ func (c *Controller) ServeUpdateLastFMAPIKey(w http.ResponseWriter, r *http.Requ
}
func (c *Controller) ServeUpdateLastFMAPIKeyDo(w http.ResponseWriter, r *http.Request) {
session := r.Context().Value("session").(*sessions.Session)
session := r.Context().Value(contextSessionKey).(*sessions.Session)
apiKey := r.FormValue("api_key")
secret := r.FormValue("secret")
err := utilities.ValidateAPIKey(apiKey, secret)
err := validateAPIKey(apiKey, secret)
if err != nil {
session.AddFlash(err.Error())
session.Save(r, w)

View File

@@ -0,0 +1,8 @@
package handler
type contextKey int
const (
contextUserKey contextKey = iota
contextSessionKey
)

View File

@@ -1,15 +1,15 @@
package utilities
package handler
import "fmt"
func ValidateUsername(username string) error {
func validateUsername(username string) error {
if username == "" {
return fmt.Errorf("please enter the username")
}
return nil
}
func ValidatePasswords(pOne, pTwo string) error {
func validatePasswords(pOne, pTwo string) error {
if pOne == "" || pTwo == "" {
return fmt.Errorf("please enter the password twice")
}
@@ -19,14 +19,14 @@ func ValidatePasswords(pOne, pTwo string) error {
return nil
}
func ValidateAPIKey(apiKey, secret string) error {
func validateAPIKey(apiKey, secret string) error {
if apiKey == "" || secret == "" {
return fmt.Errorf("please enter both the api key and secret")
}
return nil
}
func FirstExisting(or string, strings ...string) string {
func firstExisting(or string, strings ...string) string {
current := ""
for _, s := range strings {
if s == "" {

View File

@@ -1,4 +1,3 @@
// func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) {
// var artists []*db.Artist
// c.DB.Find(&artists)

View File

@@ -0,0 +1,59 @@
package handler
import (
"context"
"net/http"
"github.com/gorilla/sessions"
"github.com/sentriz/gonic/db"
)
func (c *Controller) WithSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
withSession := context.WithValue(r.Context(), contextSessionKey, session)
next.ServeHTTP(w, r.WithContext(withSession))
}
}
func (c *Controller) WithUserSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// session exists at this point
session := r.Context().Value(contextSessionKey).(*sessions.Session)
username, ok := session.Values["user"].(string)
if !ok {
session.AddFlash("you are not authenticated")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
// take username from sesion and add the user row to the context
user := c.GetUserFromName(username)
if user.ID == 0 {
// the username in the client's session no longer relates to a
// user in the database (maybe the user was deleted)
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
withUser := context.WithValue(r.Context(), contextUserKey, user)
next.ServeHTTP(w, r.WithContext(withUser))
}
}
func (c *Controller) WithAdminSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// session and user exist at this point
session := r.Context().Value(contextSessionKey).(*sessions.Session)
user := r.Context().Value(contextUserKey).(*db.User)
if !user.IsAdmin {
session.AddFlash("you are not an admin")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
}
}

View File

@@ -0,0 +1,13 @@
package handler
import (
"log"
"net/http"
)
func (c *Controller) WithLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("connection from `%s` for `%s`", r.RemoteAddr, r.URL)
next.ServeHTTP(w, r)
}
}

View File

@@ -1,22 +1,21 @@
package handler
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"log"
"net/http"
"github.com/gorilla/sessions"
"github.com/jinzhu/gorm"
"github.com/sentriz/gonic/db"
)
var requiredParameters = []string{
"u", "v", "c",
}
var (
requiredParameters = []string{
"u", "v", "c",
}
)
func checkCredentialsToken(password, token, salt string) bool {
toHash := fmt.Sprintf("%s%s", password, salt)
@@ -33,13 +32,6 @@ func checkCredentialsBasic(password, givenPassword string) bool {
return password == givenPassword
}
func (c *Controller) WithLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("connection from `%s` for `%s`", r.RemoteAddr, r.URL)
next.ServeHTTP(w, r)
}
}
func (c *Controller) WithValidSubsonicArgs(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, req := range requiredParameters {
@@ -101,52 +93,3 @@ func (c *Controller) WithCORS(next http.HandlerFunc) http.HandlerFunc {
next.ServeHTTP(w, r)
}
}
func (c *Controller) WithSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := c.SStore.Get(r, "gonic")
withSession := context.WithValue(r.Context(), "session", session)
next.ServeHTTP(w, r.WithContext(withSession))
}
}
func (c *Controller) WithUserSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// session exists at this point
session := r.Context().Value("session").(*sessions.Session)
username, ok := session.Values["user"].(string)
if !ok {
session.AddFlash("you are not authenticated")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
// take username from sesion and add the user row to the context
user := c.GetUserFromName(username)
if user.ID == 0 {
// the username in the client's session no longer relates to a
// user in the database (maybe the user was deleted)
session.Options.MaxAge = -1
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
withUser := context.WithValue(r.Context(), "user", user)
next.ServeHTTP(w, r.WithContext(withUser))
}
}
func (c *Controller) WithAdminSession(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// session and user exist at this point
session := r.Context().Value("session").(*sessions.Session)
user := r.Context().Value("user").(*db.User)
if !user.IsAdmin {
session.AddFlash("you are not an admin")
session.Save(r, w)
http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
}
}

39
handler/parse.go Normal file
View File

@@ -0,0 +1,39 @@
package handler
import (
"fmt"
"net/http"
"strconv"
)
func getStrParam(r *http.Request, key string) string {
return r.URL.Query().Get(key)
}
func getStrParamOr(r *http.Request, key, or string) string {
val := getStrParam(r, key)
if val == "" {
return or
}
return val
}
func getIntParam(r *http.Request, key string) (int, error) {
strVal := r.URL.Query().Get(key)
if strVal == "" {
return 0, fmt.Errorf("no param with key `%s`", key)
}
val, err := strconv.Atoi(strVal)
if err != nil {
return 0, fmt.Errorf("not an int `%s`", strVal)
}
return val, nil
}
func getIntParamOr(r *http.Request, key string, or int) int {
val, err := getIntParam(r, key)
if err != nil {
return or
}
return val
}

43
handler/respond_admin.go Normal file
View File

@@ -0,0 +1,43 @@
package handler
import (
"fmt"
"html/template"
"net/http"
"github.com/gorilla/sessions"
"github.com/sentriz/gonic/db"
)
type templateData struct {
Flashes []interface{}
User *db.User
SelectedUser *db.User
AllUsers []*db.User
ArtistCount int
AlbumCount int
TrackCount int
CurrentLastFMAPIKey string
CurrentLastFMAPISecret string
RequestRoot string
}
func renderTemplate(w http.ResponseWriter, r *http.Request,
tmpl *template.Template, data *templateData) {
session := r.Context().Value(contextSessionKey).(*sessions.Session)
if data == nil {
data = &templateData{}
}
data.Flashes = session.Flashes()
session.Save(r, w)
user, ok := r.Context().Value(contextUserKey).(*db.User)
if ok {
data.User = user
}
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
return
}
}

56
handler/respond_sub.go Normal file
View File

@@ -0,0 +1,56 @@
package handler
import (
"encoding/json"
"encoding/xml"
"log"
"net/http"
"github.com/sentriz/gonic/subsonic"
)
func respondRaw(w http.ResponseWriter, r *http.Request,
code int, sub *subsonic.Response) {
res := subsonic.MetaResponse{
Response: sub,
}
switch r.URL.Query().Get("f") {
case "json":
w.Header().Set("Content-Type", "application/json")
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
w.Write(data)
case "jsonp":
w.Header().Set("Content-Type", "application/javascript")
data, err := json.Marshal(res)
if err != nil {
log.Printf("could not marshall to json: %v\n", err)
}
callback := r.URL.Query().Get("callback")
w.Write([]byte(callback))
w.Write([]byte("("))
w.Write(data)
w.Write([]byte(");"))
default:
w.Header().Set("Content-Type", "application/xml")
data, err := xml.Marshal(res)
if err != nil {
log.Printf("could not marshall to xml: %v\n", err)
}
w.Write(data)
}
}
func respond(w http.ResponseWriter, r *http.Request,
sub *subsonic.Response) {
respondRaw(w, r, http.StatusOK, sub)
}
func respondError(w http.ResponseWriter, r *http.Request,
code int, message string) {
respondRaw(w, r, http.StatusBadRequest, subsonic.NewError(
code, message,
))
}