This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
atsebay.t/shares.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
}
}