Can share survey results with a secret shared key
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
9fd73ce235
commit
fff8b821c5
10 changed files with 367 additions and 7 deletions
187
shares.go
Normal file
187
shares.go
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func declareAPISharesRoutes(router *gin.RouterGroup) {
|
||||
surveysRoutes := router.Group("/s/surveys/:sid")
|
||||
surveysRoutes.Use(surveyHandler)
|
||||
surveysRoutes.Use(sharesAccessHandler)
|
||||
|
||||
surveysRoutes.GET("/", func(c *gin.Context) {
|
||||
share := c.MustGet("survey_share").(*SurveyShared)
|
||||
share.Count += 1
|
||||
share.Update()
|
||||
|
||||
c.JSON(http.StatusOK, c.MustGet("survey").(*Survey))
|
||||
})
|
||||
|
||||
surveysRoutes.GET("/questions", func(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
if questions, err := s.GetQuestions(); err != nil {
|
||||
log.Println("Unable to getQuestions:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, questions)
|
||||
}
|
||||
})
|
||||
|
||||
questionsRoutes := surveysRoutes.Group("/questions/:qid")
|
||||
questionsRoutes.Use(questionHandler)
|
||||
|
||||
questionsRoutes.GET("/", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, c.MustGet("question").(*Question))
|
||||
})
|
||||
|
||||
questionsRoutes.GET("/proposals", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
proposals, err := q.GetProposals()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetProposals(qid=%d): %s", q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during proposals retrieving"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, proposals)
|
||||
})
|
||||
|
||||
questionsRoutes.GET("/responses", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
res, err := q.GetResponses()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetResponses(qid=%d): %s", q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during responses retrieval."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, res)
|
||||
})
|
||||
}
|
||||
|
||||
func sharesAccessHandler(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
secret := c.Query("secret")
|
||||
|
||||
shares, err := s.getShares()
|
||||
if err != nil {
|
||||
log.Printf("Unable to getShares(sid=%d): %s", s.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something went wrong when authenticating the query."})
|
||||
return
|
||||
}
|
||||
|
||||
for _, share := range shares {
|
||||
if share.Authenticate(secret) {
|
||||
c.Set("survey_share", share)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Not authorized"})
|
||||
}
|
||||
|
||||
type SurveyShared struct {
|
||||
Id int64 `json:"id"`
|
||||
IdSurvey int64 `json:"id_survey"`
|
||||
Count int64 `json:"count"`
|
||||
secret []byte
|
||||
}
|
||||
|
||||
func (s *Survey) Share() (*SurveyShared, error) {
|
||||
secret := make([]byte, 32)
|
||||
if _, err := rand.Read(secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res, err := DBExec("INSERT INTO survey_shared (id_survey, secret) VALUES (?, ?)", s.Id, secret); err != nil {
|
||||
return nil, err
|
||||
} else if sid, err := res.LastInsertId(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &SurveyShared{sid, s.Id, 0, secret}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *SurveyShared) getMAC() []byte {
|
||||
mac := hmac.New(sha512.New, sh.secret)
|
||||
mac.Write([]byte(fmt.Sprintf("%d", sh.IdSurvey)))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func (sh *SurveyShared) GetURL() (*url.URL, error) {
|
||||
u, err := url.Parse(oidcRedirectURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Path = filepath.Join(baseURL, "results")
|
||||
u.RawQuery = url.Values{
|
||||
"secret": []string{base64.RawURLEncoding.EncodeToString(sh.getMAC())},
|
||||
"survey": []string{fmt.Sprintf("%d", sh.IdSurvey)},
|
||||
}.Encode()
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Survey) getShares() (shares []*SurveyShared, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_share, id_survey, secret, count FROM survey_shared"); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var sh SurveyShared
|
||||
if err = rows.Scan(&sh.Id, &sh.IdSurvey, &sh.secret, &sh.Count); err != nil {
|
||||
return
|
||||
}
|
||||
shares = append(shares, &sh)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *SurveyShared) Authenticate(secret string) bool {
|
||||
messageMAC, err := base64.RawURLEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return hmac.Equal(messageMAC, sh.getMAC())
|
||||
}
|
||||
|
||||
func (sh *SurveyShared) Update() (*SurveyShared, error) {
|
||||
if _, err := DBExec("UPDATE survey_shared SET id_survey = ?, secret = ?, count = ? WHERE id_share = ?", sh.IdSurvey, sh.secret, sh.Count, sh.Id); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return sh, err
|
||||
}
|
||||
}
|
||||
|
||||
func (sh SurveyShared) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM survey_shared WHERE id_share = ?", sh.Id); err != nil {
|
||||
return 0, err
|
||||
} else if nb, err := res.RowsAffected(); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return nb, err
|
||||
}
|
||||
}
|
||||
Reference in a new issue