|
@@ -3,21 +3,56 @@ package web
|
|
|
import (
|
|
|
"net/http"
|
|
|
"net/url"
|
|
|
+ "strconv"
|
|
|
"strings"
|
|
|
|
|
|
+ "github.com/aws/aws-sdk-go-v2/aws/arn"
|
|
|
"github.com/gin-contrib/sessions"
|
|
|
"github.com/gin-gonic/gin"
|
|
|
+ "github.com/google/uuid"
|
|
|
csrf "github.com/utrack/gin-csrf"
|
|
|
)
|
|
|
|
|
|
-type QueueOp string
|
|
|
+type (
|
|
|
+ // Level is an information level: danger | warning.
|
|
|
+ Level string
|
|
|
+ // QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
|
|
|
+ QueueOp string
|
|
|
+)
|
|
|
|
|
|
const (
|
|
|
+ LevelDanger Level = "danger"
|
|
|
+ LevelInfo Level = "info"
|
|
|
+ LevelWarning Level = "warning"
|
|
|
+
|
|
|
OpDelete QueueOp = "delete"
|
|
|
OpRedrive QueueOp = "redrive"
|
|
|
OpInvalid QueueOp = "invalid"
|
|
|
)
|
|
|
|
|
|
+var confirms = map[QueueOp]struct {
|
|
|
+ question, description string
|
|
|
+ confirm string
|
|
|
+ Level // danger|warning
|
|
|
+}{
|
|
|
+ OpDelete: {
|
|
|
+ confirm: "Delete",
|
|
|
+ description: "These messages cannot be recovered after that step",
|
|
|
+ question: "Do you confirm this deletion request?",
|
|
|
+ Level: LevelDanger,
|
|
|
+ },
|
|
|
+ OpRedrive: {
|
|
|
+ confirm: "Redrive",
|
|
|
+ description: "These messages will be in their source queue after that step",
|
|
|
+ question: "Do you confirm this redriving request?",
|
|
|
+ Level: LevelWarning,
|
|
|
+ },
|
|
|
+ OpInvalid: {
|
|
|
+ description: "The operation requested is invalid",
|
|
|
+ Level: LevelInfo,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
// getOp extracts the operation from the unsafe form data, returning a safe value.
|
|
|
func getOp(u *url.URL) QueueOp {
|
|
|
q := u.Query()
|
|
@@ -38,7 +73,26 @@ func getOp(u *url.URL) QueueOp {
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
-func parseIDs(u *url.URL) []string {
|
|
|
+type IDValidator func(in string) bool
|
|
|
+
|
|
|
+// validateUint is an IDValidator for unsigned integer strings.
|
|
|
+func validateUint(in string) bool {
|
|
|
+ _, err := strconv.Atoi(in)
|
|
|
+ return err == nil
|
|
|
+}
|
|
|
+
|
|
|
+// validateUUID is an IDValidator for UUID strings
|
|
|
+func validateUUID(in string) bool {
|
|
|
+ _, err := uuid.Parse(in)
|
|
|
+ return err == nil
|
|
|
+}
|
|
|
+
|
|
|
+// validateARN is an IDValidator for ARN strings
|
|
|
+func validateARN(in string) bool {
|
|
|
+ return arn.IsARN(in)
|
|
|
+}
|
|
|
+
|
|
|
+func parseIDs(u *url.URL, isValid IDValidator) []string {
|
|
|
const prefix = "id-"
|
|
|
q := u.Query()
|
|
|
var ids []string
|
|
@@ -48,78 +102,48 @@ func parseIDs(u *url.URL) []string {
|
|
|
continue
|
|
|
}
|
|
|
b, a, found := strings.Cut(k, prefix)
|
|
|
- if b == "" && found {
|
|
|
+ if b == "" && found && isValid(a) {
|
|
|
ids = append(ids, a)
|
|
|
}
|
|
|
}
|
|
|
return ids
|
|
|
}
|
|
|
|
|
|
-type confirmData struct {
|
|
|
- question, description string
|
|
|
- confirm, cancel string
|
|
|
- redirect string // site-relative path
|
|
|
-}
|
|
|
-
|
|
|
func makeConfirmHandler() gin.HandlerFunc {
|
|
|
return func(c *gin.Context) {
|
|
|
u := c.Request.URL
|
|
|
op := getOp(u)
|
|
|
- var data confirmData
|
|
|
- switch op {
|
|
|
- case OpDelete:
|
|
|
- data = confirmDelete(c)
|
|
|
- case OpRedrive:
|
|
|
- data = confirmRedrive(c)
|
|
|
- default:
|
|
|
- data = confirmInvalid(c)
|
|
|
-
|
|
|
+ // For real data, probably use validateUUID or validateARN.
|
|
|
+ ids := parseIDs(u, validateUint)
|
|
|
+ if len(ids) == 0 {
|
|
|
+ op = OpInvalid
|
|
|
}
|
|
|
|
|
|
- ids := parseIDs(u)
|
|
|
+ data := confirms[op] // op is guaranteed to be valid by getOp().
|
|
|
+ qName := c.Param("name")
|
|
|
|
|
|
sess := sessions.Default(c)
|
|
|
flashes := sess.Flashes()
|
|
|
token := csrf.GetToken(c)
|
|
|
sess.Save()
|
|
|
|
|
|
- c.HTML(http.StatusOK, "confirm", gin.H{
|
|
|
+ h := gin.H{
|
|
|
"cancel": "Cancel",
|
|
|
+ "cancelURL": "/queue/" + qName,
|
|
|
"confirm": data.confirm,
|
|
|
"csrf": token,
|
|
|
"description": data.description,
|
|
|
"flashes": flashes,
|
|
|
+ "level": data.Level,
|
|
|
"list": ids,
|
|
|
"question": data.question,
|
|
|
- "redirect": data.redirect,
|
|
|
- })
|
|
|
-
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func confirmDelete(c *gin.Context) confirmData {
|
|
|
- qName := c.Param("name")
|
|
|
-
|
|
|
- cd := confirmData{
|
|
|
- cancel: "",
|
|
|
- confirm: "Delete",
|
|
|
- description: "These messages cannot be recovered after that step",
|
|
|
- question: "Do you confirm this deletion request ?",
|
|
|
- redirect: "/queue/" + qName,
|
|
|
- }
|
|
|
- if qName == "" {
|
|
|
- cd.redirect = "/"
|
|
|
+ }
|
|
|
+ if qName == "" {
|
|
|
+ h["cancelURL"] = "/"
|
|
|
+ }
|
|
|
+ if op == OpInvalid {
|
|
|
+ h["list"] = nil
|
|
|
+ }
|
|
|
+ c.HTML(http.StatusOK, "confirm", h)
|
|
|
}
|
|
|
- return cd
|
|
|
-}
|
|
|
-
|
|
|
-func confirmRedrive(c *gin.Context) confirmData {
|
|
|
- cd := confirmData{}
|
|
|
- return cd
|
|
|
-}
|
|
|
-
|
|
|
-func confirmInvalid(c *gin.Context) confirmData {
|
|
|
- cd := confirmData{}
|
|
|
- return cd
|
|
|
-
|
|
|
}
|