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 } }