diff --git a/server/datastore/datastore.go b/server/datastore/datastore.go index e91c828f5b..7abb75eb30 100644 --- a/server/datastore/datastore.go +++ b/server/datastore/datastore.go @@ -54,6 +54,7 @@ var TestFunctions = [...]func(*testing.T, kolide.Datastore){ testListPacksForHost, testHostIDsByName, testHostByIdentifier, + testAddHostsToTeam, testListPacks, testDistributedQueryCampaign, testCleanupDistributedQueryCampaigns, @@ -98,6 +99,5 @@ var TestFunctions = [...]func(*testing.T, kolide.Datastore){ testTeamUsers, testUserTeams, testUserCreateWithTeams, - testTeamAddHostsToTeam, testSaveHostSoftware, } diff --git a/server/datastore/datastore_hosts.go b/server/datastore/datastore_hosts.go index d91bb9c00a..2dd36c9069 100644 --- a/server/datastore/datastore_hosts.go +++ b/server/datastore/datastore_hosts.go @@ -14,6 +14,7 @@ import ( "github.com/fleetdm/fleet/server/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v3" ) var enrollTests = []struct { @@ -799,3 +800,50 @@ func testHostByIdentifier(t *testing.T, ds kolide.Datastore) { h, err = ds.HostByIdentifier("foobar") require.Error(t, err) } + +func testAddHostsToTeam(t *testing.T, ds kolide.Datastore) { + team1, err := ds.NewTeam(&kolide.Team{Name: "team1"}) + require.NoError(t, err) + team2, err := ds.NewTeam(&kolide.Team{Name: "team2"}) + require.NoError(t, err) + + for i := 0; i < 10; i++ { + test.NewHost(t, ds, fmt.Sprint(i), "", "key"+fmt.Sprint(i), "uuid"+fmt.Sprint(i), time.Now()) + } + + for i := 1; i <= 10; i++ { + host, err := ds.Host(uint(i)) + require.NoError(t, err) + assert.Equal(t, null.Int{}, host.TeamID) + } + + require.NoError(t, ds.AddHostsToTeam(&team1.ID, []uint{1, 2, 3})) + require.NoError(t, ds.AddHostsToTeam(&team2.ID, []uint{3, 4, 5})) + + for i := 1; i <= 10; i++ { + host, err := ds.Host(uint(i)) + require.NoError(t, err) + expectedID := null.Int{} + switch { + case i <= 2: + expectedID = null.IntFrom(int64(team1.ID)) + case i <= 5: + expectedID = null.IntFrom(int64(team2.ID)) + } + assert.Equal(t, expectedID, host.TeamID) + } + + require.NoError(t, ds.AddHostsToTeam(nil, []uint{1, 2, 3, 4})) + require.NoError(t, ds.AddHostsToTeam(&team1.ID, []uint{5, 6, 7, 8, 9, 10})) + + for i := 1; i <= 10; i++ { + host, err := ds.Host(uint(i)) + require.NoError(t, err) + expectedID := null.Int{} + switch { + case i >= 5: + expectedID = null.IntFrom(int64(team1.ID)) + } + assert.Equal(t, expectedID, host.TeamID) + } +} diff --git a/server/datastore/datastore_teams.go b/server/datastore/datastore_teams.go index 3b211c2a58..8efeb4df58 100644 --- a/server/datastore/datastore_teams.go +++ b/server/datastore/datastore_teams.go @@ -1,15 +1,11 @@ package datastore import ( - "fmt" "testing" - "time" "github.com/fleetdm/fleet/server/kolide" - "github.com/fleetdm/fleet/server/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/guregu/null.v3" ) func testTeamGetSetDelete(t *testing.T, ds kolide.Datastore) { @@ -104,50 +100,3 @@ func testTeamUsers(t *testing.T, ds kolide.Datastore) { assert.ElementsMatch(t, team2Users, team2.Users) } - -func testTeamAddHostsToTeam(t *testing.T, ds kolide.Datastore) { - team1, err := ds.NewTeam(&kolide.Team{Name: "team1"}) - require.NoError(t, err) - team2, err := ds.NewTeam(&kolide.Team{Name: "team2"}) - require.NoError(t, err) - - for i := 0; i < 10; i++ { - test.NewHost(t, ds, fmt.Sprint(i), "", "key"+fmt.Sprint(i), "uuid"+fmt.Sprint(i), time.Now()) - } - - for i := 1; i <= 10; i++ { - host, err := ds.Host(uint(i)) - require.NoError(t, err) - assert.Equal(t, null.Int{}, host.TeamID) - } - - require.NoError(t, ds.AddHostsToTeam(&team1.ID, []uint{1, 2, 3})) - require.NoError(t, ds.AddHostsToTeam(&team2.ID, []uint{3, 4, 5})) - - for i := 1; i <= 10; i++ { - host, err := ds.Host(uint(i)) - require.NoError(t, err) - expectedID := null.Int{} - switch { - case i <= 2: - expectedID = null.IntFrom(int64(team1.ID)) - case i <= 5: - expectedID = null.IntFrom(int64(team2.ID)) - } - assert.Equal(t, expectedID, host.TeamID) - } - - require.NoError(t, ds.AddHostsToTeam(nil, []uint{1, 2, 3, 4})) - require.NoError(t, ds.AddHostsToTeam(&team1.ID, []uint{5, 6, 7, 8, 9, 10})) - - for i := 1; i <= 10; i++ { - host, err := ds.Host(uint(i)) - require.NoError(t, err) - expectedID := null.Int{} - switch { - case i >= 5: - expectedID = null.IntFrom(int64(team1.ID)) - } - assert.Equal(t, expectedID, host.TeamID) - } -} diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index e23ee4cb79..7d0c3f5b94 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -607,3 +607,24 @@ func (d *Datastore) HostByIdentifier(identifier string) (*kolide.Host, error) { return host, nil } + +func (d *Datastore) AddHostsToTeam(teamID *uint, hostIDs []uint) error { + if len(hostIDs) == 0 { + return nil + } + + sql := ` + UPDATE hosts SET team_id = ? + WHERE id IN (?) + ` + sql, args, err := sqlx.In(sql, teamID, hostIDs) + if err != nil { + return errors.Wrap(err, "sqlx.In AddHostsToTeam") + } + + if _, err := d.db.Exec(sql, args...); err != nil { + return errors.Wrap(err, "exec AddHostsToTeam") + } + + return nil +} diff --git a/server/datastore/mysql/teams.go b/server/datastore/mysql/teams.go index 88fd3a73e8..dd5134f8b7 100644 --- a/server/datastore/mysql/teams.go +++ b/server/datastore/mysql/teams.go @@ -165,24 +165,3 @@ func (d *Datastore) ListTeams(opt kolide.ListOptions) ([]*kolide.Team, error) { return teams, nil } - -func (d *Datastore) AddHostsToTeam(teamID *uint, hostIDs []uint) error { - if len(hostIDs) == 0 { - return nil - } - - sql := ` - UPDATE hosts SET team_id = ? - WHERE id IN (?) - ` - sql, args, err := sqlx.In(sql, teamID, hostIDs) - if err != nil { - return errors.Wrap(err, "sqlx.In AddHostsToTeam") - } - - if _, err := d.db.Exec(sql, args...); err != nil { - return errors.Wrap(err, "exec AddHostsToTeam") - } - - return nil -} diff --git a/server/kolide/hosts.go b/server/kolide/hosts.go index acca173c45..108fc5582b 100644 --- a/server/kolide/hosts.go +++ b/server/kolide/hosts.go @@ -75,6 +75,9 @@ type HostStore interface { // Possible matches can be on osquery_host_identifier, node_key, UUID, or // hostname. HostByIdentifier(identifier string) (*Host, error) + // AddHostsToTeam adds hosts to an existing team, clearing their team + // settings if teamID is nil. + AddHostsToTeam(teamID *uint, hostIDs []uint) error } type HostService interface { @@ -86,8 +89,10 @@ type HostService interface { // Possible matches can be on osquery_host_identifier, node_key, UUID, or // hostname. HostByIdentifier(ctx context.Context, identifier string) (*HostDetail, error) - FlushSeenHosts(ctx context.Context) error + // AddHostsToTeam adds hosts to an existing team, clearing their team + // settings if teamID is nil. + AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error } type HostListOptions struct { diff --git a/server/kolide/teams.go b/server/kolide/teams.go index 3c6b0d4424..c7ff55187c 100644 --- a/server/kolide/teams.go +++ b/server/kolide/teams.go @@ -20,9 +20,6 @@ type TeamStore interface { // ListTeams lists teams with the ordering and filters in the provided // options. ListTeams(opt ListOptions) ([]*Team, error) - // AddHostsToTeam adds hosts to an existing team, clearing their team - // settings if teamID is nil. - AddHostsToTeam(teamID *uint, hostIDs []uint) error } type TeamService interface { @@ -43,9 +40,6 @@ type TeamService interface { 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) - // AddHostsToTeam adds hosts to an existing team, clearing their team - // settings if teamID is nil. - AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error } type TeamPayload struct { diff --git a/server/mock/datastore_hosts.go b/server/mock/datastore_hosts.go index 3c76777d15..29f2e83626 100644 --- a/server/mock/datastore_hosts.go +++ b/server/mock/datastore_hosts.go @@ -40,6 +40,8 @@ type DistributedQueriesForHostFunc func(host *kolide.Host) (map[uint]string, err type HostIDsByNameFunc func(hostnames []string) ([]uint, error) +type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error + type HostStore struct { NewHostFunc NewHostFunc NewHostFuncInvoked bool @@ -85,6 +87,9 @@ type HostStore struct { HostIDsByNameFunc HostIDsByNameFunc HostIDsByNameFuncInvoked bool + + AddHostsToTeamFunc AddHostsToTeamFunc + AddHostsToTeamFuncInvoked bool } func (s *HostStore) NewHost(host *kolide.Host) (*kolide.Host, error) { @@ -161,3 +166,8 @@ func (s *HostStore) HostIDsByName(hostnames []string) ([]uint, error) { s.HostIDsByNameFuncInvoked = true return s.HostIDsByNameFunc(hostnames) } + +func (s *HostStore) AddHostsToTeam(teamID *uint, hostIDs []uint) error { + s.AddHostsToTeamFuncInvoked = true + return s.AddHostsToTeamFunc(teamID, hostIDs) +} diff --git a/server/mock/datastore_teams.go b/server/mock/datastore_teams.go index 690e0005b0..a6331069a9 100644 --- a/server/mock/datastore_teams.go +++ b/server/mock/datastore_teams.go @@ -20,8 +20,6 @@ type TeamByNameFunc func(name string) (*kolide.Team, error) type ListTeamsFunc func(opt kolide.ListOptions) ([]*kolide.Team, error) -type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error - type TeamStore struct { NewTeamFunc NewTeamFunc NewTeamFuncInvoked bool @@ -40,9 +38,6 @@ type TeamStore struct { ListTeamsFunc ListTeamsFunc ListTeamsFuncInvoked bool - - AddHostsToTeamFunc AddHostsToTeamFunc - AddHostsToTeamFuncInvoked bool } func (s *TeamStore) NewTeam(team *kolide.Team) (*kolide.Team, error) { @@ -74,8 +69,3 @@ func (s *TeamStore) ListTeams(opt kolide.ListOptions) ([]*kolide.Team, error) { s.ListTeamsFuncInvoked = true return s.ListTeamsFunc(opt) } - -func (s *TeamStore) AddHostsToTeam(teamID *uint, hostIDs []uint) error { - s.AddHostsToTeamFuncInvoked = true - return s.AddHostsToTeamFunc(teamID, hostIDs) -} diff --git a/server/service/endpoint_hosts.go b/server/service/endpoint_hosts.go index 3a0eb8ccdf..e102561944 100644 --- a/server/service/endpoint_hosts.go +++ b/server/service/endpoint_hosts.go @@ -188,3 +188,28 @@ func makeDeleteHostEndpoint(svc kolide.Service) endpoint.Endpoint { return deleteHostResponse{}, nil } } + +//////////////////////////////////////////////////////////////////////////////// +// Add Hosts to Team +//////////////////////////////////////////////////////////////////////////////// + +type addHostsToTeamRequest struct { + TeamID uint `json:"team_id"` + HostIDs []uint `json:"hosts"` +} + +type addHostsToTeamResponse struct { + Err error `json:"error,omitempty"` +} + +func makeAddHostsToTeamEndpoint(svc kolide.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(addHostsToTeamRequest) + err := svc.AddHostsToTeam(ctx, &req.TeamID, req.HostIDs) + if err != nil { + return addHostsToTeamResponse{Err: err}, nil + } + + return addHostsToTeamResponse{}, err + } +} diff --git a/server/service/endpoint_teams.go b/server/service/endpoint_teams.go index 8c2a4b3164..ef1364ef7e 100644 --- a/server/service/endpoint_teams.go +++ b/server/service/endpoint_teams.go @@ -193,28 +193,3 @@ func makeDeleteTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint { return teamResponse{Team: team}, err } } - -//////////////////////////////////////////////////////////////////////////////// -// Add Hosts to Team -//////////////////////////////////////////////////////////////////////////////// - -type addHostsToTeamRequest struct { - TeamID uint // From request path - HostIDs []uint `json:"hosts"` -} - -type addHostsToTeamResponse struct { - Err error `json:"error,omitempty"` -} - -func makeAddHostsToTeamEndpoint(svc kolide.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(addHostsToTeamRequest) - err := svc.AddHostsToTeam(ctx, &req.TeamID, req.HostIDs) - if err != nil { - return addHostsToTeamResponse{Err: err}, nil - } - - return addHostsToTeamResponse{}, err - } -} diff --git a/server/service/handler.go b/server/service/handler.go index 087069e1e9..f316f5946c 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -96,6 +96,7 @@ type KolideEndpoints struct { DeleteHost endpoint.Endpoint ListHosts endpoint.Endpoint GetHostSummary endpoint.Endpoint + AddHostsToTeam endpoint.Endpoint SearchTargets endpoint.Endpoint GetCertificate endpoint.Endpoint ChangeEmail endpoint.Endpoint @@ -116,7 +117,6 @@ type KolideEndpoints struct { ListTeamUsers endpoint.Endpoint AddTeamUsers endpoint.Endpoint DeleteTeamUsers endpoint.Endpoint - AddHostsToTeam endpoint.Endpoint } // MakeKolideServerEndpoints creates the Kolide API endpoints. @@ -200,6 +200,7 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string, lim ListHosts: authenticatedUser(jwtKey, svc, makeListHostsEndpoint(svc)), GetHostSummary: authenticatedUser(jwtKey, svc, makeGetHostSummaryEndpoint(svc)), DeleteHost: authenticatedUser(jwtKey, svc, makeDeleteHostEndpoint(svc)), + AddHostsToTeam: authenticatedUser(jwtKey, svc, makeAddHostsToTeamEndpoint(svc)), CreateLabel: authenticatedUser(jwtKey, svc, makeCreateLabelEndpoint(svc)), ModifyLabel: authenticatedUser(jwtKey, svc, makeModifyLabelEndpoint(svc)), GetLabel: authenticatedUser(jwtKey, svc, makeGetLabelEndpoint(svc)), @@ -226,7 +227,6 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string, lim ListTeamUsers: authenticatedUser(jwtKey, svc, makeListTeamUsersEndpoint(svc)), AddTeamUsers: authenticatedUser(jwtKey, svc, makeAddTeamUsersEndpoint(svc)), DeleteTeamUsers: authenticatedUser(jwtKey, svc, makeDeleteTeamUsersEndpoint(svc)), - AddHostsToTeam: authenticatedUser(jwtKey, svc, makeAddHostsToTeamEndpoint(svc)), // Authenticated status endpoints StatusResultStore: authenticatedUser(jwtKey, svc, makeStatusResultStoreEndpoint(svc)), @@ -322,6 +322,7 @@ type kolideHandlers struct { DeleteHost http.Handler ListHosts http.Handler GetHostSummary http.Handler + AddHostsToTeam http.Handler SearchTargets http.Handler GetCertificate http.Handler ChangeEmail http.Handler @@ -342,7 +343,6 @@ type kolideHandlers struct { ListTeamUsers http.Handler AddTeamUsers http.Handler DeleteTeamUsers http.Handler - AddHostsToTeam http.Handler } func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *kolideHandlers { @@ -425,6 +425,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli DeleteHost: newServer(e.DeleteHost, decodeDeleteHostRequest), ListHosts: newServer(e.ListHosts, decodeListHostsRequest), GetHostSummary: newServer(e.GetHostSummary, decodeNoParamsRequest), + AddHostsToTeam: newServer(e.AddHostsToTeam, decodeAddHostsToTeamRequest), SearchTargets: newServer(e.SearchTargets, decodeSearchTargetsRequest), GetCertificate: newServer(e.GetCertificate, decodeNoParamsRequest), ChangeEmail: newServer(e.ChangeEmail, decodeChangeEmailRequest), @@ -445,7 +446,6 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli ListTeamUsers: newServer(e.ListTeamUsers, decodeListTeamUsersRequest), AddTeamUsers: newServer(e.AddTeamUsers, decodeModifyTeamUsersRequest), DeleteTeamUsers: newServer(e.DeleteTeamUsers, decodeModifyTeamUsersRequest), - AddHostsToTeam: newServer(e.AddHostsToTeam, decodeAddHostsToTeamRequest), } } @@ -643,6 +643,7 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) { r.Handle("/api/v1/fleet/hosts/{id}", h.GetHost).Methods("GET").Name("get_host") r.Handle("/api/v1/fleet/hosts/identifier/{identifier}", h.HostByIdentifier).Methods("GET").Name("host_by_identifier") r.Handle("/api/v1/fleet/hosts/{id}", h.DeleteHost).Methods("DELETE").Name("delete_host") + r.Handle("/api/v1/fleet/hosts/transfer", h.AddHostsToTeam).Methods("POST").Name("add_hosts_to_team") r.Handle("/api/v1/fleet/targets", h.SearchTargets).Methods("POST").Name("search_targets") @@ -663,7 +664,6 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) { 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/fleet/teams/{id}/hosts", h.AddHostsToTeam).Methods("POST").Name("add_hosts_to_team") 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") diff --git a/server/service/service_hosts.go b/server/service/service_hosts.go index 5cbb7e3c14..c53da7311e 100644 --- a/server/service/service_hosts.go +++ b/server/service/service_hosts.go @@ -75,3 +75,7 @@ func (svc *service) FlushSeenHosts(ctx context.Context) error { hostIDs := svc.seenHostSet.getAndClearHostIDs() return svc.ds.MarkHostsSeen(hostIDs, svc.clock.Now()) } + +func (svc service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error { + return svc.ds.AddHostsToTeam(teamID, hostIDs) +} diff --git a/server/service/service_teams.go b/server/service/service_teams.go index 06a52ff4ab..918443e6a8 100644 --- a/server/service/service_teams.go +++ b/server/service/service_teams.go @@ -133,7 +133,3 @@ func (svc service) ListTeams(ctx context.Context, opt kolide.ListOptions) ([]*ko func (svc service) DeleteTeam(ctx context.Context, tid uint) error { return svc.ds.DeleteTeam(tid) } - -func (svc service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error { - return svc.ds.AddHostsToTeam(teamID, hostIDs) -} diff --git a/server/service/transport_hosts.go b/server/service/transport_hosts.go index ad817aebb6..862a8df5fe 100644 --- a/server/service/transport_hosts.go +++ b/server/service/transport_hosts.go @@ -2,6 +2,7 @@ package service import ( "context" + "encoding/json" "net/http" ) @@ -37,3 +38,12 @@ func decodeListHostsRequest(ctx context.Context, r *http.Request) (interface{}, return listHostsRequest{ListOptions: hopt}, nil } + +func decodeAddHostsToTeamRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var req addHostsToTeamRequest + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + return req, nil +} diff --git a/server/service/transport_teams.go b/server/service/transport_teams.go index d73d97f101..ff4856027e 100644 --- a/server/service/transport_teams.go +++ b/server/service/transport_teams.go @@ -81,16 +81,3 @@ func decodeModifyTeamUsersRequest(ctx context.Context, r *http.Request) (interfa } return req, nil } - -func decodeAddHostsToTeamRequest(ctx context.Context, r *http.Request) (interface{}, error) { - id, err := idFromRequest(r, "id") - if err != nil { - return nil, err - } - req := addHostsToTeamRequest{TeamID: id} - err = json.NewDecoder(r.Body).Decode(&req) - if err != nil { - return nil, err - } - return req, nil -}