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.
This commit is contained in:
Azareal 2019-11-09 07:46:50 +10:00
parent 7e1372b0b4
commit ffdc9e7705
8 changed files with 129 additions and 56 deletions

View File

@ -38,12 +38,12 @@ func BlankCustomPage() *CustomPage {
return new(CustomPage) return new(CustomPage)
} }
func (page *CustomPage) AddAllowedGroup(gid int) { func (p *CustomPage) AddAllowedGroup(gid int) {
page.AllowedGroups = append(page.AllowedGroups, gid) p.AllowedGroups = append(p.AllowedGroups, gid)
} }
func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { func (p *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) {
for _, group := range page.AllowedGroups { for _, group := range p.AllowedGroups {
rawAllowedGroups += strconv.Itoa(group) + "," rawAllowedGroups += strconv.Itoa(group) + ","
} }
if len(rawAllowedGroups) > 0 { if len(rawAllowedGroups) > 0 {
@ -52,18 +52,17 @@ func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) {
return rawAllowedGroups return rawAllowedGroups
} }
func (page *CustomPage) Commit() error { func (p *CustomPage) Commit() error {
_, err := customPageStmts.update.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID, page.ID) _, err := customPageStmts.update.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID, p.ID)
Pages.Reload(page.ID) Pages.Reload(p.ID)
return err return err
} }
func (page *CustomPage) Create() (int, error) { func (p *CustomPage) Create() (int, error) {
res, err := customPageStmts.create.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID) res, err := customPageStmts.create.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
pid64, err := res.LastInsertId() pid64, err := res.LastInsertId()
return int(pid64), err return int(pid64), err
} }

View File

@ -4,7 +4,7 @@ import (
"database/sql" "database/sql"
"sync/atomic" "sync/atomic"
"github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
) )
// TODO: Move some features into methods on this? // TODO: Move some features into methods on this?
@ -19,7 +19,8 @@ var WordFilters WordFilterStore
type WordFilterStore interface { type WordFilterStore interface {
ReloadAll() error ReloadAll() error
GetAll() (filters map[int]*WordFilter, err 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 Delete(id int) error
Update(id int, find string, replacement string) error Update(id int, find string, replacement string) error
Length() int Length() int
@ -31,6 +32,7 @@ type DefaultWordFilterStore struct {
box atomic.Value // An atomic value holding a WordFilterMap box atomic.Value // An atomic value holding a WordFilterMap
getAll *sql.Stmt getAll *sql.Stmt
get *sql.Stmt
create *sql.Stmt create *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
update *sql.Stmt update *sql.Stmt
@ -41,6 +43,7 @@ func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore,
wf := "word_filters" wf := "word_filters"
store := &DefaultWordFilterStore{ store := &DefaultWordFilterStore{
getAll: acc.Select(wf).Columns("wfid,find,replacement").Prepare(), 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(), create: acc.Insert(wf).Columns("find,replacement").Fields("?,?").Prepare(),
delete: acc.Delete(wf).Where("wfid = ?").Prepare(), delete: acc.Delete(wf).Where("wfid = ?").Prepare(),
update: acc.Update(wf).Set("find = ?, replacement = ?").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 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 // Create adds a new word filter to the database and refreshes the memory cache
func (s *DefaultWordFilterStore) Create(find string, replace string) error { func (s *DefaultWordFilterStore) Create(find string, replace string) (int, error) {
_, err := s.create.Exec(find, replace) res, err := s.create.Exec(find, replace)
if err != nil { 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 // Delete removes a word filter from the database and refreshes the memory cache

View File

@ -1021,6 +1021,8 @@
"topic_unknown":"Unknown", "topic_unknown":"Unknown",
"group_unknown":"Unknown", "group_unknown":"Unknown",
"forum_unknown":"Unknown", "forum_unknown":"Unknown",
"page_unknown":"Unknown",
"setting_unknown":"unknown",
"panel_logs_administration_head":"Admin Action Logs", "panel_logs_administration_head":"Admin Action Logs",
"panel_logs_administration_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_administration_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
@ -1032,6 +1034,14 @@
"panel_logs_administration_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>", "panel_logs_administration_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", "panel_logs_administration_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_administration_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_create":"Page <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_delete":"Page <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_edit":"Page <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_setting_edit":"Setting <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_create":"Word Filter <a href='%s'>%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_delete":"Word Filter <a href='%s'>%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_edit":"Word Filter <a href='%s'>%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_backup_download":"A backup was downloaded by <a href='%s'>%s</a>",
"panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>", "panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>",
"panel_logs_administration_no_logs":"There aren't any events logged.", "panel_logs_administration_no_logs":"There aren't any events logged.",

View File

@ -28,19 +28,24 @@ func Backups(w http.ResponseWriter, r *http.Request, user c.User, backupURL stri
if err != nil { if err != nil {
return c.NotFound(w, r, basePage.Header) 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" { 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 // 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") h.Set("Content-Disposition", "attachment; filename=gosora_backup.sql")
w.Header().Set("Content-Type", "application/sql") h.Set("Content-Type", "application/sql")
} else { } 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 // 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") h.Set("Content-Disposition", "attachment; filename=gosora_backup.zip")
w.Header().Set("Content-Type", "application/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 // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
http.ServeFile(w, r, "./backups/"+backupURL) 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 return nil
} }

View File

@ -124,6 +124,26 @@ func adminlogsElementType(action string, elementType string, elementID int, acto
} else { } else {
out = p.GetTmplPhrasef("panel_logs_administration_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name) 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 == "" { if out == "" {
out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name)

View File

@ -42,24 +42,28 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro
return ferr return ferr
} }
pname := r.PostFormValue("name") name := c.SanitiseSingleLine(r.PostFormValue("name"))
if pname == "" { if name == "" {
return c.LocalError("No name was provided for this page", w, r, user) return c.LocalError("No name was provided for this page", w, r, user)
} }
ptitle := r.PostFormValue("title") title := c.SanitiseSingleLine(r.PostFormValue("title"))
if ptitle == "" { if title == "" {
return c.LocalError("No title was provided for this page", w, r, user) return c.LocalError("No title was provided for this page", w, r, user)
} }
pbody := r.PostFormValue("body") body := r.PostFormValue("body")
if pbody == "" { if body == "" {
return c.LocalError("No body was provided for this page", w, r, user) return c.LocalError("No body was provided for this page", w, r, user)
} }
page := c.BlankCustomPage() page := c.BlankCustomPage()
page.Name = pname page.Name = name
page.Title = ptitle page.Title = title
page.Body = pbody page.Body = body
_, err := page.Create() 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 { if err != nil {
return c.InternalError(err, w, r) 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 { if err != nil {
return c.LocalError("Page ID needs to be an integer", w, r, user) return c.LocalError("Page ID needs to be an integer", w, r, user)
} }
pname := r.PostFormValue("name") name := c.SanitiseSingleLine(r.PostFormValue("name"))
if pname == "" { if name == "" {
return c.LocalError("No name was provided for this page", w, r, user) return c.LocalError("No name was provided for this page", w, r, user)
} }
ptitle := r.PostFormValue("title") title := c.SanitiseSingleLine(r.PostFormValue("title"))
if ptitle == "" { if title == "" {
return c.LocalError("No title was provided for this page", w, r, user) return c.LocalError("No title was provided for this page", w, r, user)
} }
pbody := r.PostFormValue("body") body := r.PostFormValue("body")
if pbody == "" { if body == "" {
return c.LocalError("No body was provided for this page", w, r, user) 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 { if err != nil {
return c.NotFound(w, r, nil) return c.NotFound(w, r, nil)
} }
page.Name = pname page.Name = name
page.Title = ptitle page.Title = title
page.Body = pbody page.Body = body
err = page.Commit() err = page.Commit()
if err != nil { if err != nil {
return c.InternalError(err, w, r) 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) http.Redirect(w, r, "/panel/pages/?updated=1", http.StatusSeeOther)
return nil return nil
@ -145,6 +153,10 @@ func PagesDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid
if err != nil { if err != nil {
return c.InternalError(err, w, r) 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) http.Redirect(w, r, "/panel/pages/?deleted=1", http.StatusSeeOther)
return nil return nil

View File

@ -77,7 +77,6 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user c.User, sname stri
if err != nil { if err != nil {
return c.LocalError("The value of this setting couldn't be converted to an integer", w, r, user) 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, ",") { for index, label := range strings.Split(llist, ",") {
itemList = append(itemList, c.OptionLabel{ itemList = append(itemList, c.OptionLabel{
Label: label, 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}) 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) headerLite, ferr := c.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -103,11 +102,17 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, snam
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
scontent := c.SanitiseBody(r.PostFormValue("value")) name = c.SanitiseSingleLine(name)
rerr := headerLite.Settings.Update(sname, scontent) content := c.SanitiseBody(r.PostFormValue("value"))
rerr := headerLite.Settings.Update(name, content)
if rerr != nil { if rerr != nil {
return rerr 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) http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther)
return nil return nil

View File

@ -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 // 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")) replace := strings.TrimSpace(r.PostFormValue("replace"))
err := c.WordFilters.Create(find, replace) wfid, err := c.WordFilters.Create(find, replace)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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) 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}) 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) _, ferr := c.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
@ -80,11 +84,10 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, user, js)
} }
id, err := strconv.Atoi(wfid) wfid, err := strconv.Atoi(swfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js)
} }
find := strings.TrimSpace(r.PostFormValue("find")) find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" { if find == "" {
return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, js) 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 // 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")) replace := strings.TrimSpace(r.PostFormValue("replace"))
err = c.WordFilters.Update(id, find, replace) err = c.WordFilters.Update(wfid, find, replace)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) 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) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)
return nil 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) _, ferr := c.SimplePanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := (r.PostFormValue("js") == "1") js := (r.PostFormValue("js") == "1")
if !user.Perms.EditSettings { if !user.Perms.EditSettings {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, user, js)
} }
id, err := strconv.Atoi(wfid) wfid, err := strconv.Atoi(swfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js)
} }
err = c.WordFilters.Delete(wfid)
err = c.WordFilters.Delete(id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js) 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) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)
return nil return nil