2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
2021-09-14 12:11:07 +00:00
|
|
|
"context"
|
2016-12-20 21:31:09 +00:00
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
2021-04-05 18:15:26 +00:00
|
|
|
"strings"
|
2016-12-20 21:31:09 +00:00
|
|
|
|
2021-11-11 20:33:06 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2021-04-05 18:15:26 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
2021-04-06 18:09:28 +00:00
|
|
|
var inviteSearchColumns = []string{"name", "email"}
|
|
|
|
|
2020-10-22 17:51:26 +00:00
|
|
|
// NewInvite generates a new invitation.
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) NewInvite(ctx context.Context, i *fleet.Invite) (*fleet.Invite, error) {
|
2021-07-13 19:33:04 +00:00
|
|
|
if err := fleet.ValidateRole(i.GlobalRole.Ptr(), i.Teams); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2021-09-08 18:43:22 +00:00
|
|
|
sqlStmt := `
|
2021-04-07 01:27:10 +00:00
|
|
|
INSERT INTO invites ( invited_by, email, name, position, token, sso_enabled, global_role )
|
|
|
|
VALUES ( ?, ?, ?, ?, ?, ?, ?)
|
2020-10-22 17:51:26 +00:00
|
|
|
`
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-14 14:44:02 +00:00
|
|
|
result, err := tx.ExecContext(ctx, sqlStmt, i.InvitedBy, i.Email,
|
2021-09-08 18:43:22 +00:00
|
|
|
i.Name, i.Position, i.Token, i.SSOEnabled, i.GlobalRole)
|
|
|
|
if err != nil && isDuplicate(err) {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, alreadyExists("Invite", i.Email))
|
2021-09-08 18:43:22 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "create invite")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
id, _ := result.LastInsertId()
|
|
|
|
i.ID = uint(id)
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
if len(i.Teams) == 0 {
|
|
|
|
i.Teams = []fleet.UserTeam{}
|
|
|
|
return nil
|
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
2021-09-08 18:43:22 +00:00
|
|
|
// Bulk insert teams
|
|
|
|
const valueStr = "(?,?,?),"
|
|
|
|
var args []interface{}
|
|
|
|
for _, userTeam := range i.Teams {
|
|
|
|
args = append(args, i.ID, userTeam.Team.ID, userTeam.Role)
|
|
|
|
}
|
|
|
|
sql := "INSERT INTO invite_teams (invite_id, team_id, role) VALUES " +
|
|
|
|
strings.Repeat(valueStr, len(i.Teams))
|
|
|
|
sql = strings.TrimSuffix(sql, ",")
|
2021-09-14 14:44:02 +00:00
|
|
|
if _, err := tx.ExecContext(ctx, sql, args...); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "insert teams")
|
2021-09-08 18:43:22 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
|
2019-01-24 17:39:32 +00:00
|
|
|
// ListInvites lists all invites in the Fleet database. Supply query options
|
2021-06-06 22:07:29 +00:00
|
|
|
// using the opt parameter. See fleet.ListOptions
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) ListInvites(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Invite, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
invites := []*fleet.Invite{}
|
2021-04-06 18:09:28 +00:00
|
|
|
query := "SELECT * FROM invites WHERE true"
|
|
|
|
query, params := searchLike(query, nil, opt.MatchQuery, inviteSearchColumns...)
|
2023-11-09 19:18:29 +00:00
|
|
|
query, params = appendListOptionsWithCursorToSQL(query, params, &opt)
|
2021-04-06 18:09:28 +00:00
|
|
|
|
2023-06-19 17:55:15 +00:00
|
|
|
err := sqlx.SelectContext(ctx, ds.reader(ctx), &invites, query, params...)
|
2016-12-30 01:58:12 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, notFound("Invite"))
|
2016-12-20 21:31:09 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "select invite by ID")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
if err := ds.loadTeamsForInvites(ctx, invites); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "load teams")
|
2021-04-05 18:15:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
return invites, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invite returns Invite identified by id.
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) Invite(ctx context.Context, id uint) (*fleet.Invite, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
var invite fleet.Invite
|
2023-06-19 17:55:15 +00:00
|
|
|
err := sqlx.GetContext(ctx, ds.reader(ctx), &invite, "SELECT * FROM invites WHERE id = ?", id)
|
2016-12-30 01:58:12 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, notFound("Invite").WithID(id))
|
2016-12-20 21:31:09 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "select invite by ID")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
if err := ds.loadTeamsForInvites(ctx, []*fleet.Invite{&invite}); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "load teams")
|
2021-04-05 18:15:26 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 01:58:12 +00:00
|
|
|
return &invite, nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InviteByEmail finds an Invite with a particular email, if one exists.
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) InviteByEmail(ctx context.Context, email string) (*fleet.Invite, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
var invite fleet.Invite
|
2023-06-19 17:55:15 +00:00
|
|
|
err := sqlx.GetContext(ctx, ds.reader(ctx), &invite, "SELECT * FROM invites WHERE email = ?", email)
|
2016-12-30 01:58:12 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, notFound("Invite").
|
|
|
|
WithMessage(fmt.Sprintf("with email %s", email)))
|
2016-12-20 21:31:09 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "sqlx get invite by email")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
if err := ds.loadTeamsForInvites(ctx, []*fleet.Invite{&invite}); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "load teams")
|
2021-04-05 18:15:26 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 01:58:12 +00:00
|
|
|
return &invite, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// InviteByToken finds an Invite with a particular token, if one exists.
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) InviteByToken(ctx context.Context, token string) (*fleet.Invite, error) {
|
2021-06-06 22:07:29 +00:00
|
|
|
var invite fleet.Invite
|
2023-06-19 17:55:15 +00:00
|
|
|
err := sqlx.GetContext(ctx, ds.reader(ctx), &invite, "SELECT * FROM invites WHERE token = ?", token)
|
2016-12-30 01:58:12 +00:00
|
|
|
if err == sql.ErrNoRows {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, notFound("Invite").
|
|
|
|
WithMessage(fmt.Sprintf("with token %s", token)))
|
2016-12-30 01:58:12 +00:00
|
|
|
} else if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "sqlx get invite by token")
|
2016-12-30 01:58:12 +00:00
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
if err := ds.loadTeamsForInvites(ctx, []*fleet.Invite{&invite}); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return nil, ctxerr.Wrap(ctx, err, "load teams")
|
2021-04-05 18:15:26 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 01:58:12 +00:00
|
|
|
return &invite, nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) DeleteInvite(ctx context.Context, id uint) error {
|
|
|
|
return ds.deleteEntity(ctx, invitesTable, id)
|
2021-04-05 18:15:26 +00:00
|
|
|
}
|
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) loadTeamsForInvites(ctx context.Context, invites []*fleet.Invite) error {
|
2021-04-05 18:15:26 +00:00
|
|
|
inviteIDs := make([]uint, 0, len(invites)+1)
|
|
|
|
// Make sure the slice is never empty for IN by filling a nonexistent ID
|
|
|
|
inviteIDs = append(inviteIDs, 0)
|
2021-06-06 22:07:29 +00:00
|
|
|
idToInvite := make(map[uint]*fleet.Invite, len(invites))
|
2021-04-05 18:15:26 +00:00
|
|
|
for _, u := range invites {
|
|
|
|
// Initialize empty slice so we get an array in JSON responses instead
|
|
|
|
// of null if it is empty
|
2021-06-06 22:07:29 +00:00
|
|
|
u.Teams = []fleet.UserTeam{}
|
2021-04-05 18:15:26 +00:00
|
|
|
// Track IDs for queries and matching
|
|
|
|
inviteIDs = append(inviteIDs, u.ID)
|
|
|
|
idToInvite[u.ID] = u
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
sql := `
|
2021-04-05 18:15:26 +00:00
|
|
|
SELECT ut.team_id AS id, ut.invite_id, ut.role, t.name
|
|
|
|
FROM invite_teams ut INNER JOIN teams t ON ut.team_id = t.id
|
|
|
|
WHERE ut.invite_id IN (?)
|
|
|
|
ORDER BY invite_id, team_id
|
2016-11-16 13:47:49 +00:00
|
|
|
`
|
2021-04-05 18:15:26 +00:00
|
|
|
sql, args, err := sqlx.In(sql, inviteIDs)
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "sqlx.In loadTeamsForInvites")
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2021-04-05 18:15:26 +00:00
|
|
|
|
|
|
|
var rows []struct {
|
2021-06-06 22:07:29 +00:00
|
|
|
fleet.UserTeam
|
2021-04-05 18:15:26 +00:00
|
|
|
InviteID uint `db:"invite_id"`
|
2017-03-30 22:03:48 +00:00
|
|
|
}
|
2023-06-19 17:55:15 +00:00
|
|
|
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &rows, sql, args...); err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
return ctxerr.Wrap(ctx, err, "get loadTeamsForInvites")
|
2017-03-30 22:03:48 +00:00
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-04-05 18:15:26 +00:00
|
|
|
// Map each row to the appropriate invite
|
|
|
|
for _, r := range rows {
|
|
|
|
invite := idToInvite[r.InviteID]
|
|
|
|
invite.Teams = append(invite.Teams, r.UserTeam)
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2021-04-05 18:15:26 +00:00
|
|
|
return nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
2021-11-11 20:33:06 +00:00
|
|
|
|
2022-02-03 17:56:22 +00:00
|
|
|
func (ds *Datastore) UpdateInvite(ctx context.Context, id uint, i *fleet.Invite) (*fleet.Invite, error) {
|
|
|
|
return i, ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
2021-11-11 20:33:06 +00:00
|
|
|
_, err := tx.ExecContext(ctx,
|
|
|
|
`UPDATE invites SET invited_by = ?, email = ?, name = ?, position = ?, sso_enabled = ?, global_role = ? WHERE id = ?`,
|
|
|
|
i.InvitedBy, i.Email, i.Name, i.Position, i.SSOEnabled, i.GlobalRole, id,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "updating invite")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = tx.ExecContext(ctx, `DELETE FROM invite_teams WHERE invite_id = ?`, id)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "deleting invite teams")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, team := range i.Teams {
|
|
|
|
_, err = tx.ExecContext(ctx, `INSERT INTO invite_teams(invite_id, team_id, role) VALUES(?, ?, ?)`, id, team.ID, team.Role)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "updating invite teams")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|