mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Add team user management (#672)
- Add list team users endpoint. - Add add/delete team users endpoints. - Update list users to support filter by team.
This commit is contained in:
parent
af802dc15f
commit
e8f4860d51
19 changed files with 422 additions and 29 deletions
|
|
@ -98,6 +98,7 @@ var TestFunctions = [...]func(*testing.T, kolide.Datastore){
|
|||
testCarveCleanupCarves,
|
||||
testCarveUpdateCarve,
|
||||
testTeamGetSetDelete,
|
||||
testTeamUsers,
|
||||
testUserTeams,
|
||||
testUserCreateWithTeams,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,3 +43,61 @@ func testTeamGetSetDelete(t *testing.T, ds kolide.Datastore) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testTeamUsers(t *testing.T, ds kolide.Datastore) {
|
||||
users := createTestUsers(t, ds)
|
||||
user1 := kolide.User{Name: users[0].Name, Email: users[0].Email, ID: users[0].ID}
|
||||
user2 := kolide.User{Name: users[1].Name, Email: users[1].Email, ID: users[1].ID}
|
||||
|
||||
team1, err := ds.NewTeam(&kolide.Team{Name: "team1"})
|
||||
require.NoError(t, err)
|
||||
team2, err := ds.NewTeam(&kolide.Team{Name: "team2"})
|
||||
require.NoError(t, err)
|
||||
_ = team2
|
||||
|
||||
team1, err = ds.Team(team1.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, team1.Users, 0)
|
||||
|
||||
team1Users := []kolide.TeamUser{
|
||||
{User: user1, Role: "maintainer"},
|
||||
{User: user2, Role: "observer"},
|
||||
}
|
||||
team1.Users = team1Users
|
||||
team1, err = ds.SaveTeam(team1)
|
||||
require.NoError(t, err)
|
||||
|
||||
team1, err = ds.Team(team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, team1Users, team1.Users)
|
||||
// Ensure team 2 not effected
|
||||
team2, err = ds.Team(team2.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, team2.Users, 0)
|
||||
|
||||
team1Users = []kolide.TeamUser{
|
||||
{User: user2, Role: "maintainer"},
|
||||
}
|
||||
team1.Users = team1Users
|
||||
team1, err = ds.SaveTeam(team1)
|
||||
require.NoError(t, err)
|
||||
team1, err = ds.Team(team1.ID)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, team1Users, team1.Users)
|
||||
|
||||
team2Users := []kolide.TeamUser{
|
||||
{User: user2, Role: "observer"},
|
||||
}
|
||||
team2.Users = team2Users
|
||||
team1, err = ds.SaveTeam(team1)
|
||||
require.NoError(t, err)
|
||||
team1, err = ds.Team(team1.ID)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, team1Users, team1.Users)
|
||||
team2, err = ds.SaveTeam(team2)
|
||||
require.NoError(t, err)
|
||||
team2, err = ds.Team(team2.ID)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, team2Users, team2.Users)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,16 +130,16 @@ func testUserGlobalRole(t *testing.T, ds kolide.Datastore, users []*kolide.User)
|
|||
func testListUsers(t *testing.T, ds kolide.Datastore) {
|
||||
createTestUsers(t, ds)
|
||||
|
||||
users, err := ds.ListUsers(kolide.ListOptions{})
|
||||
users, err := ds.ListUsers(kolide.UserListOptions{})
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, users, 2)
|
||||
|
||||
users, err = ds.ListUsers(kolide.ListOptions{MatchQuery: "jason"})
|
||||
users, err = ds.ListUsers(kolide.UserListOptions{ListOptions: kolide.ListOptions{MatchQuery: "jason"}})
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, "jason@kolide.co", users[0].Email)
|
||||
|
||||
users, err = ds.ListUsers(kolide.ListOptions{MatchQuery: "paia"})
|
||||
users, err = ds.ListUsers(kolide.UserListOptions{ListOptions: kolide.ListOptions{MatchQuery: "paia"}})
|
||||
assert.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, "mike@kolide.co", users[0].Email)
|
||||
|
|
@ -176,7 +176,11 @@ func testUserTeams(t *testing.T, ds kolide.Datastore) {
|
|||
err = ds.SaveUser(users[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err = ds.ListUsers(kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending})
|
||||
users, err = ds.ListUsers(
|
||||
kolide.UserListOptions{
|
||||
ListOptions: kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, users[0].Teams, 1)
|
||||
|
|
@ -199,7 +203,11 @@ func testUserTeams(t *testing.T, ds kolide.Datastore) {
|
|||
err = ds.SaveUser(users[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err = ds.ListUsers(kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending})
|
||||
users, err = ds.ListUsers(
|
||||
kolide.UserListOptions{
|
||||
ListOptions: kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, users[0].Teams, 1)
|
||||
|
|
@ -210,7 +218,11 @@ func testUserTeams(t *testing.T, ds kolide.Datastore) {
|
|||
err = ds.SaveUser(users[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err = ds.ListUsers(kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending})
|
||||
users, err = ds.ListUsers(
|
||||
kolide.UserListOptions{
|
||||
ListOptions: kolide.ListOptions{OrderKey: "name", OrderDirection: kolide.OrderDescending},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, users[0].Teams, 1)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (d *Datastore) User(username string) (*kolide.User, error) {
|
|||
WithMessage(fmt.Sprintf("with username %s", username))
|
||||
}
|
||||
|
||||
func (d *Datastore) ListUsers(opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
func (d *Datastore) ListUsers(opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
d.mtx.Lock()
|
||||
defer d.mtx.Unlock()
|
||||
|
||||
|
|
@ -66,13 +66,13 @@ func (d *Datastore) ListUsers(opt kolide.ListOptions) ([]*kolide.User, error) {
|
|||
"enabled": "Enabled",
|
||||
"position": "Position",
|
||||
}
|
||||
if err := sortResults(users, opt, fields); err != nil {
|
||||
if err := sortResults(users, opt.ListOptions, fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Apply limit/offset
|
||||
low, high := d.getLimitOffsetSliceBounds(opt, len(users))
|
||||
low, high := d.getLimitOffsetSliceBounds(opt.ListOptions, len(users))
|
||||
users = users[low:high]
|
||||
|
||||
return users, nil
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package mysql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -39,6 +42,10 @@ func (d *Datastore) Team(tid uint) (*kolide.Team, error) {
|
|||
return nil, errors.Wrap(err, "select team")
|
||||
}
|
||||
|
||||
if err := d.loadUsersForTeam(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
|
|
@ -60,9 +67,63 @@ func (d *Datastore) TeamByName(name string) (*kolide.Team, error) {
|
|||
return nil, errors.Wrap(err, "select team")
|
||||
}
|
||||
|
||||
if err := d.loadUsersForTeam(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) loadUsersForTeam(team *kolide.Team) error {
|
||||
sql := `
|
||||
SELECT u.name, u.id, u.email, ut.role
|
||||
FROM user_teams ut JOIN users u ON (ut.user_id = u.id)
|
||||
WHERE ut.team_id = ?
|
||||
`
|
||||
rows := []kolide.TeamUser{}
|
||||
if err := d.db.Select(&rows, sql, team.ID); err != nil {
|
||||
return errors.Wrap(err, "load users for team")
|
||||
}
|
||||
|
||||
team.Users = rows
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) saveUsersForTeam(team *kolide.Team) error {
|
||||
// Do a full user update by deleting existing users and then inserting all
|
||||
// the current users in a single transaction.
|
||||
if err := d.withRetryTxx(func(tx *sqlx.Tx) error {
|
||||
// Delete before insert
|
||||
sql := `DELETE FROM user_teams WHERE team_id = ?`
|
||||
if _, err := tx.Exec(sql, team.ID); err != nil {
|
||||
return errors.Wrap(err, "delete existing users")
|
||||
}
|
||||
|
||||
if len(team.Users) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bulk insert
|
||||
const valueStr = "(?,?,?),"
|
||||
var args []interface{}
|
||||
for _, teamUser := range team.Users {
|
||||
args = append(args, teamUser.User.ID, team.ID, teamUser.Role)
|
||||
}
|
||||
sql = "INSERT INTO user_teams (user_id, team_id, role) VALUES " +
|
||||
strings.Repeat(valueStr, len(team.Users))
|
||||
sql = strings.TrimSuffix(sql, ",")
|
||||
if _, err := tx.Exec(sql, args...); err != nil {
|
||||
return errors.Wrap(err, "insert users")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "save users for team")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) SaveTeam(team *kolide.Team) (*kolide.Team, error) {
|
||||
query := `
|
||||
UPDATE teams SET
|
||||
|
|
@ -74,6 +135,11 @@ func (d *Datastore) SaveTeam(team *kolide.Team) (*kolide.Team, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Wrap(err, "saving team")
|
||||
}
|
||||
|
||||
if err := d.saveUsersForTeam(team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return team, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,16 +75,21 @@ 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) {
|
||||
// ListUsers lists all users with team ID, limit, sort and offset passed in with
|
||||
// UserListOptions.
|
||||
func (d *Datastore) ListUsers(opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
sqlStatement := `
|
||||
SELECT * FROM users
|
||||
WHERE TRUE
|
||||
`
|
||||
var params []interface{}
|
||||
if opt.TeamID != 0 {
|
||||
sqlStatement += " AND id IN (SELECT user_id FROM user_teams WHERE team_id = ?)"
|
||||
params = append(params, opt.TeamID)
|
||||
}
|
||||
|
||||
sqlStatement, params := searchLike(sqlStatement, nil, opt.MatchQuery, userSearchColumns...)
|
||||
sqlStatement = appendListOptionsToSQL(sqlStatement, opt)
|
||||
sqlStatement, params = searchLike(sqlStatement, params, opt.MatchQuery, userSearchColumns...)
|
||||
sqlStatement = appendListOptionsToSQL(sqlStatement, opt.ListOptions)
|
||||
users := []*kolide.User{}
|
||||
|
||||
if err := d.db.Select(&users, sqlStatement, params...); err != nil {
|
||||
|
|
@ -214,6 +219,7 @@ func (d *Datastore) saveTeamsForUser(user *kolide.User) error {
|
|||
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")
|
||||
|
|
|
|||
|
|
@ -26,11 +26,17 @@ type TeamService interface {
|
|||
NewTeam(ctx context.Context, p TeamPayload) (*Team, error)
|
||||
// ModifyTeam modifies an existing team.
|
||||
ModifyTeam(ctx context.Context, id uint, payload TeamPayload) (*Team, error)
|
||||
// AddTeamUsers adds users to an existing team.
|
||||
AddTeamUsers(ctx context.Context, teamID uint, users []TeamUser) (*Team, error)
|
||||
// DeleteTeamUsers deletes users from an existing team.
|
||||
DeleteTeamUsers(ctx context.Context, teamID uint, users []TeamUser) (*Team, error)
|
||||
// DeleteTeam deletes an existing team.
|
||||
DeleteTeam(ctx context.Context, id uint) error
|
||||
// ListTeams lists teams with the ordering and filters in the provided
|
||||
// options.
|
||||
ListTeams(ctx context.Context, opt ListOptions) ([]*Team, error)
|
||||
// ListTeams lists users on the team with the provided list options.
|
||||
ListTeamUsers(ctx context.Context, teamID uint, opt ListOptions) ([]*User, error)
|
||||
}
|
||||
|
||||
type TeamPayload struct {
|
||||
|
|
@ -64,9 +70,49 @@ type Team struct {
|
|||
Hosts []Host `json:"hosts,omitempty"`
|
||||
}
|
||||
|
||||
// TeamUser is a user mapped to a team with a role.
|
||||
type TeamUser struct {
|
||||
// User is the user object
|
||||
// User is the user object. At least ID must be specified for most uses.
|
||||
User
|
||||
// Role is the role the user has for the team.
|
||||
Role string `json:"role" db:"role"`
|
||||
}
|
||||
|
||||
var teamRoles = map[string]bool{
|
||||
"observer": true,
|
||||
"maintainer": true,
|
||||
}
|
||||
|
||||
// ValidTeamRole returns whether the role provided is valid for a team user.
|
||||
func ValidTeamRole(role string) bool {
|
||||
return teamRoles[role]
|
||||
}
|
||||
|
||||
// ValidTeamRoles returns the list of valid roles for a team user.
|
||||
func ValidTeamRoles() []string {
|
||||
var roles []string
|
||||
for role, _ := range teamRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
var globalRoles = map[string]bool{
|
||||
"observer": true,
|
||||
"maintainer": true,
|
||||
"admin": true,
|
||||
}
|
||||
|
||||
// ValidGlobalRole returns whether the role provided is valid for a global user.
|
||||
func ValidGlobalRole(role string) bool {
|
||||
return globalRoles[role]
|
||||
}
|
||||
|
||||
// ValidGlobalRoles returns the list of valid roles for a global user.
|
||||
func ValidGlobalRoles() []string {
|
||||
var roles []string
|
||||
for role, _ := range globalRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
type UserStore interface {
|
||||
NewUser(user *User) (*User, error)
|
||||
User(username string) (*User, error)
|
||||
ListUsers(opt ListOptions) ([]*User, error)
|
||||
ListUsers(opt UserListOptions) ([]*User, error)
|
||||
UserByEmail(email string) (*User, error)
|
||||
UserByID(id uint) (*User, error)
|
||||
SaveUser(user *User) error
|
||||
|
|
@ -47,7 +47,7 @@ type UserService interface {
|
|||
AuthenticatedUser(ctx context.Context) (user *User, err error)
|
||||
|
||||
// ListUsers returns all users.
|
||||
ListUsers(ctx context.Context, opt ListOptions) (users []*User, err error)
|
||||
ListUsers(ctx context.Context, opt UserListOptions) (users []*User, err error)
|
||||
|
||||
// ChangePassword validates the existing password, and sets the new
|
||||
// password. User is retrieved from the viewer context.
|
||||
|
|
@ -115,6 +115,14 @@ type UserTeam struct {
|
|||
Role string `json:"role" db:"role"`
|
||||
}
|
||||
|
||||
// UserListOptions is additional options that can be set for listing users.
|
||||
type UserListOptions struct {
|
||||
ListOptions
|
||||
|
||||
// TeamID, if set, indicates to only return members of the identified team.
|
||||
TeamID uint
|
||||
}
|
||||
|
||||
// UserPayload is used to modify an existing user
|
||||
type UserPayload struct {
|
||||
Username *string `json:"username,omitempty"`
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ type NewUserFunc func(user *kolide.User) (*kolide.User, error)
|
|||
|
||||
type UserFunc func(username string) (*kolide.User, error)
|
||||
|
||||
type ListUsersFunc func(opt kolide.ListOptions) ([]*kolide.User, error)
|
||||
type ListUsersFunc func(opt kolide.UserListOptions) ([]*kolide.User, error)
|
||||
|
||||
type UserByEmailFunc func(email string) (*kolide.User, error)
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ func (s *UserStore) User(username string) (*kolide.User, error) {
|
|||
return s.UserFunc(username)
|
||||
}
|
||||
|
||||
func (s *UserStore) ListUsers(opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
func (s *UserStore) ListUsers(opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
s.ListUsersFuncInvoked = true
|
||||
return s.ListUsersFunc(opt)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,3 +118,63 @@ func makeDeleteTeamEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
return deleteTeamResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// List Team Users
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listTeamUsersRequest struct {
|
||||
TeamID uint
|
||||
ListOptions kolide.ListOptions
|
||||
}
|
||||
|
||||
func makeListTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(listTeamUsersRequest)
|
||||
users, err := svc.ListTeamUsers(ctx, req.TeamID, req.ListOptions)
|
||||
if err != nil {
|
||||
return listUsersResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp := listUsersResponse{Users: []kolide.User{}}
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, *user)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Add / Delete Team Users
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type modifyTeamUsersRequest struct {
|
||||
TeamID uint // From request path
|
||||
// User ID and role must be specified for add users, user ID must be
|
||||
// specified for delete users.
|
||||
Users []kolide.TeamUser `json:"users"`
|
||||
}
|
||||
|
||||
func makeAddTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(modifyTeamUsersRequest)
|
||||
team, err := svc.AddTeamUsers(ctx, req.TeamID, req.Users)
|
||||
if err != nil {
|
||||
return modifyTeamResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return modifyTeamResponse{Team: team}, err
|
||||
}
|
||||
}
|
||||
|
||||
func makeDeleteTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(modifyTeamUsersRequest)
|
||||
team, err := svc.DeleteTeamUsers(ctx, req.TeamID, req.Users)
|
||||
if err != nil {
|
||||
return modifyTeamResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return modifyTeamResponse{Team: team}, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func makeGetSessionUserEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listUsersRequest struct {
|
||||
ListOptions kolide.ListOptions
|
||||
ListOptions kolide.UserListOptions
|
||||
}
|
||||
|
||||
type listUsersResponse struct {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ type KolideEndpoints struct {
|
|||
ModifyTeam endpoint.Endpoint
|
||||
DeleteTeam endpoint.Endpoint
|
||||
ListTeams endpoint.Endpoint
|
||||
ListTeamUsers endpoint.Endpoint
|
||||
AddTeamUsers endpoint.Endpoint
|
||||
DeleteTeamUsers endpoint.Endpoint
|
||||
}
|
||||
|
||||
// MakeKolideServerEndpoints creates the Kolide API endpoints.
|
||||
|
|
@ -217,10 +220,13 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string, lim
|
|||
GetCarveBlock: authenticatedUser(jwtKey, svc, makeGetCarveBlockEndpoint(svc)),
|
||||
Version: authenticatedUser(jwtKey, svc, makeVersionEndpoint(svc)),
|
||||
// TODO permissions for teams endpoints
|
||||
CreateTeam: authenticatedUser(jwtKey, svc, makeCreateTeamEndpoint(svc)),
|
||||
ModifyTeam: authenticatedUser(jwtKey, svc, makeModifyTeamEndpoint(svc)),
|
||||
DeleteTeam: authenticatedUser(jwtKey, svc, makeDeleteTeamEndpoint(svc)),
|
||||
ListTeams: authenticatedUser(jwtKey, svc, makeListTeamsEndpoint(svc)),
|
||||
CreateTeam: authenticatedUser(jwtKey, svc, makeCreateTeamEndpoint(svc)),
|
||||
ModifyTeam: authenticatedUser(jwtKey, svc, makeModifyTeamEndpoint(svc)),
|
||||
DeleteTeam: authenticatedUser(jwtKey, svc, makeDeleteTeamEndpoint(svc)),
|
||||
ListTeams: authenticatedUser(jwtKey, svc, makeListTeamsEndpoint(svc)),
|
||||
ListTeamUsers: authenticatedUser(jwtKey, svc, makeListTeamUsersEndpoint(svc)),
|
||||
AddTeamUsers: authenticatedUser(jwtKey, svc, makeAddTeamUsersEndpoint(svc)),
|
||||
DeleteTeamUsers: authenticatedUser(jwtKey, svc, makeDeleteTeamUsersEndpoint(svc)),
|
||||
|
||||
// Authenticated status endpoints
|
||||
StatusResultStore: authenticatedUser(jwtKey, svc, makeStatusResultStoreEndpoint(svc)),
|
||||
|
|
@ -334,6 +340,9 @@ type kolideHandlers struct {
|
|||
ModifyTeam http.Handler
|
||||
DeleteTeam http.Handler
|
||||
ListTeams http.Handler
|
||||
ListTeamUsers http.Handler
|
||||
AddTeamUsers http.Handler
|
||||
DeleteTeamUsers http.Handler
|
||||
}
|
||||
|
||||
func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *kolideHandlers {
|
||||
|
|
@ -434,6 +443,9 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
|
|||
ModifyTeam: newServer(e.ModifyTeam, decodeModifyTeamRequest),
|
||||
DeleteTeam: newServer(e.DeleteTeam, decodeDeleteTeamRequest),
|
||||
ListTeams: newServer(e.ListTeams, decodeListTeamsRequest),
|
||||
ListTeamUsers: newServer(e.ListTeamUsers, decodeListTeamUsersRequest),
|
||||
AddTeamUsers: newServer(e.AddTeamUsers, decodeModifyTeamUsersRequest),
|
||||
DeleteTeamUsers: newServer(e.DeleteTeamUsers, decodeModifyTeamUsersRequest),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -650,6 +662,9 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
|
|||
r.Handle("/api/v1/fleet/teams", h.ListTeams).Methods("GET").Name("list_teams")
|
||||
r.Handle("/api/v1/fleet/teams/{id}", h.ModifyTeam).Methods("PATCH").Name("modify_team")
|
||||
r.Handle("/api/v1/fleet/teams/{id}", h.DeleteTeam).Methods("DELETE").Name("delete_team")
|
||||
r.Handle("/api/v1/fleet/teams/{id}/users", h.ListTeamUsers).Methods("GET").Name("team_users")
|
||||
r.Handle("/api/v1/fleet/teams/{id}/users", h.AddTeamUsers).Methods("PATCH").Name("add_team_users")
|
||||
r.Handle("/api/v1/fleet/teams/{id}/users", h.DeleteTeamUsers).Methods("DELETE").Name("delete_team_users")
|
||||
|
||||
r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST").Name("enroll_agent")
|
||||
r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST").Name("get_client_config")
|
||||
|
|
@ -721,7 +736,7 @@ func RedirectLoginToSetup(svc kolide.Service, logger kitlog.Logger, next http.Ha
|
|||
// RequireSetup checks to see if the service has been setup.
|
||||
func RequireSetup(svc kolide.Service) (bool, error) {
|
||||
ctx := context.Background()
|
||||
users, err := svc.ListUsers(ctx, kolide.ListOptions{Page: 0, PerPage: 1})
|
||||
users, err := svc.ListUsers(ctx, kolide.UserListOptions{ListOptions: kolide.ListOptions{Page: 0, PerPage: 1}})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (mw loggingMiddleware) CreateUser(ctx context.Context, p kolide.UserPayload
|
|||
return user, err
|
||||
}
|
||||
|
||||
func (mw loggingMiddleware) ListUsers(ctx context.Context, opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
func (mw loggingMiddleware) ListUsers(ctx context.Context, opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
var (
|
||||
users []*kolide.User
|
||||
err error
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func (mw metricsMiddleware) User(ctx context.Context, id uint) (*kolide.User, er
|
|||
return user, err
|
||||
}
|
||||
|
||||
func (mw metricsMiddleware) ListUsers(ctx context.Context, opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
func (mw metricsMiddleware) ListUsers(ctx context.Context, opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
|
||||
var (
|
||||
users []*kolide.User
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
)
|
||||
|
|
@ -46,6 +47,69 @@ func (svc service) ModifyTeam(ctx context.Context, id uint, payload kolide.TeamP
|
|||
return svc.ds.SaveTeam(team)
|
||||
}
|
||||
|
||||
func (svc service) AddTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
|
||||
idMap := make(map[uint]kolide.TeamUser)
|
||||
for _, user := range users {
|
||||
if !kolide.ValidTeamRole(user.Role) {
|
||||
return nil, newInvalidArgumentError("users", fmt.Sprintf("%s is not a valid role for a team user", user.Role))
|
||||
}
|
||||
idMap[user.ID] = user
|
||||
}
|
||||
|
||||
team, err := svc.ds.Team(teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Replace existing
|
||||
for i, existingUser := range team.Users {
|
||||
if user, ok := idMap[existingUser.ID]; ok {
|
||||
team.Users[i] = user
|
||||
delete(idMap, user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Add new (that have not already been replaced)
|
||||
for _, user := range idMap {
|
||||
team.Users = append(team.Users, user)
|
||||
}
|
||||
|
||||
return svc.ds.SaveTeam(team)
|
||||
}
|
||||
|
||||
func (svc service) DeleteTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
|
||||
idMap := make(map[uint]bool)
|
||||
for _, user := range users {
|
||||
idMap[user.ID] = true
|
||||
}
|
||||
|
||||
team, err := svc.ds.Team(teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newUsers := []kolide.TeamUser{}
|
||||
// Delete existing
|
||||
for _, existingUser := range team.Users {
|
||||
if _, ok := idMap[existingUser.ID]; !ok {
|
||||
// Only add non-deleted users
|
||||
newUsers = append(newUsers, existingUser)
|
||||
}
|
||||
}
|
||||
team.Users = newUsers
|
||||
|
||||
return svc.ds.SaveTeam(team)
|
||||
}
|
||||
|
||||
func (svc service) ListTeamUsers(ctx context.Context, teamID uint, opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
team, err := svc.ds.Team(teamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.ds.ListUsers(kolide.UserListOptions{ListOptions: opt, TeamID: team.ID})
|
||||
}
|
||||
|
||||
func (svc service) ListTeams(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Team, error) {
|
||||
return svc.ds.ListTeams(opt)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ func (svc service) AuthenticatedUser(ctx context.Context) (*kolide.User, error)
|
|||
return vc.User, nil
|
||||
}
|
||||
|
||||
func (svc service) ListUsers(ctx context.Context, opt kolide.ListOptions) ([]*kolide.User, error) {
|
||||
func (svc service) ListUsers(ctx context.Context, opt kolide.UserListOptions) ([]*kolide.User, error) {
|
||||
return svc.ds.ListUsers(opt)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,25 @@ func hostListOptionsFromRequest(r *http.Request) (kolide.HostListOptions, error)
|
|||
return hopt, nil
|
||||
}
|
||||
|
||||
func userListOptionsFromRequest(r *http.Request) (kolide.UserListOptions, error) {
|
||||
opt, err := listOptionsFromRequest(r)
|
||||
if err != nil {
|
||||
return kolide.UserListOptions{}, err
|
||||
}
|
||||
|
||||
uopt := kolide.UserListOptions{ListOptions: opt}
|
||||
|
||||
if tid := r.URL.Query().Get("team_id"); tid != "" {
|
||||
teamID, err := strconv.ParseUint(tid, 10, 64)
|
||||
if err != nil {
|
||||
return uopt, errors.Wrap(err, "parse team_id as int")
|
||||
}
|
||||
uopt.TeamID = uint(teamID)
|
||||
}
|
||||
|
||||
return uopt, nil
|
||||
}
|
||||
|
||||
func decodeNoParamsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -214,3 +233,15 @@ func decodeGetGenericSpecRequest(ctx context.Context, r *http.Request) (interfac
|
|||
req.Name = name
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type genericIDListRequest struct {
|
||||
IDs []uint `json:"ids"`
|
||||
}
|
||||
|
||||
func decodeGenericIDListRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req genericIDListRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,3 +43,29 @@ func decodeDeleteTeamRequest(ctx context.Context, r *http.Request) (interface{},
|
|||
}
|
||||
return deleteTeamRequest{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeListTeamUsersRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt, err := listOptionsFromRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listTeamUsersRequest{TeamID: id, ListOptions: opt}, nil
|
||||
}
|
||||
|
||||
func decodeModifyTeamUsersRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req modifyTeamUsersRequest
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.TeamID = id
|
||||
return req, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func decodeGetUserRequest(ctx context.Context, r *http.Request) (interface{}, er
|
|||
}
|
||||
|
||||
func decodeListUsersRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
opt, err := listOptionsFromRequest(r)
|
||||
opt, err := userListOptionsFromRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue