Add permission check on post editing

This commit is contained in:
Ben Visness 2021-07-21 21:16:10 -05:00
parent 27b8157a89
commit e9ba9b3dde
10 changed files with 103 additions and 24 deletions

View File

@ -0,0 +1,44 @@
package migrations
import (
"context"
"time"
"git.handmade.network/hmn/hmn/src/migration/types"
"git.handmade.network/hmn/hmn/src/oops"
"github.com/jackc/pgx/v4"
)
func init() {
registerMigration(DropSuperuserColumn{})
}
type DropSuperuserColumn struct{}
func (m DropSuperuserColumn) Version() types.MigrationVersion {
return types.MigrationVersion(time.Date(2021, 7, 22, 1, 59, 29, 0, time.UTC))
}
func (m DropSuperuserColumn) Name() string {
return "DropSuperuserColumn"
}
func (m DropSuperuserColumn) Description() string {
return "Drop the is_superuser column on users, in favor of is_staff"
}
func (m DropSuperuserColumn) Up(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx, `
ALTER TABLE auth_user
DROP is_superuser;
`)
if err != nil {
return oops.New(err, "failed to drop superuser column")
}
return nil
}
func (m DropSuperuserColumn) Down(ctx context.Context, tx pgx.Tx) error {
panic("Implement me")
}

View File

@ -17,7 +17,6 @@ type User struct {
DateJoined time.Time `db:"date_joined"` DateJoined time.Time `db:"date_joined"`
LastLogin *time.Time `db:"last_login"` LastLogin *time.Time `db:"last_login"`
IsSuperuser bool `db:"is_superuser"`
IsStaff bool `db:"is_staff"` IsStaff bool `db:"is_staff"`
IsActive bool `db:"is_active"` IsActive bool `db:"is_active"`

View File

@ -160,7 +160,6 @@ func UserToTemplate(u *models.User, currentTheme string) User {
ID: u.ID, ID: u.ID,
Username: u.Username, Username: u.Username,
Email: email, Email: email,
IsSuperuser: u.IsSuperuser,
IsStaff: u.IsStaff, IsStaff: u.IsStaff,
Name: UserDisplayName(u), Name: UserDisplayName(u),

View File

@ -19,8 +19,8 @@
{{ define "content" }} {{ define "content" }}
<div class="content-block ph3 ph0-ns"> <div class="content-block ph3 ph0-ns">
{{ if .ThreadTitle }} {{ if .Title }}
<h2>{{ .ThreadTitle }}</h2> <h2>{{ .Title }}</h2>
{{ end }} {{ end }}
<div class="flex flex-column flex-row-ns"> <div class="flex flex-column flex-row-ns">
<form id="form" action="{{ .SubmitUrl }}" method="post" class="flex-fair-ns"> <form id="form" action="{{ .SubmitUrl }}" method="post" class="flex-fair-ns">

View File

@ -1,7 +1,7 @@
<header class="mb3"> <header class="mb3">
<div class="user-options flex justify-center justify-end-ns"> <div class="user-options flex justify-center justify-end-ns">
{{ if .User }} {{ if .User }}
{{ if .User.IsSuperuser }} {{ if .User.IsStaff }}
<a class="admin-panel" href="{{ .Header.AdminUrl }}"><span class="icon-settings"> Admin</span></a> <a class="admin-panel" href="{{ .Header.AdminUrl }}"><span class="icon-settings"> Admin</span></a>
{{ end }} {{ end }}
<a class="username settings" href="{{ .Header.UserSettingsUrl }}"><span class="icon-settings"></span> {{ .User.Username }}</a> <a class="username settings" href="{{ .Header.UserSettingsUrl }}"><span class="icon-settings"></span> {{ .User.Username }}</a>

View File

@ -120,7 +120,6 @@ type User struct {
ID int ID int
Username string Username string
Email string Email string
IsSuperuser bool
IsStaff bool IsStaff bool
Name string Name string

View File

@ -41,7 +41,7 @@ type forumSubcategoryData struct {
type editorData struct { type editorData struct {
templates.BaseData templates.BaseData
SubmitUrl string SubmitUrl string
ThreadTitle string Title string
SubmitLabel string SubmitLabel string
IsEditing bool // false if new post, true if updating existing one IsEditing bool // false if new post, true if updating existing one
@ -710,7 +710,7 @@ func ForumPostReply(c *RequestContext) ResponseData {
SubmitUrl: hmnurl.BuildForumPostReply(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), requestedThreadId, requestedPostId), SubmitUrl: hmnurl.BuildForumPostReply(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), requestedThreadId, requestedPostId),
SubmitLabel: "Submit Reply", SubmitLabel: "Submit Reply",
ThreadTitle: result.Thread.Title, Title: "Replying to post",
PostReplyingTo: &templatePost, PostReplyingTo: &templatePost,
}, c.Perf) }, c.Perf)
return res return res
@ -820,6 +820,12 @@ func ForumPostEdit(c *RequestContext) ResponseData {
} }
result := postQueryResult.(*postQuery) result := postQueryResult.(*postQuery)
// Ensure that the user is permitted to edit the post
isPostAuthor := result.Author != nil && result.Author.ID == c.CurrentUser.ID
if !(isPostAuthor || c.CurrentUser.IsStaff) {
return FourOhFour(c)
}
baseData := getBaseData(c) baseData := getBaseData(c)
baseData.Title = fmt.Sprintf("Editing \"%s\" | %s", result.Thread.Title, *categoryTree[currentCatId].Name) baseData.Title = fmt.Sprintf("Editing \"%s\" | %s", result.Thread.Title, *categoryTree[currentCatId].Name)
baseData.MathjaxEnabled = true baseData.MathjaxEnabled = true
@ -832,7 +838,7 @@ func ForumPostEdit(c *RequestContext) ResponseData {
res.MustWriteTemplate("editor.html", editorData{ res.MustWriteTemplate("editor.html", editorData{
BaseData: baseData, BaseData: baseData,
SubmitUrl: hmnurl.BuildForumPostEdit(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), requestedThreadId, requestedPostId), SubmitUrl: hmnurl.BuildForumPostEdit(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(currentCatId), requestedThreadId, requestedPostId),
ThreadTitle: result.Thread.Title, Title: result.Thread.Title,
SubmitLabel: "Submit Edited Post", SubmitLabel: "Submit Edited Post",
IsEditing: true, IsEditing: true,
@ -868,8 +874,38 @@ func ForumPostEditSubmit(c *RequestContext) ResponseData {
return FourOhFour(c) return FourOhFour(c)
} }
c.Req.ParseForm() // Ensure that the user is permitted to edit the post
type postResult struct {
AuthorID *int `db:"author.id"`
}
iresult, err := db.QueryOne(c.Context(), c.Conn, postResult{},
`
SELECT $columns
FROM
handmade_post AS post
LEFT JOIN auth_user AS author ON post.author_id = author.id
WHERE
post.category_id = $1
AND post.thread_id = $2
AND post.id = $3
AND NOT post.deleted
ORDER BY postdate
`,
currentCatId,
threadId,
postId,
)
if err != nil && !errors.Is(err, db.ErrNoMatchingRows) {
return ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to get author of post to delete"))
}
result := iresult.(*postResult)
isPostAuthor := result.AuthorID != nil && *result.AuthorID == c.CurrentUser.ID
if !(isPostAuthor || c.CurrentUser.IsStaff) {
return FourOhFour(c)
}
c.Req.ParseForm()
unparsed := c.Req.Form.Get("body") unparsed := c.Req.Form.Get("body")
editReason := c.Req.Form.Get("editreason") editReason := c.Req.Form.Get("editreason")
@ -888,8 +924,8 @@ func createNewForumPostAndVersion(ctx context.Context, tx pgx.Tx, catId, threadI
// Create post // Create post
err := tx.QueryRow(ctx, err := tx.QueryRow(ctx,
` `
INSERT INTO handmade_post (postdate, category_id, thread_id, current_id, author_id, category_kind, project_id, reply_id) INSERT INTO handmade_post (postdate, category_id, thread_id, current_id, author_id, category_kind, project_id, reply_id, preview)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id RETURNING id
`, `,
time.Now(), time.Now(),
@ -900,6 +936,7 @@ func createNewForumPostAndVersion(ctx context.Context, tx pgx.Tx, catId, threadI
models.CatKindForum, models.CatKindForum,
projectId, projectId,
replyId, replyId,
"", // empty preview, will be updated later
).Scan(&postId) ).Scan(&postId)
if err != nil { if err != nil {
panic(oops.New(err, "failed to create post")) panic(oops.New(err, "failed to create post"))

View File

@ -288,7 +288,7 @@ func ProjectHomepage(c *RequestContext) ResponseData {
canView := false canView := false
canEdit := false canEdit := false
if c.CurrentUser != nil { if c.CurrentUser != nil {
if c.CurrentUser.IsSuperuser { if c.CurrentUser.IsStaff {
canView = true canView = true
canEdit = true canEdit = true
} else { } else {

View File

@ -162,6 +162,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
mainRoutes.POST(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReplySubmit)) mainRoutes.POST(hmnurl.RegexForumPostReply, authMiddleware(ForumPostReplySubmit))
mainRoutes.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit)) mainRoutes.GET(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEdit))
mainRoutes.POST(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEditSubmit)) mainRoutes.POST(hmnurl.RegexForumPostEdit, authMiddleware(ForumPostEditSubmit))
// mainRoutes.GET(hmnurl.RegexForumPostDelete, authMiddleware(ForumPostDelete))
mainRoutes.GET(hmnurl.RegexProjectCSS, ProjectCSS) mainRoutes.GET(hmnurl.RegexProjectCSS, ProjectCSS)

View File

@ -100,7 +100,7 @@ func UserProfile(c *RequestContext) ResponseData {
AND ($2 OR (project.flags = 0 AND project.lifecycle = ANY ($3))) AND ($2 OR (project.flags = 0 AND project.lifecycle = ANY ($3)))
`, `,
profileUser.ID, profileUser.ID,
(c.CurrentUser != nil && (profileUser == c.CurrentUser || c.CurrentUser.IsSuperuser)), (c.CurrentUser != nil && (profileUser == c.CurrentUser || c.CurrentUser.IsStaff)),
models.VisibleProjectLifecycles, models.VisibleProjectLifecycles,
) )
if err != nil { if err != nil {