confirm.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package web
  2. import (
  3. "net/http"
  4. "net/url"
  5. "strconv"
  6. "strings"
  7. "github.com/aws/aws-sdk-go-v2/aws/arn"
  8. "github.com/gin-contrib/sessions"
  9. "github.com/gin-gonic/gin"
  10. "github.com/google/uuid"
  11. csrf "github.com/utrack/gin-csrf"
  12. )
  13. type (
  14. // Level is an information level: danger | warning.
  15. Level string
  16. // QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
  17. QueueOp string
  18. )
  19. const (
  20. LevelDanger Level = "danger"
  21. LevelInfo Level = "info"
  22. LevelWarning Level = "warning"
  23. OpDelete QueueOp = "delete"
  24. OpRedrive QueueOp = "redrive"
  25. OpInvalid QueueOp = "invalid"
  26. )
  27. var confirms = map[QueueOp]struct {
  28. question, description string
  29. confirm string
  30. Level // danger|warning
  31. }{
  32. OpDelete: {
  33. confirm: "Delete",
  34. description: "These messages cannot be recovered after that step",
  35. question: "Do you confirm this deletion request?",
  36. Level: LevelDanger,
  37. },
  38. OpRedrive: {
  39. confirm: "Redrive",
  40. description: "These messages will be in their source queue after that step",
  41. question: "Do you confirm this redriving request?",
  42. Level: LevelWarning,
  43. },
  44. OpInvalid: {
  45. description: "The operation requested is invalid",
  46. Level: LevelInfo,
  47. },
  48. }
  49. // getOp extracts the operation from the unsafe form data, returning a safe value.
  50. func getOp(u *url.URL) QueueOp {
  51. q := u.Query()
  52. found := 0
  53. var res QueueOp
  54. for key := range q {
  55. switch op := QueueOp(strings.ToLower(key)); op {
  56. case OpDelete, OpRedrive:
  57. res = op
  58. found++
  59. default:
  60. continue
  61. }
  62. }
  63. if found != 1 {
  64. return OpInvalid
  65. }
  66. return res
  67. }
  68. type IDValidator func(in string) bool
  69. // validateUint is an IDValidator for unsigned integer strings.
  70. func validateUint(in string) bool {
  71. _, err := strconv.Atoi(in)
  72. return err == nil
  73. }
  74. // validateUUID is an IDValidator for UUID strings
  75. func validateUUID(in string) bool {
  76. _, err := uuid.Parse(in)
  77. return err == nil
  78. }
  79. // validateARN is an IDValidator for ARN strings
  80. func validateARN(in string) bool {
  81. return arn.IsARN(in)
  82. }
  83. func parseIDs(u *url.URL, isValid IDValidator) []string {
  84. const prefix = "id-"
  85. q := u.Query()
  86. var ids []string
  87. for k, vs := range q {
  88. // Weed out bad keys, bad value lengths, and non-matching checkbox statuses.
  89. if pos := strings.Index(k, prefix); pos == -1 || len(vs) != 1 || vs[0] != "on" {
  90. continue
  91. }
  92. b, a, found := strings.Cut(k, prefix)
  93. if b == "" && found && isValid(a) {
  94. ids = append(ids, a)
  95. }
  96. }
  97. return ids
  98. }
  99. func makeConfirmHandler() gin.HandlerFunc {
  100. return func(c *gin.Context) {
  101. u := c.Request.URL
  102. op := getOp(u)
  103. // For real data, probably use validateUUID or validateARN.
  104. ids := parseIDs(u, validateUint)
  105. if len(ids) == 0 {
  106. op = OpInvalid
  107. }
  108. data := confirms[op] // op is guaranteed to be valid by getOp().
  109. qName := c.Param("name")
  110. sess := sessions.Default(c)
  111. flashes := sess.Flashes()
  112. token := csrf.GetToken(c)
  113. sess.Save()
  114. h := gin.H{
  115. "cancel": "Cancel",
  116. "cancelURL": "/queue/" + qName,
  117. "confirm": data.confirm,
  118. "csrf": token,
  119. "description": data.description,
  120. "flashes": flashes,
  121. "level": data.Level,
  122. "list": ids,
  123. "question": data.question,
  124. }
  125. if qName == "" {
  126. h["cancelURL"] = "/"
  127. }
  128. if op == OpInvalid {
  129. h["list"] = nil
  130. }
  131. c.HTML(http.StatusOK, "confirm", h)
  132. }
  133. }