From b8f03b87c92de10c2fc561eca78a8551f9c9651a Mon Sep 17 00:00:00 2001 From: Meutel Date: Sat, 29 Jul 2017 19:22:11 +0200 Subject: [PATCH] Photoblog: Refactoring --- photoblog/admin/admin.go | 97 ++++++++++++++------- photoblog/main.go | 8 +- photoblog/photo/photo.go | 147 ++++++++++++++++++++------------ photoblog/templates/header.html | 2 +- photoblog/templates/login.html | 2 +- 5 files changed, 163 insertions(+), 93 deletions(-) diff --git a/photoblog/admin/admin.go b/photoblog/admin/admin.go index 044c27d..f4e331d 100644 --- a/photoblog/admin/admin.go +++ b/photoblog/admin/admin.go @@ -15,6 +15,14 @@ import ( "github.com/gorilla/sessions" ) +const ( + USER = "username" + PWD = "password" + SESS_USER = "username" + SESSION = "photoblog.session" +) + +// Application type AuthCookie struct { Templates *template.Template Store *sessions.CookieStore @@ -22,37 +30,23 @@ type AuthCookie struct { PasswordSecret string } -func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) { - formErr := make(map[string]error) - switch req.Method { - case "POST": - username := req.FormValue("username") - formErr["username"] = app.VerifyUsername(username) - formErr["password"] = app.VerifyPassword(username, req.FormValue("password"), res, req) - if formErr["username"] == nil && formErr["password"] == nil { - app.SaveUsername(username, res, req) - RedirectHome(res, req) - return - } - fallthrough - case "GET": - app.Templates.ExecuteTemplate(res, "login.html", formErr) - } -} -func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) { - app.SaveUsername("", res, req) - RedirectHome(res, req) -} +// Verify Username func (app *AuthCookie) VerifyUsername(username string) error { if username == "" { return errors.New("Empty username") } return nil } + +// Verify password for user, check with file func (app *AuthCookie) VerifyPassword(username, password string, res http.ResponseWriter, req *http.Request) error { if password == "" { return errors.New("Empty password") } + if username == "" { + return nil + } + // open password file passfile, err := os.Open(filepath.Join(app.DataDir.Name(), username, ".password")) if err != nil { log.Println("Cannot open password file", err) @@ -60,14 +54,13 @@ func (app *AuthCookie) VerifyPassword(username, password string, res http.Respon } defer passfile.Close() - if username == "" { - return nil - } + // read password file expected, err := ioutil.ReadAll(passfile) if err != nil { log.Println("Cannot read password file", err) return errors.New("Authentification failed") } + // hash password and compare expectedStr := string(expected) var expectedMAC []byte fmt.Sscanf(expectedStr, "%x", &expectedMAC) @@ -78,29 +71,67 @@ func (app *AuthCookie) VerifyPassword(username, password string, res http.Respon log.Printf("Unmatched password for %s: %x - %x\n", username, passwordMAC, expectedMAC) return errors.New("Authentification failed") } - log.Printf("Authentification successful") + log.Printf("Authentification successful (" + username + ")") return nil } -func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session { - session, _ := app.Store.Get(req, "session") - return session -} + +// Save username in session func (app *AuthCookie) SaveUsername(username string, res http.ResponseWriter, req *http.Request) { session := app.CurrentSession(res, req) - session.Values["username"] = username + session.Values[SESS_USER] = username session.Save(req, res) } + +// Test if a user is logged in func (app *AuthCookie) IsLoggedIn(res http.ResponseWriter, req *http.Request) bool { session := app.CurrentSession(res, req) - return session != nil && session.Values["username"] != "" + return session != nil && session.Values[SESS_USER] != "" } + +// Current username (from session cookie) func (app *AuthCookie) Username(res http.ResponseWriter, req *http.Request) string { session := app.CurrentSession(res, req) if session == nil { return "" } - return session.Values["username"].(string) + val := session.Values[SESS_USER] + if val == nil { + return "" + } + return val.(string) } -func RedirectHome(res http.ResponseWriter, req *http.Request) { + +// Current session +func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session { + session, _ := app.Store.Get(req, SESSION) + return session +} + +// Redirect to home page +func (app *AuthCookie) RedirectHome(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, "/", http.StatusSeeOther) } + +// ROUTES // + +// login: form +func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) { + formErr := make(map[string]error) + if req.Method == http.MethodPost { + username := req.FormValue(USER) + formErr[USER] = app.VerifyUsername(username) + formErr[PWD] = app.VerifyPassword(username, req.FormValue(PWD), res, req) + if formErr[USER] == nil && formErr[PWD] == nil { + app.SaveUsername(username, res, req) + app.RedirectHome(res, req) + return + } + } + app.Templates.ExecuteTemplate(res, "login.html", formErr) +} + +// logout: delete session and redirect to home +func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) { + app.SaveUsername("", res, req) + app.RedirectHome(res, req) +} diff --git a/photoblog/main.go b/photoblog/main.go index efc645d..8c4a705 100644 --- a/photoblog/main.go +++ b/photoblog/main.go @@ -28,19 +28,19 @@ func main() { } defer data.Close() + // FIXME config file app := photo.PhotoBlog{ - admin.AuthCookie{Templates: tpl, + admin.AuthCookie{ + Templates: tpl, Store: sessions.NewCookieStore([]byte("flQ6QzM/c3Jtdl9ycDx6OXRIfFgK")), DataDir: data, PasswordSecret: "d2xnNSwoREQhfSxBVDQ0bF0yb2AK", }, } - fileServer := http.FileServer(http.Dir(data.Name())) - http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { if strings.HasPrefix(req.RequestURI, "/data") { - http.StripPrefix("/data/", fileServer).ServeHTTP(res, req) + http.StripPrefix("/data/", http.FileServer(http.Dir(data.Name()))).ServeHTTP(res, req) } else { app.HomePage(res, req) } diff --git a/photoblog/photo/photo.go b/photoblog/photo/photo.go index be7cdca..4151e65 100644 --- a/photoblog/photo/photo.go +++ b/photoblog/photo/photo.go @@ -1,6 +1,7 @@ package photo import ( + "errors" "fmt" "io" "net/http" @@ -18,22 +19,59 @@ const ( ) var ( - PHOTOEXT = [3]string{".jpg", ".jpeg", ".png"} + PHOTOEXT = [3]string{".jpg", ".jpeg", ".png"} + PHOTOMIME = [2]string{"image/png", "image/jpeg"} ) -type AppData struct { +// Application +type PhotoBlog struct { + admin.AuthCookie +} + +// Generic model +type PhotoModel struct { Username string - Photos []string Err error Message string } +// Constructor PhotoModel +func NewPhotoModel(username string) *PhotoModel { + return &PhotoModel{ + Username: username, + } +} + +// Home page model +type PhotoHomeModel struct { + PhotoModel + Photos []string +} + +// Constructor PhotoHomeModel +func NewPhotoHomeModel(username string, photos []string) *PhotoHomeModel { + return &PhotoHomeModel{ + PhotoModel: *NewPhotoModel(username), + Photos: photos, + } +} + +// File and date of last modification type TimedFile struct { Mod time.Time Path string } -type timeMap []TimedFile +// Constructor TimedFile +func NewTimedFile(mod time.Time, path string) *TimedFile { + return &TimedFile{ + mod, + path, + } +} + +// TimedFile sort +type timeMap []*TimedFile func (m timeMap) Len() int { return len(m) @@ -45,14 +83,7 @@ func (m timeMap) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -type PhotoBlog struct { - admin.AuthCookie -} - -func RedirectHome(res http.ResponseWriter, req *http.Request) { - // FIXME duplicate admin - http.Redirect(res, req, "/", http.StatusSeeOther) -} +// test if file is a photo (by extension) func IsPhoto(path string) bool { for _, ext := range PHOTOEXT { if strings.HasSuffix(path, ext) { @@ -62,6 +93,17 @@ func IsPhoto(path string) bool { return false } +// test if mime type is a photo +func IsPhotoMime(mime string) bool { + for _, accepted := range PHOTOMIME { + if mime == accepted { + return true + } + } + return false +} + +// get Latest files func Latest(photos timeMap) []string { latest := make([]string, 0, DISPLAY_SIZE) sort.Sort(photos) @@ -74,60 +116,57 @@ func Latest(photos timeMap) []string { return latest } -func (app *PhotoBlog) HomePage(res http.ResponseWriter, req *http.Request) { - data := AppData{ - Username: app.Username(res, req), - } - photos := make([]TimedFile, 0) - filepath.Walk(app.DataDir.Name(), func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && IsPhoto(path) { - photos = append(photos, TimedFile{ - info.ModTime(), - path, - }) - } - return nil - }) - data.Photos = Latest(photos) - app.Templates.ExecuteTemplate(res, "home.html", data) -} -func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) { - if !app.IsLoggedIn(res, req) { - RedirectHome(res, req) - return - } - data := AppData{ - Username: app.Username(res, req), - } - switch req.Method { - case "POST": - message, err := app.NewPhoto(res, req) - if err != nil { - data.Err = err - } - data.Message = message - fallthrough - case "GET": - app.Templates.ExecuteTemplate(res, "upload.html", data) - } -} -func (app *PhotoBlog) NewPhoto(res http.ResponseWriter, req *http.Request) (string, error) { +// Add new uploaded photo +func (app *PhotoBlog) AddPhoto(res http.ResponseWriter, req *http.Request) (string, error) { file, header, err := req.FormFile("file") if err != nil { return "", err } defer file.Close() - // FIXME check type - tmpFile, err := os.Create(filepath.Join(app.DataDir.Name(), app.Username(res, req), header.Filename)) + mime := header.Header.Get("Content-Type") + if !IsPhotoMime(mime) { + return "", errors.New("This file type is not accepted (" + mime + ")") + } + + target, err := os.Create(filepath.Join(app.DataDir.Name(), app.Username(res, req), header.Filename)) if err != nil { return "", err } - defer tmpFile.Close() + defer target.Close() - sz, err := io.Copy(tmpFile, file) + sz, err := io.Copy(target, file) if err != nil { return "", err } return fmt.Sprintf("File uploaded (%d bytes)", sz), nil } + +// ROUTES // + +// Home: list latest photos +func (app *PhotoBlog) HomePage(res http.ResponseWriter, req *http.Request) { + photos := make([]*TimedFile, 0) + filepath.Walk(app.DataDir.Name(), func(path string, info os.FileInfo, err error) error { + if !info.IsDir() && IsPhoto(path) { + photos = append(photos, NewTimedFile(info.ModTime(), path)) + } + return nil + }) + app.Templates.ExecuteTemplate(res, "home.html", NewPhotoHomeModel(app.Username(res, req), Latest(photos))) +} + +// Upload: form upload photo +func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) { + if !app.IsLoggedIn(res, req) { + app.RedirectHome(res, req) + return + } + data := NewPhotoModel(app.Username(res, req)) + if req.Method == http.MethodPost { + message, err := app.AddPhoto(res, req) + data.Err = err + data.Message = message + } + app.Templates.ExecuteTemplate(res, "upload.html", data) +} diff --git a/photoblog/templates/header.html b/photoblog/templates/header.html index 975f599..c9bc2ee 100644 --- a/photoblog/templates/header.html +++ b/photoblog/templates/header.html @@ -4,7 +4,7 @@