From b30c368c4827a81d32a0c56df1bcda69daed9184 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Wed, 18 Nov 2020 18:07:53 +0500 Subject: [PATCH] Sign edits and refactor mutators a little --- history/operations.go | 19 ++++++---- http_mutators.go | 84 +++++++++++++++++++++---------------------- hypha.go | 51 +++++++++++++++++--------- user/group.go | 10 +++--- user/user.go | 2 +- 5 files changed, 93 insertions(+), 73 deletions(-) diff --git a/history/operations.go b/history/operations.go index 0507483..e16c367 100644 --- a/history/operations.go +++ b/history/operations.go @@ -8,6 +8,7 @@ import ( "path/filepath" "sync" + "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -29,7 +30,7 @@ const ( type HistoryOp struct { // All errors are appended here. Errs []error - opType OpType + Type OpType userMsg string name string email string @@ -39,8 +40,10 @@ type HistoryOp struct { func Operation(opType OpType) *HistoryOp { gitMutex.Lock() hop := &HistoryOp{ - Errs: []error{}, - opType: opType, + Errs: []error{}, + name: "anon", + email: "anon@mycorrhiza", + Type: opType, } return hop } @@ -116,9 +119,11 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp { return hop } -// WithSignature sets a signature for the future commit. You need to pass a username only, the rest is upon us (including email and time). -func (hop *HistoryOp) WithSignature(username string) *HistoryOp { - hop.name = username - hop.email = username + "@mycorrhiza" // A fake email, why not +// WithUser sets a user for the commit. +func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp { + if u.Group != user.UserAnon { + hop.name = u.Name + hop.email = u.Name + "@mycorrhiza" + } return hop } diff --git a/http_mutators.go b/http_mutators.go index 59f464b..232fed3 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -11,12 +11,14 @@ import ( ) func init() { - http.HandleFunc("/upload-binary/", handlerUploadBinary) - http.HandleFunc("/upload-text/", handlerUploadText) + // Those that do not actually mutate anything: http.HandleFunc("/edit/", handlerEdit) http.HandleFunc("/delete-ask/", handlerDeleteAsk) - http.HandleFunc("/delete-confirm/", handlerDeleteConfirm) http.HandleFunc("/rename-ask/", handlerRenameAsk) + // And those that do mutate something: + http.HandleFunc("/upload-binary/", handlerUploadBinary) + http.HandleFunc("/upload-text/", handlerUploadText) + http.HandleFunc("/delete-confirm/", handlerDeleteConfirm) http.HandleFunc("/rename-confirm/", handlerRenameConfirm) } @@ -38,20 +40,16 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( hyphaName = HyphaNameFromRq(rq, "rename-confirm") - hyphaData, isOld = HyphaStorage[hyphaName] + _, isOld = HyphaStorage[hyphaName] newName = CanonicalName(rq.PostFormValue("new-name")) _, newNameIsUsed = HyphaStorage[newName] - recursive bool + recursive = rq.PostFormValue("recursive") == "true" + u = user.FromRequest(rq).OrAnon() ) - if ok := user.CanProceed(rq, "rename-confirm"); !ok { + switch { + case !u.CanProceed("rename-confirm"): HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.") log.Println("Rejected", rq.URL) - return - } - if rq.PostFormValue("recursive") == "true" { - recursive = true - } - switch { case newNameIsUsed: HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists", fmt.Sprintf("Hypha named %s already exists.", hyphaName, hyphaName)) @@ -65,12 +63,12 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name", "Invalid new name. Names cannot contain characters ^?!:#@><*|\"\\'&%") default: - if hop := hyphaData.RenameHypha(hyphaName, newName, recursive); len(hop.Errs) == 0 { - http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther) - } else { + if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 { HttpErr(w, http.StatusInternalServerError, hyphaName, "Error: could not rename hypha", fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: %v", hop.Errs)) + } else { + http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther) } } } @@ -96,27 +94,27 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) { var ( hyphaName = HyphaNameFromRq(rq, "delete-confirm") hyphaData, isOld = HyphaStorage[hyphaName] + u = user.FromRequest(rq) ) - if ok := user.CanProceed(rq, "delete-confirm"); !ok { + if !u.CanProceed("delete-confirm") { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.") log.Println("Rejected", rq.URL) return } - if isOld { - // If deleted successfully - if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 { - http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) - } else { - HttpErr(w, http.StatusInternalServerError, hyphaName, - "Error: could not delete hypha", - fmt.Sprintf("Could not delete this hypha due to an internal error. Server errors: %v", hop.Errs)) - } - } else { + if !isOld { // The precondition is to have the hypha in the first place. HttpErr(w, http.StatusPreconditionFailed, hyphaName, "Error: no such hypha", "Could not delete this hypha because it does not exist.") + return } + if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 { + HttpErr(w, http.StatusInternalServerError, hyphaName, + "Error: could not delete hypha", + fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: %v", hop.Errs)) + return + } + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } // handlerEdit shows the edit form. It doesn't edit anything actually. @@ -151,23 +149,20 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { func handlerUploadText(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( - hyphaName = HyphaNameFromRq(rq, "upload-text") - hyphaData, isOld = HyphaStorage[hyphaName] - textData = rq.PostFormValue("text") + hyphaName = HyphaNameFromRq(rq, "upload-text") + textData = rq.PostFormValue("text") + u = user.FromRequest(rq).OrAnon() ) if ok := user.CanProceed(rq, "upload-text"); !ok { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.") log.Println("Rejected", rq.URL) return } - if !isOld { - hyphaData = &HyphaData{} - } if textData == "" { HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed") return } - if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 { + if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 { HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) } else { http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) @@ -177,36 +172,37 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { // handlerUploadBinary uploads a new binary part for the hypha. func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) - hyphaName := HyphaNameFromRq(rq, "upload-binary") - if ok := user.CanProceed(rq, "upload-binary"); !ok { + var ( + hyphaName = HyphaNameFromRq(rq, "upload-binary") + u = user.FromRequest(rq) + ) + if !u.CanProceed("upload-binary") { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") log.Println("Rejected", rq.URL) return } - rq.ParseMultipartForm(10 << 20) + rq.ParseMultipartForm(10 << 20) // Set upload limit file, handler, err := rq.FormFile("binary") if file != nil { defer file.Close() } + // If file is not passed: if err != nil { HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed") return } + // If file is passed: var ( - hyphaData, isOld = HyphaStorage[hyphaName] - mime = handler.Header.Get("Content-Type") + mime = handler.Header.Get("Content-Type") + hop = UploadBinary(hyphaName, mime, file, u) ) - if !isOld { - hyphaData = &HyphaData{} - } - hop := hyphaData.UploadBinary(hyphaName, mime, file, isOld) if len(hop.Errs) != 0 { HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) - } else { - http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) + return } + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } diff --git a/hypha.go b/hypha.go index a49b785..0e6e583 100644 --- a/hypha.go +++ b/hypha.go @@ -12,6 +12,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -33,6 +34,15 @@ func init() { } } +// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist. +func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) { + hyphaData, isOld = HyphaStorage[hyphaName] + if hyphaData == nil { + hyphaData = &HyphaData{} + } + return +} + // HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. type HyphaData struct { textPath string @@ -40,10 +50,16 @@ type HyphaData struct { } // uploadHelp is a helper function for UploadText and UploadBinary -func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, originalFullPath *string, isOld bool, data []byte) *history.HistoryOp { +func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp { var ( - fullPath = filepath.Join(WikiDir, hyphaName+ext) + hyphaData, isOld = GetHyphaData(hyphaName) + fullPath = filepath.Join(WikiDir, hyphaName+ext) + originalFullPath = &hyphaData.textPath ) + if hop.Type == history.TypeEditBinary { + originalFullPath = &hyphaData.binaryPath + } + if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { return hop.WithError(err) } @@ -51,30 +67,34 @@ func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, o if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { return hop.WithError(err) } + if isOld && *originalFullPath != fullPath && *originalFullPath != "" { if err := history.Rename(*originalFullPath, fullPath); err != nil { return hop.WithError(err) } log.Println("Move", *originalFullPath, "to", fullPath) } + // New hyphae must be added to the hypha storage if !isOld { - HyphaStorage[hyphaName] = hd + HyphaStorage[hyphaName] = hyphaData } *originalFullPath = fullPath - log.Printf("%v\n", *hd) return hop.WithFiles(fullPath). - WithSignature("anon"). + WithUser(u). Apply() } -// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`. -func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp { - hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)) - return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData)) +// UploadText loads a new text part from `textData` for hypha `hyphaName`. +func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp { + return uploadHelp( + history. + Operation(history.TypeEditText). + WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)), + hyphaName, ".myco", []byte(textData), u) } // UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`. -func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp { +func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp { var ( hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime)) data, err = ioutil.ReadAll(file) @@ -82,16 +102,15 @@ func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, i if err != nil { return hop.WithError(err).Apply() } - - return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data) + return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u) } // DeleteHypha deletes hypha and makes a history record about that. -func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { +func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp { hop := history.Operation(history.TypeDeleteHypha). WithFilesRemoved(hd.textPath, hd.binaryPath). WithMsg(fmt.Sprintf("Delete ‘%s’", hyphaName)). - WithSignature("anon"). + WithUser(u). Apply() if len(hop.Errs) == 0 { delete(HyphaStorage, hyphaName) @@ -138,7 +157,7 @@ func relocateHyphaData(hyphaNames []string, replaceName func(string) string) { } // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. -func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *history.HistoryOp { +func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp { var ( replaceName = func(str string) string { return strings.Replace(str, hyphaName, newName, 1) @@ -157,7 +176,7 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his } hop.WithFilesRenamed(renameMap). WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)). - WithSignature("anon"). + WithUser(u). Apply() if len(hop.Errs) == 0 { relocateHyphaData(hyphaNames, replaceName) diff --git a/user/group.go b/user/group.go index d06fb29..6a9a296 100644 --- a/user/group.go +++ b/user/group.go @@ -62,9 +62,9 @@ func (ug UserGroup) CanAccessRoute(route string) bool { } func CanProceed(rq *http.Request, route string) bool { - ug := UserAnon - if u := FromRequest(rq); u != nil { - ug = u.Group - } - return ug.CanAccessRoute(route) + return FromRequest(rq).OrAnon().CanProceed(route) +} + +func (u *User) CanProceed(route string) bool { + return u.Group.CanAccessRoute(route) } diff --git a/user/user.go b/user/user.go index 3c999c8..7169760 100644 --- a/user/user.go +++ b/user/user.go @@ -44,7 +44,7 @@ func FromRequest(rq *http.Request) *User { if err != nil { return nil } - return UserStorage.userByToken(cookie.Value) + return UserStorage.userByToken(cookie.Value).OrAnon() } func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {