From ffdc9e770569defcd0f8afb9e7e6ff9d65ba3bb8 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 9 Nov 2019 07:46:50 +1000 Subject: [PATCH] Adminlogs: Custom pages, settings, word filters and backups. WordFilters.Create now returns the ID. Add the WordFilters.Get method. Reduce the boilerplate in the backup page and page store. Sanitise the names and titles in the Page Manager. Add page_unknown phrase. Add setting_unknown phrase. Add panel_logs_administration_action_page_create phrase. Add panel_logs_administration_action_page_delete phrase. Add panel_logs_administration_action_page_edit phrase. Add panel_logs_administration_action_setting_edit phrase. Add panel_logs_administration_action_word_filter_create phrase. Add panel_logs_administration_action_word_filter_delete phrase. Add panel_logs_administration_action_word_filter_edit phrase. Add panel_logs_administration_action_backup_download phrase. --- common/page_store.go | 19 +++++++------- common/word_filters.go | 25 +++++++++++++----- langs/english.json | 10 ++++++++ routes/panel/backups.go | 19 +++++++++----- routes/panel/logs.go | 20 +++++++++++++++ routes/panel/pages.go | 50 ++++++++++++++++++++++-------------- routes/panel/settings.go | 13 +++++++--- routes/panel/word_filters.go | 29 +++++++++++++-------- 8 files changed, 129 insertions(+), 56 deletions(-) diff --git a/common/page_store.go b/common/page_store.go index 74017d76..4c0288bd 100644 --- a/common/page_store.go +++ b/common/page_store.go @@ -38,12 +38,12 @@ func BlankCustomPage() *CustomPage { return new(CustomPage) } -func (page *CustomPage) AddAllowedGroup(gid int) { - page.AllowedGroups = append(page.AllowedGroups, gid) +func (p *CustomPage) AddAllowedGroup(gid int) { + p.AllowedGroups = append(p.AllowedGroups, gid) } -func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { - for _, group := range page.AllowedGroups { +func (p *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { + for _, group := range p.AllowedGroups { rawAllowedGroups += strconv.Itoa(group) + "," } if len(rawAllowedGroups) > 0 { @@ -52,18 +52,17 @@ func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { return rawAllowedGroups } -func (page *CustomPage) Commit() error { - _, err := customPageStmts.update.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID, page.ID) - Pages.Reload(page.ID) +func (p *CustomPage) Commit() error { + _, err := customPageStmts.update.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID, p.ID) + Pages.Reload(p.ID) return err } -func (page *CustomPage) Create() (int, error) { - res, err := customPageStmts.create.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID) +func (p *CustomPage) Create() (int, error) { + res, err := customPageStmts.create.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID) if err != nil { return 0, err } - pid64, err := res.LastInsertId() return int(pid64), err } diff --git a/common/word_filters.go b/common/word_filters.go index 55b1deb7..798b74c3 100644 --- a/common/word_filters.go +++ b/common/word_filters.go @@ -4,7 +4,7 @@ import ( "database/sql" "sync/atomic" - "github.com/Azareal/Gosora/query_gen" + qgen "github.com/Azareal/Gosora/query_gen" ) // TODO: Move some features into methods on this? @@ -19,7 +19,8 @@ var WordFilters WordFilterStore type WordFilterStore interface { ReloadAll() error GetAll() (filters map[int]*WordFilter, err error) - Create(find string, replacement string) error + Get(id int) (*WordFilter, error) + Create(find string, replacement string) (int, error) Delete(id int) error Update(id int, find string, replacement string) error Length() int @@ -31,6 +32,7 @@ type DefaultWordFilterStore struct { box atomic.Value // An atomic value holding a WordFilterMap getAll *sql.Stmt + get *sql.Stmt create *sql.Stmt delete *sql.Stmt update *sql.Stmt @@ -41,6 +43,7 @@ func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore, wf := "word_filters" store := &DefaultWordFilterStore{ getAll: acc.Select(wf).Columns("wfid,find,replacement").Prepare(), + get: acc.Select(wf).Columns("wfid,find,replacement").Where("wfid = ?").Prepare(), create: acc.Insert(wf).Columns("find,replacement").Fields("?,?").Prepare(), delete: acc.Delete(wf).Where("wfid = ?").Prepare(), update: acc.Update(wf).Set("find = ?, replacement = ?").Where("wfid = ?").Prepare(), @@ -93,13 +96,23 @@ func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err erro return s.box.Load().(map[int]*WordFilter), nil } +func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) { + wf := &WordFilter{ID: id} + err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replacement) + return wf, err +} + // Create adds a new word filter to the database and refreshes the memory cache -func (s *DefaultWordFilterStore) Create(find string, replace string) error { - _, err := s.create.Exec(find, replace) +func (s *DefaultWordFilterStore) Create(find string, replace string) (int, error) { + res, err := s.create.Exec(find, replace) if err != nil { - return err + return 0, err } - return s.ReloadAll() + id64, err := res.LastInsertId() + if err != nil { + return 0, err + } + return int(id64), s.ReloadAll() } // Delete removes a word filter from the database and refreshes the memory cache diff --git a/langs/english.json b/langs/english.json index 6ae0b5a4..cd4b2ed6 100644 --- a/langs/english.json +++ b/langs/english.json @@ -1021,6 +1021,8 @@ "topic_unknown":"Unknown", "group_unknown":"Unknown", "forum_unknown":"Unknown", + "page_unknown":"Unknown", + "setting_unknown":"unknown", "panel_logs_administration_head":"Admin Action Logs", "panel_logs_administration_action_user_edit":"User %s was modified by %s", @@ -1032,6 +1034,14 @@ "panel_logs_administration_action_forum_create":"Forum %s was created by %s", "panel_logs_administration_action_forum_delete":"Forum %s was deleted by %s", "panel_logs_administration_action_forum_edit":"Forum %s was modified by %s", + "panel_logs_administration_action_page_create":"Page %s was created by %s", + "panel_logs_administration_action_page_delete":"Page %s was deleted by %s", + "panel_logs_administration_action_page_edit":"Page %s was modified by %s", + "panel_logs_administration_action_setting_edit":"Setting %s was modified by %s", + "panel_logs_administration_action_word_filter_create":"Word Filter %d was created by %s", + "panel_logs_administration_action_word_filter_delete":"Word Filter %d was deleted by %s", + "panel_logs_administration_action_word_filter_edit":"Word Filter %d was modified by %s", + "panel_logs_administration_action_backup_download":"A backup was downloaded by %s", "panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by %s", "panel_logs_administration_no_logs":"There aren't any events logged.", diff --git a/routes/panel/backups.go b/routes/panel/backups.go index 6affcdbc..f3946ce9 100644 --- a/routes/panel/backups.go +++ b/routes/panel/backups.go @@ -28,19 +28,24 @@ func Backups(w http.ResponseWriter, r *http.Request, user c.User, backupURL stri if err != nil { return c.NotFound(w, r, basePage.Header) } - w.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) + h := w.Header() + h.Set("Content-Length", strconv.FormatInt(info.Size(), 10)) if ext == ".sql" { - // TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be - w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.sql") - w.Header().Set("Content-Type", "application/sql") + // TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be + h.Set("Content-Disposition", "attachment; filename=gosora_backup.sql") + h.Set("Content-Type", "application/sql") } else { - // TODO: Change the served filename to gosora_backup_%timestamp%.zip, the time the file was generated, not when it was modified aka what the name of it should be - w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.zip") - w.Header().Set("Content-Type", "application/zip") + // TODO: Change the served filename to gosora_backup_%timestamp%.zip, the time the file was generated, not when it was modified aka what the name of it should be + h.Set("Content-Disposition", "attachment; filename=gosora_backup.zip") + h.Set("Content-Type", "application/zip") } // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side http.ServeFile(w, r, "./backups/"+backupURL) + err = c.AdminLogs.Create("download", 0, "backup", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } return nil } diff --git a/routes/panel/logs.go b/routes/panel/logs.go index bf1c7333..3479a45e 100644 --- a/routes/panel/logs.go +++ b/routes/panel/logs.go @@ -124,6 +124,26 @@ func adminlogsElementType(action string, elementType string, elementID int, acto } else { out = p.GetTmplPhrasef("panel_logs_administration_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name) } + case "page": + pp, err := c.Pages.Get(elementID) + if err != nil { + pp = &c.CustomPage{Name: p.GetTmplPhrase("page_unknown")} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_page_"+action, "/panel/pages/edit/"+strconv.Itoa(pp.ID), pp.Name, actor.Link, actor.Name) + case "setting": + s, err := c.SettingBox.Load().(c.SettingMap).BypassGet(action) + if err != nil { + s = &c.Setting{Name: p.GetTmplPhrase("setting_unknown")} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_setting_edit", "/panel/settings/edit/"+s.Name, s.Name, actor.Link, actor.Name) + case "word_filter": + wf, err := c.WordFilters.Get(elementID) + if err != nil { + wf = &c.WordFilter{} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_word_filter_"+action, "/panel/settings/word-filters/", wf.ID, actor.Link, actor.Name) + case "backup": + out = p.GetTmplPhrasef("panel_logs_administration_action_backup_"+action, actor.Link, actor.Name) } if out == "" { out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name) diff --git a/routes/panel/pages.go b/routes/panel/pages.go index a5577fb6..454999b4 100644 --- a/routes/panel/pages.go +++ b/routes/panel/pages.go @@ -42,24 +42,28 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro return ferr } - pname := r.PostFormValue("name") - if pname == "" { + name := c.SanitiseSingleLine(r.PostFormValue("name")) + if name == "" { return c.LocalError("No name was provided for this page", w, r, user) } - ptitle := r.PostFormValue("title") - if ptitle == "" { + title := c.SanitiseSingleLine(r.PostFormValue("title")) + if title == "" { return c.LocalError("No title was provided for this page", w, r, user) } - pbody := r.PostFormValue("body") - if pbody == "" { + body := r.PostFormValue("body") + if body == "" { return c.LocalError("No body was provided for this page", w, r, user) } page := c.BlankCustomPage() - page.Name = pname - page.Title = ptitle - page.Body = pbody - _, err := page.Create() + page.Name = name + page.Title = title + page.Body = body + pid, err := page.Create() + if err != nil { + return c.InternalError(err, w, r) + } + err = c.AdminLogs.Create("create", pid, "page", user.LastIP, user.ID) if err != nil { return c.InternalError(err, w, r) } @@ -102,16 +106,16 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid s if err != nil { return c.LocalError("Page ID needs to be an integer", w, r, user) } - pname := r.PostFormValue("name") - if pname == "" { + name := c.SanitiseSingleLine(r.PostFormValue("name")) + if name == "" { return c.LocalError("No name was provided for this page", w, r, user) } - ptitle := r.PostFormValue("title") - if ptitle == "" { + title := c.SanitiseSingleLine(r.PostFormValue("title")) + if title == "" { return c.LocalError("No title was provided for this page", w, r, user) } - pbody := r.PostFormValue("body") - if pbody == "" { + body := r.PostFormValue("body") + if body == "" { return c.LocalError("No body was provided for this page", w, r, user) } @@ -119,13 +123,17 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid s if err != nil { return c.NotFound(w, r, nil) } - page.Name = pname - page.Title = ptitle - page.Body = pbody + page.Name = name + page.Title = title + page.Body = body err = page.Commit() if err != nil { return c.InternalError(err, w, r) } + err = c.AdminLogs.Create("edit", pid, "page", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/pages/?updated=1", http.StatusSeeOther) return nil @@ -145,6 +153,10 @@ func PagesDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid if err != nil { return c.InternalError(err, w, r) } + err = c.AdminLogs.Create("delete", pid, "page", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/pages/?deleted=1", http.StatusSeeOther) return nil diff --git a/routes/panel/settings.go b/routes/panel/settings.go index 280f87c6..ac1fbd3f 100644 --- a/routes/panel/settings.go +++ b/routes/panel/settings.go @@ -77,7 +77,6 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user c.User, sname stri if err != nil { return c.LocalError("The value of this setting couldn't be converted to an integer", w, r, user) } - for index, label := range strings.Split(llist, ",") { itemList = append(itemList, c.OptionLabel{ Label: label, @@ -94,7 +93,7 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user c.User, sname stri return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_setting", &pi}) } -func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sname string) c.RouteError { +func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, name string) c.RouteError { headerLite, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr @@ -103,11 +102,17 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, snam return c.NoPermissions(w, r, user) } - scontent := c.SanitiseBody(r.PostFormValue("value")) - rerr := headerLite.Settings.Update(sname, scontent) + name = c.SanitiseSingleLine(name) + content := c.SanitiseBody(r.PostFormValue("value")) + rerr := headerLite.Settings.Update(name, content) if rerr != nil { return rerr } + // TODO: Avoid this hack + err := c.AdminLogs.Create(name, 0, "setting", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther) return nil diff --git a/routes/panel/word_filters.go b/routes/panel/word_filters.go index 6be12bc7..c022ee06 100644 --- a/routes/panel/word_filters.go +++ b/routes/panel/word_filters.go @@ -47,10 +47,14 @@ func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement replace := strings.TrimSpace(r.PostFormValue("replace")) - err := c.WordFilters.Create(find, replace) + wfid, err := c.WordFilters.Create(find, replace) if err != nil { return c.InternalErrorJSQ(err, w, r, js) } + err = c.AdminLogs.Create("create", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } return successRedirect("/panel/settings/word-filters/", w, r, js) } @@ -70,7 +74,7 @@ func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user c.User, wfid s return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_word_filters_edit", &pi}) } -func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError { +func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, swfid string) c.RouteError { _, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr @@ -80,11 +84,10 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, return c.NoPermissionsJSQ(w, r, user, js) } - id, err := strconv.Atoi(wfid) + wfid, err := strconv.Atoi(swfid) if err != nil { return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) } - find := strings.TrimSpace(r.PostFormValue("find")) if find == "" { return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, js) @@ -92,35 +95,41 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement replace := strings.TrimSpace(r.PostFormValue("replace")) - err = c.WordFilters.Update(id, find, replace) + err = c.WordFilters.Update(wfid, find, replace) if err != nil { return c.InternalErrorJSQ(err, w, r, js) } + err = c.AdminLogs.Create("edit", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) return nil } -func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError { +func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, swfid string) c.RouteError { _, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr } - js := (r.PostFormValue("js") == "1") if !user.Perms.EditSettings { return c.NoPermissionsJSQ(w, r, user, js) } - id, err := strconv.Atoi(wfid) + wfid, err := strconv.Atoi(swfid) if err != nil { return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) } - - err = c.WordFilters.Delete(id) + err = c.WordFilters.Delete(wfid) if err == sql.ErrNoRows { return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js) } + err = c.AdminLogs.Create("delete", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) return nil