diff --git a/server/service/activities.go b/server/service/activities.go new file mode 100644 index 0000000000..8dae97ab13 --- /dev/null +++ b/server/service/activities.go @@ -0,0 +1,40 @@ +package service + +import ( + "context" + + "github.com/fleetdm/fleet/v4/server/fleet" +) + +//////////////////////////////////////////////////////////////////////////////// +// Get activities +//////////////////////////////////////////////////////////////////////////////// + +type listActivitiesRequest struct { + ListOptions fleet.ListOptions `url:"list_options"` +} + +type listActivitiesResponse struct { + Activities []*fleet.Activity `json:"activities"` + Err error `json:"error,omitempty"` +} + +func (r listActivitiesResponse) error() error { return r.Err } + +func listActivitiesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) { + req := request.(*listActivitiesRequest) + activities, err := svc.ListActivities(ctx, req.ListOptions) + if err != nil { + return listActivitiesResponse{Err: err}, nil + } + + return listActivitiesResponse{Activities: activities}, nil +} + +// ListActivities returns a slice of activities for the whole organization +func (svc *Service) ListActivities(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Activity, error) { + if err := svc.authz.Authorize(ctx, &fleet.Activity{}, fleet.ActionRead); err != nil { + return nil, err + } + return svc.ds.ListActivities(ctx, opt) +} diff --git a/server/service/activities_test.go b/server/service/activities_test.go new file mode 100644 index 0000000000..7c124a2996 --- /dev/null +++ b/server/service/activities_test.go @@ -0,0 +1,39 @@ +package service + +import ( + "context" + "testing" + + "github.com/fleetdm/fleet/v4/server/authz" + "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/mock" + "github.com/fleetdm/fleet/v4/server/test" + "github.com/stretchr/testify/require" +) + +func TestListActivities(t *testing.T) { + ds := new(mock.Store) + svc := newTestService(ds, nil, nil) + + ds.ListActivitiesFunc = func(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Activity, error) { + return []*fleet.Activity{ + {ID: 1}, + {ID: 2}, + }, nil + } + + // admin user + activities, err := svc.ListActivities(test.UserContext(test.UserAdmin), fleet.ListOptions{}) + require.NoError(t, err) + require.Len(t, activities, 2) + + // anyone can read activities + activities, err = svc.ListActivities(test.UserContext(test.UserNoRoles), fleet.ListOptions{}) + require.NoError(t, err) + require.Len(t, activities, 2) + + // no user in context + _, err = svc.ListActivities(context.Background(), fleet.ListOptions{}) + require.Error(t, err) + require.Contains(t, err.Error(), authz.ForbiddenErrorMessage) +} diff --git a/server/service/endpoint_activities.go b/server/service/endpoint_activities.go deleted file mode 100644 index fe2f75f71f..0000000000 --- a/server/service/endpoint_activities.go +++ /dev/null @@ -1,34 +0,0 @@ -package service - -import ( - "context" - "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/go-kit/kit/endpoint" -) - -//////////////////////////////////////////////////////////////////////////////// -// Get activities -//////////////////////////////////////////////////////////////////////////////// - -type listActivitiesRequest struct { - ListOptions fleet.ListOptions -} - -type listActivitiesResponse struct { - Activities []*fleet.Activity `json:"activities"` - Err error `json:"error,omitempty"` -} - -func (r listActivitiesResponse) error() error { return r.Err } - -func makeListActivitiesEndpoint(svc fleet.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(listActivitiesRequest) - activities, err := svc.ListActivities(ctx, req.ListOptions) - if err != nil { - return listActivitiesResponse{Err: err}, nil - } - - return listActivitiesResponse{Activities: activities}, err - } -} diff --git a/server/service/handler.go b/server/service/handler.go index eea09df056..7b8825e251 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -120,7 +120,6 @@ type FleetEndpoints struct { AddTeamUsers endpoint.Endpoint DeleteTeamUsers endpoint.Endpoint TeamEnrollSecrets endpoint.Endpoint - ListActivities endpoint.Endpoint } // MakeFleetServerEndpoints creates the Fleet API endpoints. @@ -228,7 +227,6 @@ func MakeFleetServerEndpoints(svc fleet.Service, urlPrefix string, limitStore th AddTeamUsers: authenticatedUser(svc, makeAddTeamUsersEndpoint(svc)), DeleteTeamUsers: authenticatedUser(svc, makeDeleteTeamUsersEndpoint(svc)), TeamEnrollSecrets: authenticatedUser(svc, makeTeamEnrollSecretsEndpoint(svc)), - ListActivities: authenticatedUser(svc, makeListActivitiesEndpoint(svc)), // Authenticated status endpoints StatusResultStore: authenticatedUser(svc, makeStatusResultStoreEndpoint(svc)), @@ -348,7 +346,6 @@ type fleetHandlers struct { AddTeamUsers http.Handler DeleteTeamUsers http.Handler TeamEnrollSecrets http.Handler - ListActivities http.Handler } func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandlers { @@ -455,7 +452,6 @@ func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandle AddTeamUsers: newServer(e.AddTeamUsers, decodeModifyTeamUsersRequest), DeleteTeamUsers: newServer(e.DeleteTeamUsers, decodeModifyTeamUsersRequest), TeamEnrollSecrets: newServer(e.TeamEnrollSecrets, decodeTeamEnrollSecretsRequest), - ListActivities: newServer(e.ListActivities, decodeListActivitiesRequest), } } @@ -666,8 +662,6 @@ func attachFleetAPIRoutes(r *mux.Router, h *fleetHandlers) { r.Handle("/api/v1/osquery/log", h.SubmitLogs).Methods("POST").Name("submit_logs") r.Handle("/api/v1/osquery/carve/begin", h.CarveBegin).Methods("POST").Name("carve_begin") r.Handle("/api/v1/osquery/carve/block", h.CarveBlock).Methods("POST").Name("carve_block") - - r.Handle("/api/v1/fleet/activities", h.ListActivities).Methods("GET").Name("list_activities") } func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kithttp.ServerOption) { @@ -723,6 +717,8 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht e.GET("/api/v1/fleet/queries/run", runLiveQueryEndpoint, runLiveQueryRequest{}) e.PATCH("/api/v1/fleet/invites/{id:[0-9]+}", updateInviteEndpoint, updateInviteRequest{}) + + e.GET("/api/v1/fleet/activities", listActivitiesEndpoint, listActivitiesRequest{}) } // TODO: this duplicates the one in makeKitHandler diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 26f79f1ff0..b73ae18bfe 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -1146,3 +1146,34 @@ func (s *integrationTestSuite) TestHostDetailsPolicies() { // Try to create a team policy with an existing name. s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/teams/%d/policies", team1.ID), tpParams, http.StatusConflict, &tpResp) } + +func (s *integrationTestSuite) TestListActivities() { + t := s.T() + + ctx := context.Background() + u := s.users["admin1@example.com"] + details := make(map[string]interface{}) + + err := s.ds.NewActivity(ctx, &u, fleet.ActivityTypeAppliedSpecPack, &details) + require.NoError(t, err) + + err = s.ds.NewActivity(ctx, &u, fleet.ActivityTypeDeletedPack, &details) + require.NoError(t, err) + + err = s.ds.NewActivity(ctx, &u, fleet.ActivityTypeEditedPack, &details) + require.NoError(t, err) + + var listResp listActivitiesResponse + s.DoJSON("GET", "/api/v1/fleet/activities", nil, http.StatusOK, &listResp, "per_page", "2", "order_key", "id") + require.Len(t, listResp.Activities, 2) + assert.Equal(t, fleet.ActivityTypeAppliedSpecPack, listResp.Activities[0].Type) + assert.Equal(t, fleet.ActivityTypeDeletedPack, listResp.Activities[1].Type) + + s.DoJSON("GET", "/api/v1/fleet/activities", nil, http.StatusOK, &listResp, "per_page", "2", "order_key", "id", "page", "1") + require.Len(t, listResp.Activities, 1) + assert.Equal(t, fleet.ActivityTypeEditedPack, listResp.Activities[0].Type) + + s.DoJSON("GET", "/api/v1/fleet/activities", nil, http.StatusOK, &listResp, "per_page", "1", "order_key", "id", "order_direction", "desc") + require.Len(t, listResp.Activities, 1) + assert.Equal(t, fleet.ActivityTypeEditedPack, listResp.Activities[0].Type) +} diff --git a/server/service/service_activities.go b/server/service/service_activities.go deleted file mode 100644 index ee8fe28ebf..0000000000 --- a/server/service/service_activities.go +++ /dev/null @@ -1,15 +0,0 @@ -package service - -import ( - "context" - - "github.com/fleetdm/fleet/v4/server/fleet" -) - -// ListActivities returns a slice of activities for the whole organization -func (svc *Service) ListActivities(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Activity, error) { - if err := svc.authz.Authorize(ctx, &fleet.Activity{}, fleet.ActionRead); err != nil { - return nil, err - } - return svc.ds.ListActivities(ctx, opt) -} diff --git a/server/service/transport_activities.go b/server/service/transport_activities.go deleted file mode 100644 index f6d06179e8..0000000000 --- a/server/service/transport_activities.go +++ /dev/null @@ -1,14 +0,0 @@ -package service - -import ( - "context" - "net/http" -) - -func decodeListActivitiesRequest(ctx context.Context, r *http.Request) (interface{}, error) { - opt, err := listOptionsFromRequest(r) - if err != nil { - return nil, err - } - return listActivitiesRequest{ListOptions: opt}, nil -}