diff --git a/handler/controller.go b/handler/controller.go new file mode 100644 index 0000000..9e2ab3a --- /dev/null +++ b/handler/controller.go @@ -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 +} diff --git a/handler/handler.go b/handler/handler.go deleted file mode 100644 index bc0681e..0000000 --- a/handler/handler.go +++ /dev/null @@ -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 - } -} diff --git a/handler/admin.go b/handler/handler_admin.go similarity index 87% rename from handler/admin.go rename to handler/handler_admin.go index 75c8f53..9198cdf 100644 --- a/handler/admin.go +++ b/handler/handler_admin.go @@ -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) diff --git a/handler/handler_admin_const.go b/handler/handler_admin_const.go new file mode 100644 index 0000000..95e8aec --- /dev/null +++ b/handler/handler_admin_const.go @@ -0,0 +1,8 @@ +package handler + +type contextKey int + +const ( + contextUserKey contextKey = iota + contextSessionKey +) diff --git a/handler/utilities/utilities.go b/handler/handler_admin_utils.go similarity index 71% rename from handler/utilities/utilities.go rename to handler/handler_admin_utils.go index a5c3d70..be3531e 100644 --- a/handler/utilities/utilities.go +++ b/handler/handler_admin_utils.go @@ -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 == "" { diff --git a/handler/media.go b/handler/handler_sub.go similarity index 100% rename from handler/media.go rename to handler/handler_sub.go diff --git a/handler/oldfolderbase b/handler/handler_sub_old similarity index 99% rename from handler/oldfolderbase rename to handler/handler_sub_old index d6eae8f..708483c 100644 --- a/handler/oldfolderbase +++ b/handler/handler_sub_old @@ -1,4 +1,3 @@ - // func (c *Controller) GetIndexes(w http.ResponseWriter, r *http.Request) { // var artists []*db.Artist // c.DB.Find(&artists) diff --git a/handler/middleware_admin.go b/handler/middleware_admin.go new file mode 100644 index 0000000..007a762 --- /dev/null +++ b/handler/middleware_admin.go @@ -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) + } +} diff --git a/handler/middleware_common.go b/handler/middleware_common.go new file mode 100644 index 0000000..901b51b --- /dev/null +++ b/handler/middleware_common.go @@ -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) + } +} diff --git a/handler/middleware.go b/handler/middleware_sub.go similarity index 52% rename from handler/middleware.go rename to handler/middleware_sub.go index a94f4ce..8403c3c 100644 --- a/handler/middleware.go +++ b/handler/middleware_sub.go @@ -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) - } -} diff --git a/handler/parse.go b/handler/parse.go new file mode 100644 index 0000000..4064ba8 --- /dev/null +++ b/handler/parse.go @@ -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 +} diff --git a/handler/respond_admin.go b/handler/respond_admin.go new file mode 100644 index 0000000..dc6dff1 --- /dev/null +++ b/handler/respond_admin.go @@ -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 + } +} diff --git a/handler/respond_sub.go b/handler/respond_sub.go new file mode 100644 index 0000000..b901a9d --- /dev/null +++ b/handler/respond_sub.go @@ -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, + )) +}