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:
Zach Wasserman 2021-04-21 20:54:09 -07:00 committed by GitHub
parent af802dc15f
commit e8f4860d51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 422 additions and 29 deletions

View file

@ -98,6 +98,7 @@ var TestFunctions = [...]func(*testing.T, kolide.Datastore){
testCarveCleanupCarves,
testCarveUpdateCarve,
testTeamGetSetDelete,
testTeamUsers,
testUserTeams,
testUserCreateWithTeams,
}

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -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")

View file

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

View file

@ -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"`

View file

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

View file

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

View file

@ -113,7 +113,7 @@ func makeGetSessionUserEndpoint(svc kolide.Service) endpoint.Endpoint {
////////////////////////////////////////////////////////////////////////////////
type listUsersRequest struct {
ListOptions kolide.ListOptions
ListOptions kolide.UserListOptions
}
type listUsersResponse struct {

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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