Add permission check on post editing
This commit is contained in:
parent
27b8157a89
commit
e9ba9b3dde
|
@ -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")
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue