Implement API endpoints for Teams agent options (#757)

- Add agent options endpoint.
- Remove setting agent options from standard modify team endpoint.
This commit is contained in:
Zach Wasserman 2021-05-12 10:38:00 -07:00 committed by GitHub
parent 9b4976ef8f
commit 9de5b720b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 37 deletions

View file

@ -25,8 +25,10 @@ type TeamStore interface {
type TeamService interface {
// NewTeam creates a new team.
NewTeam(ctx context.Context, p TeamPayload) (*Team, error)
// ModifyTeam modifies an existing team.
// ModifyTeam modifies an existing team (besides agent options).
ModifyTeam(ctx context.Context, id uint, payload TeamPayload) (*Team, error)
// ModifyTeam modifies agent options for a team.
ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*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.
@ -41,9 +43,9 @@ type TeamService interface {
}
type TeamPayload struct {
Name *string `json:"name"`
Description *string `json:"description"`
AgentOptions *json.RawMessage `json:"agent_options"`
Name *string `json:"name"`
Description *string `json:"description"`
// Note AgentOptions must be set by a separate endpoint.
}
// Team is the data representation for the "Team" concept (group of hosts and

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"github.com/fleetdm/fleet/server/kolide"
"github.com/go-kit/kit/endpoint"
@ -15,12 +16,12 @@ type createTeamRequest struct {
payload kolide.TeamPayload
}
type createTeamResponse struct {
type teamResponse struct {
Team *kolide.Team `json:"team,omitempty"`
Err error `json:"error,omitempty"`
}
func (r createTeamResponse) error() error { return r.Err }
func (r teamResponse) error() error { return r.Err }
func makeCreateTeamEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
@ -28,10 +29,10 @@ func makeCreateTeamEndpoint(svc kolide.Service) endpoint.Endpoint {
team, err := svc.NewTeam(ctx, req.payload)
if err != nil {
return createTeamResponse{Err: err}, nil
return teamResponse{Err: err}, nil
}
return createTeamResponse{Team: team}, nil
return teamResponse{Team: team}, nil
}
}
@ -44,22 +45,36 @@ type modifyTeamRequest struct {
payload kolide.TeamPayload
}
type modifyTeamResponse struct {
Team *kolide.Team `json:"team,omitempty"`
Err error `json:"error,omitempty"`
}
func (r modifyTeamResponse) error() error { return r.Err }
func makeModifyTeamEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(modifyTeamRequest)
team, err := svc.ModifyTeam(ctx, req.ID, req.payload)
if err != nil {
return modifyTeamResponse{Err: err}, nil
return teamResponse{Err: err}, nil
}
return modifyTeamResponse{Team: team}, err
return teamResponse{Team: team}, err
}
}
////////////////////////////////////////////////////////////////////////////////
// Modify Team Agent Options
////////////////////////////////////////////////////////////////////////////////
type modifyTeamAgentOptionsRequest struct {
ID uint
options json.RawMessage
}
func makeModifyTeamAgentOptionsEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(modifyTeamAgentOptionsRequest)
team, err := svc.ModifyTeamAgentOptions(ctx, req.ID, req.options)
if err != nil {
return teamResponse{Err: err}, nil
}
return teamResponse{Team: team}, err
}
}
@ -160,10 +175,10 @@ func makeAddTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint {
req := request.(modifyTeamUsersRequest)
team, err := svc.AddTeamUsers(ctx, req.TeamID, req.Users)
if err != nil {
return modifyTeamResponse{Err: err}, nil
return teamResponse{Err: err}, nil
}
return modifyTeamResponse{Team: team}, err
return teamResponse{Team: team}, err
}
}
@ -172,9 +187,9 @@ func makeDeleteTeamUsersEndpoint(svc kolide.Service) endpoint.Endpoint {
req := request.(modifyTeamUsersRequest)
team, err := svc.DeleteTeamUsers(ctx, req.TeamID, req.Users)
if err != nil {
return modifyTeamResponse{Err: err}, nil
return teamResponse{Err: err}, nil
}
return modifyTeamResponse{Team: team}, err
return teamResponse{Team: team}, err
}
}

View file

@ -110,6 +110,7 @@ type KolideEndpoints struct {
Version endpoint.Endpoint
CreateTeam endpoint.Endpoint
ModifyTeam endpoint.Endpoint
ModifyTeamAgentOptions endpoint.Endpoint
DeleteTeam endpoint.Endpoint
ListTeams endpoint.Endpoint
ListTeamUsers endpoint.Endpoint
@ -216,13 +217,14 @@ 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)),
ListTeamUsers: authenticatedUser(jwtKey, svc, makeListTeamUsersEndpoint(svc)),
AddTeamUsers: authenticatedUser(jwtKey, svc, makeAddTeamUsersEndpoint(svc)),
DeleteTeamUsers: authenticatedUser(jwtKey, svc, makeDeleteTeamUsersEndpoint(svc)),
CreateTeam: authenticatedUser(jwtKey, svc, makeCreateTeamEndpoint(svc)),
ModifyTeam: authenticatedUser(jwtKey, svc, makeModifyTeamEndpoint(svc)),
ModifyTeamAgentOptions: authenticatedUser(jwtKey, svc, makeModifyTeamAgentOptionsEndpoint(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)),
@ -332,6 +334,7 @@ type kolideHandlers struct {
Version http.Handler
CreateTeam http.Handler
ModifyTeam http.Handler
ModifyTeamAgentOptions http.Handler
DeleteTeam http.Handler
ListTeams http.Handler
ListTeamUsers http.Handler
@ -433,6 +436,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
Version: newServer(e.Version, decodeNoParamsRequest),
CreateTeam: newServer(e.CreateTeam, decodeCreateTeamRequest),
ModifyTeam: newServer(e.ModifyTeam, decodeModifyTeamRequest),
ModifyTeamAgentOptions: newServer(e.ModifyTeamAgentOptions, decodeModifyTeamAgentOptionsRequest),
DeleteTeam: newServer(e.DeleteTeam, decodeDeleteTeamRequest),
ListTeams: newServer(e.ListTeams, decodeListTeamsRequest),
ListTeamUsers: newServer(e.ListTeamUsers, decodeListTeamUsersRequest),
@ -651,6 +655,7 @@ 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}/agent_options", h.ModifyTeamAgentOptions).Methods("POST").Name("modify_team_agent_options")
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")

View file

@ -2,13 +2,14 @@ package service
import (
"context"
"encoding/json"
"fmt"
"github.com/fleetdm/fleet/server/kolide"
)
func (svc service) NewTeam(ctx context.Context, p kolide.TeamPayload) (*kolide.Team, error) {
team := &kolide.Team{AgentOptions: p.AgentOptions}
team := &kolide.Team{}
if p.Name == nil {
return nil, newInvalidArgumentError("name", "missing required argument")
@ -43,8 +44,20 @@ func (svc service) ModifyTeam(ctx context.Context, id uint, payload kolide.TeamP
if payload.Description != nil {
team.Description = *payload.Description
}
if payload.AgentOptions != nil {
team.AgentOptions = payload.AgentOptions
return svc.ds.SaveTeam(team)
}
func (svc service) ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*kolide.Team, error) {
team, err := svc.ds.Team(id)
if err != nil {
return nil, err
}
if options != nil {
team.AgentOptions = &options
} else {
team.AgentOptions = nil
}
return svc.ds.SaveTeam(team)

View file

@ -19,13 +19,26 @@ func decodeModifyTeamRequest(ctx context.Context, r *http.Request) (interface{},
if err != nil {
return nil, err
}
var resp modifyTeamRequest
err = json.NewDecoder(r.Body).Decode(&resp.payload)
req := modifyTeamRequest{ID: id}
err = json.NewDecoder(r.Body).Decode(&req.payload)
if err != nil {
return nil, err
}
resp.ID = id
return resp, nil
req.ID = id
return req, nil
}
func decodeModifyTeamAgentOptionsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
id, err := idFromRequest(r, "id")
if err != nil {
return nil, err
}
req := modifyTeamAgentOptionsRequest{ID: id}
err = json.NewDecoder(r.Body).Decode(&req.options)
if err != nil {
return nil, err
}
return req, nil
}
func decodeListTeamsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
@ -61,11 +74,10 @@ func decodeModifyTeamUsersRequest(ctx context.Context, r *http.Request) (interfa
if err != nil {
return nil, err
}
var req modifyTeamUsersRequest
req := modifyTeamUsersRequest{TeamID: id}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return nil, err
}
req.TeamID = id
return req, nil
}