188 lines
4.7 KiB
Go
188 lines
4.7 KiB
Go
|
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
|
||
|
}
|
||
|
}
|