2016-08-01 23:32:20 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// User is the model struct which represents a kolide user
|
|
|
|
type User struct {
|
|
|
|
BaseModel
|
|
|
|
Username string `gorm:"not null;unique_index:idx_user_unique_username"`
|
|
|
|
Password []byte `gorm:"not null"`
|
|
|
|
Salt string `gorm:"not null"`
|
|
|
|
Name string
|
|
|
|
Email string `gorm:"not null;unique_index:idx_user_unique_email"`
|
|
|
|
Admin bool `gorm:"not null"`
|
|
|
|
Enabled bool `gorm:"not null"`
|
|
|
|
NeedsPasswordReset bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUser is a wrapper around the creation of a new user.
|
|
|
|
// NewUser exists largely to allow the API to simply accept a string password
|
|
|
|
// while using the applications password hashing mechanisms to salt and hash the
|
|
|
|
// password.
|
|
|
|
func NewUser(db *gorm.DB, username, password, email string, admin, needsPasswordReset bool) (*User, error) {
|
|
|
|
salt, hash, err := SaltAndHashPassword(password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
user := &User{
|
|
|
|
Username: username,
|
|
|
|
Password: hash,
|
|
|
|
Salt: salt,
|
|
|
|
Email: email,
|
|
|
|
Admin: admin,
|
|
|
|
Enabled: true,
|
|
|
|
NeedsPasswordReset: needsPasswordReset,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = db.Create(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidatePassword accepts a potential password for a given user and attempts
|
|
|
|
// to validate it against the hash stored in the database after joining the
|
|
|
|
// supplied password with the stored password salt
|
|
|
|
func (u *User) ValidatePassword(password string) error {
|
2016-08-04 22:38:13 +00:00
|
|
|
saltAndPass := []byte(fmt.Sprintf("%s%s", password, u.Salt))
|
2016-08-01 23:32:20 +00:00
|
|
|
return bcrypt.CompareHashAndPassword(u.Password, saltAndPass)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetPassword accepts a new password for a user object and updates the salt
|
|
|
|
// and hash for that user in the database. This function assumes that the
|
|
|
|
// appropriate authorization checks have already occurred by the caller.
|
|
|
|
func (u *User) SetPassword(db *gorm.DB, password string) error {
|
|
|
|
salt, hash, err := SaltAndHashPassword(password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
u.Salt = salt
|
|
|
|
u.Password = hash
|
|
|
|
return db.Save(u).Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// MakeAdmin is a simple wrapper around promoting a user to an administrator.
|
|
|
|
// If the user is already an admin, this function will return without modifying
|
|
|
|
// the database
|
|
|
|
func (u *User) MakeAdmin(db *gorm.DB) error {
|
|
|
|
if !u.Admin {
|
|
|
|
u.Admin = true
|
|
|
|
return db.Save(&u).Error
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// User management web endpoints
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2016-08-01 23:32:20 +00:00
|
|
|
type GetUserRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetUserResponseBody struct {
|
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Admin bool `json:"admin"`
|
|
|
|
Enabled bool `json:"enabled"`
|
|
|
|
NeedsPasswordReset bool `json:"needs_password_reset"`
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetUser(c *gin.Context) {
|
|
|
|
var body GetUserRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing GetUser post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
|
|
|
if !vc.CanPerformActions() {
|
|
|
|
UnauthorizedError(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
var user User
|
2016-08-02 22:39:20 +00:00
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
if !vc.CanPerformReadActionOnUser(&user) {
|
2016-08-01 23:32:20 +00:00
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
2016-08-01 23:32:20 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type CreateUserRequestBody struct {
|
|
|
|
Username string `json:"username" binding:"required"`
|
|
|
|
Password string `json:"password" binding:"required"`
|
|
|
|
Email string `json:"email" binding:"required"`
|
|
|
|
Admin bool `json:"admin"`
|
|
|
|
NeedsPasswordReset bool `json:"needs_password_reset"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func CreateUser(c *gin.Context) {
|
|
|
|
var body CreateUserRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing CreateUser post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
if !vc.IsAdmin() {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-02 22:39:20 +00:00
|
|
|
user, err := NewUser(db, body.Username, body.Password, body.Email, body.Admin, body.NeedsPasswordReset)
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error creating new user: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
|
|
|
})
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ModifyUserRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
2016-08-01 23:32:20 +00:00
|
|
|
Username string `json:"username"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func ModifyUser(c *gin.Context) {
|
|
|
|
var body ModifyUserRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing ModifyUser post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
|
|
|
if !vc.CanPerformActions() {
|
|
|
|
UnauthorizedError(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var user User
|
2016-08-02 22:39:20 +00:00
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-02 22:39:20 +00:00
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
if !vc.CanPerformWriteActionOnUser(&user) {
|
2016-08-01 23:32:20 +00:00
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
if body.Name != "" {
|
|
|
|
user.Name = body.Name
|
|
|
|
}
|
|
|
|
if body.Email != "" {
|
|
|
|
user.Email = body.Email
|
|
|
|
}
|
2016-08-01 23:32:20 +00:00
|
|
|
err = db.Save(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error updating user in database: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
|
|
|
})
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DeleteUserRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteUser(c *gin.Context) {
|
|
|
|
var body DeleteUserRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing DeleteUser post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
if !vc.IsAdmin() {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
var user User
|
2016-08-02 22:39:20 +00:00
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = db.Delete(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error deleting user from database: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.JSON(200, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
type ResetPasswordRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
2016-08-01 23:32:20 +00:00
|
|
|
Password string `json:"password" binding:"required"`
|
|
|
|
PasswordConfim string `json:"password_confirm" binding:"required"`
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
type ChangePasswordRequestBody struct {
|
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
CurrentPassword string `json:"current_password"`
|
|
|
|
NewPassword string `json:"new_password" binding:"required"`
|
|
|
|
NewPasswordConfim string `json:"new_password_confirm" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func ChangeUserPassword(c *gin.Context) {
|
|
|
|
var body ChangePasswordRequestBody
|
2016-08-01 23:32:20 +00:00
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing ResetPassword post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
if body.NewPassword != body.NewPasswordConfim {
|
2016-08-01 23:32:20 +00:00
|
|
|
c.JSON(406, map[string]interface{}{"error": "Passwords do not match"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
|
|
|
if !vc.CanPerformActions() {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-01 23:32:20 +00:00
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-02 22:39:20 +00:00
|
|
|
var user User
|
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-02 22:39:20 +00:00
|
|
|
|
|
|
|
if !vc.IsAdmin() {
|
|
|
|
if !vc.IsUserID(user.ID) {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if user.ValidatePassword(body.CurrentPassword) != nil {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 22:39:20 +00:00
|
|
|
err = user.SetPassword(db, body.NewPassword)
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error setting user password: %s", err.Error())
|
2016-08-04 22:38:13 +00:00
|
|
|
DatabaseError(c) // probably not this
|
|
|
|
return
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = db.Save(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error updating user in database: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
|
|
|
})
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type SetUserAdminStateRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Admin bool `json:"admin"`
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func SetUserAdminState(c *gin.Context) {
|
|
|
|
var body SetUserAdminStateRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing SetUserAdminState post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
if !vc.IsAdmin() {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
var user User
|
2016-08-02 22:39:20 +00:00
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user.Admin = body.Admin
|
|
|
|
err = db.Save(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error updating user in database: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
|
|
|
})
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type SetUserEnabledStateRequestBody struct {
|
2016-08-02 22:39:20 +00:00
|
|
|
ID uint `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Enabled bool `json:"enabled"`
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func SetUserEnabledState(c *gin.Context) {
|
|
|
|
var body SetUserEnabledStateRequestBody
|
|
|
|
err := c.BindJSON(&body)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error parsing SetUserEnabledState post body: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
vc := VC(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
if !vc.IsAdmin() {
|
|
|
|
UnauthorizedError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:38:13 +00:00
|
|
|
db := GetDB(c)
|
2016-08-01 23:32:20 +00:00
|
|
|
var user User
|
2016-08-02 22:39:20 +00:00
|
|
|
user.ID = body.ID
|
|
|
|
user.Username = body.Username
|
|
|
|
err = db.Where(&user).First(&user).Error
|
2016-08-01 23:32:20 +00:00
|
|
|
if err != nil {
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user.Enabled = body.Enabled
|
|
|
|
err = db.Save(&user).Error
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Error updating user in database: %s", err.Error())
|
|
|
|
DatabaseError(c)
|
|
|
|
return
|
|
|
|
}
|
2016-08-02 22:39:20 +00:00
|
|
|
c.JSON(200, GetUserResponseBody{
|
|
|
|
ID: user.ID,
|
|
|
|
Username: user.Username,
|
|
|
|
Name: user.Name,
|
|
|
|
Email: user.Email,
|
|
|
|
Admin: user.Admin,
|
|
|
|
Enabled: user.Enabled,
|
|
|
|
NeedsPasswordReset: user.NeedsPasswordReset,
|
|
|
|
})
|
2016-08-01 23:32:20 +00:00
|
|
|
}
|