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/users.go

353 lines
9.5 KiB
Go

package main
import (
"fmt"
"log"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
var currentPromo uint = 0
func declareAPIAuthUsersRoutes(router *gin.RouterGroup) {
usersRoutes := router.Group("/users/:uid")
usersRoutes.Use(userHandler)
usersRoutes.Use(sameUserMiddleware)
usersRoutes.GET("", func(c *gin.Context) {
u := c.MustGet("user").(*User)
c.JSON(http.StatusOK, u)
})
declareAPIAuthSurveysRoutes(usersRoutes)
declareAPIAuthKeysRoutes(usersRoutes)
declareAPIAuthWorksRoutes(usersRoutes)
}
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
router.GET("/promos", func(c *gin.Context) {
promos, err := getPromos()
if err != nil {
log.Println("Unable to getPromos:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve promotions. Please try again later."})
return
}
c.JSON(http.StatusOK, promos)
})
router.GET("/session", func(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("Session").(*Session))
})
router.GET("/users", func(c *gin.Context) {
users, err := getUsers()
if err != nil {
log.Println("Unable to getUsers:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve users. Please try again later."})
return
}
var filterPromo *uint64
if c.Query("promo") != "" {
fPromo, err := strconv.ParseUint(c.Query("promo"), 10, 64)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to parse promo: %s", err.Error())})
return
}
filterPromo = &fPromo
}
filterGroup := c.Query("group")
if filterPromo == nil && filterGroup == "" {
c.JSON(http.StatusOK, users)
return
}
var ret []User
for _, u := range users {
if (filterPromo == nil || uint(*filterPromo) == u.Promo) && (filterGroup == "" || strings.Contains(u.Groups, filterGroup)) {
ret = append(ret, u)
}
}
c.JSON(http.StatusOK, ret)
})
// Anonymize old accounts
router.PATCH("/users", func(c *gin.Context) {
users, err := getUsers()
if err != nil {
log.Println("Unable to getUsers:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve users. Please try again later."})
return
}
var filterPromo *uint64
if c.Query("promo") != "" {
fPromo, err := strconv.ParseUint(c.Query("promo"), 10, 64)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to parse promo: %s", err.Error())})
return
}
filterPromo = &fPromo
}
for _, u := range users {
if (filterPromo == nil && u.Promo < currentPromo-1) || (filterPromo != nil && uint(*filterPromo) == u.Promo) {
u.Anonymize()
_, err = u.Update()
if err != nil {
log.Printf("Unable to anonymize %s: %s", u.Login, err.Error())
}
}
}
c.JSON(http.StatusOK, true)
})
usersRoutes := router.Group("/users/:uid")
usersRoutes.Use(userHandler)
usersRoutes.PUT("", updateUser)
usersRoutes.DELETE("", func(c *gin.Context) {
u := c.MustGet("user").(*User)
if _, err := u.Delete(); err != nil {
log.Println("Unable to Delete user:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the user. Please try again later."})
return
}
c.JSON(http.StatusOK, nil)
})
declareAPIAdminUserCorrectionsRoutes(usersRoutes)
declareAPIAdminUserQuestionsRoutes(usersRoutes)
declareAPIAdminWorksRoutes(usersRoutes)
}
func userHandler(c *gin.Context) {
var user *User
uid, err := strconv.Atoi(string(c.Param("uid")))
if err != nil {
user, err = getUserByLogin(c.Param("uid"))
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad user identifier."})
return
}
} else {
user, err = getUser(uid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "User not found."})
return
}
}
c.Set("user", user)
c.Next()
}
func sameUserMiddleware(c *gin.Context) {
user := c.MustGet("user").(*User)
loggeduser := c.MustGet("LoggedUser").(*User)
if user.Id != loggeduser.Id && !loggeduser.IsAdmin {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied."})
return
}
c.Next()
}
type User struct {
Id int64 `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Time time.Time `json:"time"`
Promo uint `json:"promo"`
Groups string `json:"groups,omitempty"`
IsAdmin bool `json:"is_admin"`
}
func getUsers() (users []User, err error) {
if rows, errr := DBQuery("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users ORDER BY promo DESC, id_user DESC"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u User
if err = rows.Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin); err != nil {
return
}
users = append(users, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getFilteredUsers(promo uint, group string) (users []User, err error) {
// Avoid SQL injection: check group name doesn't contains harmful content
var validGroup = regexp.MustCompile(`^[a-z0-9-]*$`)
if !validGroup.MatchString(group) {
return nil, fmt.Errorf("%q is not a valid group name", group)
}
if rows, errr := DBQuery("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE promo = ? AND groups LIKE '%,"+group+",%' ORDER BY promo DESC, id_user DESC", promo); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u User
if err = rows.Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin); err != nil {
return
}
users = append(users, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getPromos() (promos []uint, err error) {
if rows, errr := DBQuery("SELECT DISTINCT promo FROM users ORDER BY promo DESC"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var p uint
if err = rows.Scan(&p); err != nil {
return
}
promos = append(promos, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getUser(id int) (u *User, err error) {
u = new(User)
err = DBQueryRow("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE id_user=?", id).Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin)
return
}
func getUserByLogin(login string) (u *User, err error) {
u = new(User)
err = DBQueryRow("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE login=?", login).Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin)
return
}
func userExists(login string) bool {
var z int
err := DBQueryRow("SELECT 1 FROM users WHERE login=?", login).Scan(&z)
return err == nil && z == 1
}
func NewUser(login string, email string, firstname string, lastname string, promo uint, groups string) (*User, error) {
t := time.Now()
if res, err := DBExec("INSERT INTO users (login, email, firstname, lastname, time, promo, groups) VALUES (?, ?, ?, ?, ?, ?, ?)", login, email, firstname, lastname, t, promo, groups); err != nil {
return nil, err
} else if sid, err := res.LastInsertId(); err != nil {
return nil, err
} else {
return &User{sid, login, email, firstname, lastname, t, promo, groups, false}, nil
}
}
func (u *User) Anonymize() {
u.Login = fmt.Sprintf("Anonyme #%d", u.Id)
u.Email = fmt.Sprintf("anonyme-%d@non-existant.email", u.Id)
u.Firstname = "Arnaud"
u.Lastname = "Nimes"
}
func (u User) Update() (int64, error) {
if res, err := DBExec("UPDATE users SET login = ?, email = ?, firstname = ?, lastname = ?, time = ?, promo = ?, groups = ? WHERE id_user = ?", u.Login, u.Email, u.Firstname, u.Lastname, u.Time, u.Promo, u.Groups, u.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func (u User) MakeAdmin(value bool) (User, error) {
if _, err := DBExec("UPDATE users SET is_admin = ? WHERE id_user = ?", value, u.Id); err != nil {
return u, err
} else {
u.IsAdmin = value
return u, err
}
}
func (u User) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM users WHERE id_user = ?", u.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func ClearUsers() (int64, error) {
if res, err := DBExec("DELETE FROM users"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func updateUser(c *gin.Context) {
current := c.MustGet("user").(*User)
var new User
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
current.Login = new.Login
current.Email = new.Email
current.Firstname = new.Firstname
current.Lastname = new.Lastname
current.Time = new.Time
current.Promo = new.Promo
current.Groups = new.Groups
if u, err := current.Update(); err != nil {
log.Println("Unable to Update user:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update the given user. Please try again later."})
return
} else {
c.JSON(http.StatusOK, u)
}
}