2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
2016-12-20 22:09:49 +00:00
|
|
|
"database/sql"
|
2016-11-16 13:47:49 +00:00
|
|
|
"fmt"
|
2021-03-18 04:59:00 +00:00
|
|
|
"strings"
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2020-11-11 17:59:12 +00:00
|
|
|
"github.com/fleetdm/fleet/server/kolide"
|
2021-03-18 04:59:00 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2016-12-20 22:09:49 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
2021-03-18 00:24:34 +00:00
|
|
|
var userSearchColumns = []string{"name", "email"}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
// NewUser creates a new user
|
|
|
|
func (d *Datastore) NewUser(user *kolide.User) (*kolide.User, error) {
|
|
|
|
sqlStatement := `
|
2017-05-10 16:26:05 +00:00
|
|
|
INSERT INTO users (
|
|
|
|
password,
|
|
|
|
salt,
|
|
|
|
name,
|
|
|
|
username,
|
|
|
|
email,
|
|
|
|
admin,
|
|
|
|
enabled,
|
|
|
|
admin_forced_password_reset,
|
|
|
|
gravatar_url,
|
|
|
|
position,
|
|
|
|
sso_enabled
|
|
|
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
|
|
|
`
|
2016-11-16 13:47:49 +00:00
|
|
|
result, err := d.db.Exec(sqlStatement, user.Password, user.Salt, user.Name,
|
|
|
|
user.Username, user.Email, user.Admin, user.Enabled,
|
2017-05-10 16:26:05 +00:00
|
|
|
user.AdminForcedPasswordReset, user.GravatarURL, user.Position, user.SSOEnabled)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2016-12-20 22:09:49 +00:00
|
|
|
return nil, errors.Wrap(err, "create new user")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
user.ID = uint(id)
|
2021-03-18 04:59:00 +00:00
|
|
|
|
|
|
|
if err := d.saveTeamsForUser(user); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) findUser(searchCol string, searchVal interface{}) (*kolide.User, error) {
|
|
|
|
sqlStatement := fmt.Sprintf(
|
|
|
|
"SELECT * FROM users "+
|
2020-10-22 17:51:26 +00:00
|
|
|
"WHERE %s = ? LIMIT 1",
|
2016-11-16 13:47:49 +00:00
|
|
|
searchCol,
|
|
|
|
)
|
|
|
|
|
|
|
|
user := &kolide.User{}
|
|
|
|
|
2016-12-20 22:09:49 +00:00
|
|
|
err := d.db.Get(user, sqlStatement, searchVal)
|
|
|
|
if err != nil && err == sql.ErrNoRows {
|
|
|
|
return nil, notFound("User").
|
|
|
|
WithMessage(fmt.Sprintf("with %s=%v", searchCol, searchVal))
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "find user")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 04:59:00 +00:00
|
|
|
if err := d.loadTeamsForUsers([]*kolide.User{user}); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "load teams")
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// User retrieves a user by name
|
|
|
|
func (d *Datastore) User(username string) (*kolide.User, error) {
|
|
|
|
return d.findUser("username", username)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListUsers lists all users with limit, sort and offset passed in with
|
|
|
|
// kolide.ListOptions
|
|
|
|
func (d *Datastore) ListUsers(opt kolide.ListOptions) ([]*kolide.User, error) {
|
|
|
|
sqlStatement := `
|
2020-10-22 17:51:26 +00:00
|
|
|
SELECT * FROM users
|
2021-03-18 00:24:34 +00:00
|
|
|
WHERE TRUE
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
2021-03-18 00:24:34 +00:00
|
|
|
|
|
|
|
sqlStatement, params := searchLike(sqlStatement, nil, opt.MatchQuery, userSearchColumns...)
|
2016-11-16 13:47:49 +00:00
|
|
|
sqlStatement = appendListOptionsToSQL(sqlStatement, opt)
|
|
|
|
users := []*kolide.User{}
|
|
|
|
|
2021-03-18 00:24:34 +00:00
|
|
|
if err := d.db.Select(&users, sqlStatement, params...); err != nil {
|
2016-12-20 22:09:49 +00:00
|
|
|
return nil, errors.Wrap(err, "list users")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 04:59:00 +00:00
|
|
|
if err := d.loadTeamsForUsers(users); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "load teams")
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return users, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) UserByEmail(email string) (*kolide.User, error) {
|
|
|
|
return d.findUser("email", email)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) UserByID(id uint) (*kolide.User, error) {
|
|
|
|
return d.findUser("id", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) SaveUser(user *kolide.User) error {
|
|
|
|
sqlStatement := `
|
2017-05-10 16:26:05 +00:00
|
|
|
UPDATE users SET
|
|
|
|
username = ?,
|
|
|
|
password = ?,
|
|
|
|
salt = ?,
|
|
|
|
name = ?,
|
|
|
|
email = ?,
|
|
|
|
admin = ?,
|
|
|
|
enabled = ?,
|
|
|
|
admin_forced_password_reset = ?,
|
|
|
|
gravatar_url = ?,
|
|
|
|
position = ?,
|
|
|
|
sso_enabled = ?
|
|
|
|
WHERE id = ?
|
|
|
|
`
|
2017-03-30 22:03:48 +00:00
|
|
|
result, err := d.db.Exec(sqlStatement, user.Username, user.Password,
|
2016-11-16 13:47:49 +00:00
|
|
|
user.Salt, user.Name, user.Email, user.Admin, user.Enabled,
|
2017-05-10 16:26:05 +00:00
|
|
|
user.AdminForcedPasswordReset, user.GravatarURL, user.Position, user.SSOEnabled, user.ID)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2016-12-20 22:09:49 +00:00
|
|
|
return errors.Wrap(err, "save user")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2017-03-30 22:03:48 +00:00
|
|
|
rows, err := result.RowsAffected()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "rows affected save user")
|
|
|
|
}
|
|
|
|
if rows == 0 {
|
|
|
|
return notFound("User").WithID(user.ID)
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-03-18 04:59:00 +00:00
|
|
|
// REVIEW: Check if teams have been set?
|
|
|
|
if err := d.saveTeamsForUser(user); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// loadTeamsForUsers will load the teams/roles for the provided users.
|
|
|
|
func (d *Datastore) loadTeamsForUsers(users []*kolide.User) error {
|
|
|
|
userIDs := make([]uint, 0, len(users)+1)
|
|
|
|
// Make sure the slice is never empty for IN by filling a nonexistent ID
|
|
|
|
userIDs = append(userIDs, 0)
|
|
|
|
idToUser := make(map[uint]*kolide.User, len(users))
|
|
|
|
for _, u := range users {
|
|
|
|
// Initialize empty slice so we get an array in JSON responses instead
|
|
|
|
// of null if it is empty
|
|
|
|
u.Teams = []kolide.UserTeam{}
|
|
|
|
// Track IDs for queries and matching
|
|
|
|
userIDs = append(userIDs, u.ID)
|
|
|
|
idToUser[u.ID] = u
|
|
|
|
}
|
|
|
|
|
|
|
|
sql := `
|
|
|
|
SELECT ut.team_id AS id, ut.user_id, ut.role, t.name
|
|
|
|
FROM user_teams ut INNER JOIN teams t ON ut.team_id = t.id
|
|
|
|
WHERE ut.user_id IN (?)
|
|
|
|
ORDER BY user_id, team_id
|
|
|
|
`
|
|
|
|
sql, args, err := sqlx.In(sql, userIDs)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "sqlx.In loadTeamsForUsers")
|
|
|
|
}
|
|
|
|
|
|
|
|
var rows []struct {
|
|
|
|
kolide.UserTeam
|
|
|
|
UserID uint `db:"user_id"`
|
|
|
|
}
|
|
|
|
if err := d.db.Select(&rows, sql, args...); err != nil {
|
|
|
|
return errors.Wrap(err, "get loadTeamsForUsers")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map each row to the appropriate user
|
|
|
|
for _, r := range rows {
|
|
|
|
user := idToUser[r.UserID]
|
|
|
|
user.Teams = append(user.Teams, r.UserTeam)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) saveTeamsForUser(user *kolide.User) error {
|
|
|
|
// Do a full teams update by deleting existing teams and then inserting all
|
|
|
|
// the current teams in a single transaction.
|
|
|
|
if err := d.withRetryTxx(func(tx *sqlx.Tx) error {
|
|
|
|
// Delete before insert
|
|
|
|
sql := `DELETE FROM user_teams WHERE user_id = ?`
|
|
|
|
if _, err := tx.Exec(sql, user.ID); err != nil {
|
|
|
|
return errors.Wrap(err, "delete existing teams")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(user.Teams) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bulk insert
|
|
|
|
const valueStr = "(?,?,?),"
|
|
|
|
var args []interface{}
|
|
|
|
for _, userTeam := range user.Teams {
|
|
|
|
args = append(args, user.ID, userTeam.Team.ID, userTeam.Role)
|
|
|
|
}
|
|
|
|
sql = "INSERT INTO user_teams (user_id, team_id, role) VALUES " +
|
|
|
|
strings.Repeat(valueStr, len(user.Teams))
|
|
|
|
sql = strings.TrimSuffix(sql, ",")
|
|
|
|
if _, err := tx.Exec(sql, args...); err != nil {
|
|
|
|
return errors.Wrap(err, "insert teams")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return errors.Wrap(err, "save teams for user")
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
return nil
|
|
|
|
}
|