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