2021-09-14 13:58:48 +00:00
package service
import (
"context"
2024-02-22 22:03:13 +00:00
"fmt"
"net/http"
2022-01-26 14:47:56 +00:00
"time"
2021-09-14 13:58:48 +00:00
2024-02-18 13:14:20 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
2021-09-14 13:58:48 +00:00
2024-02-15 20:22:27 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
2021-09-14 13:58:48 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2024-02-15 20:22:27 +00:00
"github.com/fleetdm/fleet/v4/server/ptr"
2021-09-14 13:58:48 +00:00
)
/////////////////////////////////////////////////////////////////////////////////
// List
/////////////////////////////////////////////////////////////////////////////////
type listSoftwareRequest struct {
2021-10-20 21:01:20 +00:00
fleet . SoftwareListOptions
2021-09-14 13:58:48 +00:00
}
2024-10-18 17:38:26 +00:00
// Deprecated: listSoftwareResponse is the response struct for the deprecated
2023-12-06 14:30:49 +00:00
// listSoftwareEndpoint. It differs from listSoftwareVersionsResponse in that
// the latter includes a count of the total number of software items.
2021-09-14 13:58:48 +00:00
type listSoftwareResponse struct {
2022-01-31 22:08:03 +00:00
CountsUpdatedAt * time . Time ` json:"counts_updated_at" `
2022-01-26 14:47:56 +00:00
Software [ ] fleet . Software ` json:"software,omitempty" `
Err error ` json:"error,omitempty" `
2021-09-14 13:58:48 +00:00
}
2025-02-03 17:23:26 +00:00
func ( r listSoftwareResponse ) Error ( ) error { return r . Err }
2021-09-14 13:58:48 +00:00
2024-10-18 17:38:26 +00:00
// Deprecated: use listSoftwareVersionsEndpoint instead
2025-02-14 22:19:34 +00:00
func listSoftwareEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2021-09-14 13:58:48 +00:00
req := request . ( * listSoftwareRequest )
2023-12-12 18:24:20 +00:00
resp , _ , err := svc . ListSoftware ( ctx , req . SoftwareListOptions )
2021-09-14 13:58:48 +00:00
if err != nil {
return listSoftwareResponse { Err : err } , nil
}
2022-01-26 14:47:56 +00:00
2022-05-20 16:58:40 +00:00
// calculate the latest counts_updated_at
2022-01-26 14:47:56 +00:00
var latest time . Time
for _ , sw := range resp {
if ! sw . CountsUpdatedAt . IsZero ( ) && sw . CountsUpdatedAt . After ( latest ) {
latest = sw . CountsUpdatedAt
}
}
2022-01-31 22:08:03 +00:00
listResp := listSoftwareResponse { Software : resp }
if ! latest . IsZero ( ) {
listResp . CountsUpdatedAt = & latest
}
2022-05-20 16:58:40 +00:00
2022-01-31 22:08:03 +00:00
return listResp , nil
2021-09-14 13:58:48 +00:00
}
2023-12-06 14:30:49 +00:00
type listSoftwareVersionsResponse struct {
2023-12-12 18:24:20 +00:00
Count int ` json:"count" `
CountsUpdatedAt * time . Time ` json:"counts_updated_at" `
Software [ ] fleet . Software ` json:"software,omitempty" `
Meta * fleet . PaginationMetadata ` json:"meta" `
Err error ` json:"error,omitempty" `
2023-12-06 14:30:49 +00:00
}
2025-02-03 17:23:26 +00:00
func ( r listSoftwareVersionsResponse ) Error ( ) error { return r . Err }
2023-12-06 14:30:49 +00:00
2025-02-14 22:19:34 +00:00
func listSoftwareVersionsEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2023-12-06 14:30:49 +00:00
req := request . ( * listSoftwareRequest )
2023-12-12 18:24:20 +00:00
// always include pagination for new software versions endpoint (not included by default in
// legacy endpoint for backwards compatibility)
req . SoftwareListOptions . ListOptions . IncludeMetadata = true
resp , meta , err := svc . ListSoftware ( ctx , req . SoftwareListOptions )
2023-12-06 14:30:49 +00:00
if err != nil {
return listSoftwareVersionsResponse { Err : err } , nil
}
// calculate the latest counts_updated_at
var latest time . Time
for _ , sw := range resp {
if ! sw . CountsUpdatedAt . IsZero ( ) && sw . CountsUpdatedAt . After ( latest ) {
latest = sw . CountsUpdatedAt
}
}
2023-12-12 18:24:20 +00:00
listResp := listSoftwareVersionsResponse { Software : resp , Meta : meta }
2023-12-06 14:30:49 +00:00
if ! latest . IsZero ( ) {
listResp . CountsUpdatedAt = & latest
}
c , err := svc . CountSoftware ( ctx , req . SoftwareListOptions )
if err != nil {
return listSoftwareVersionsResponse { Err : err } , nil
}
listResp . Count = c
return listResp , nil
}
2023-12-12 18:24:20 +00:00
func ( svc * Service ) ListSoftware ( ctx context . Context , opt fleet . SoftwareListOptions ) ( [ ] fleet . Software , * fleet . PaginationMetadata , error ) {
2022-04-18 17:27:30 +00:00
if err := svc . authz . Authorize ( ctx , & fleet . AuthzSoftwareInventory {
TeamID : opt . TeamID ,
} , fleet . ActionRead ) ; err != nil {
2023-12-12 18:24:20 +00:00
return nil , nil , err
2021-09-14 13:58:48 +00:00
}
2024-08-15 18:36:47 +00:00
// Vulnerability filters are only available in premium (opt.IncludeCVEScores is only true in premium)
2024-12-13 22:39:21 +00:00
lic , err := svc . License ( ctx )
if err != nil {
return nil , nil , err
}
if ! lic . IsPremium ( ) && ( opt . MaximumCVSS > 0 || opt . MinimumCVSS > 0 || opt . KnownExploit ) {
2024-08-15 18:36:47 +00:00
return nil , nil , fleet . ErrMissingLicense
}
2022-01-26 14:47:56 +00:00
// default sort order to hosts_count descending
2023-11-01 14:56:27 +00:00
if opt . ListOptions . OrderKey == "" {
opt . ListOptions . OrderKey = "hosts_count"
opt . ListOptions . OrderDirection = fleet . OrderDescending
2022-01-26 14:47:56 +00:00
}
opt . WithHostCounts = true
2022-05-20 16:58:40 +00:00
2023-12-12 18:24:20 +00:00
softwares , meta , err := svc . ds . ListSoftware ( ctx , opt )
2022-05-20 16:58:40 +00:00
if err != nil {
2023-12-12 18:24:20 +00:00
return nil , nil , err
2022-05-20 16:58:40 +00:00
}
2023-12-12 18:24:20 +00:00
return softwares , meta , nil
2022-05-20 16:58:40 +00:00
}
/////////////////////////////////////////////////////////////////////////////////
// Get Software
/////////////////////////////////////////////////////////////////////////////////
type getSoftwareRequest struct {
2024-02-18 13:14:20 +00:00
ID uint ` url:"id" `
TeamID * uint ` query:"team_id,optional" `
2022-05-20 16:58:40 +00:00
}
type getSoftwareResponse struct {
Software * fleet . Software ` json:"software,omitempty" `
Err error ` json:"error,omitempty" `
}
2025-02-03 17:23:26 +00:00
func ( r getSoftwareResponse ) Error ( ) error { return r . Err }
2022-05-20 16:58:40 +00:00
2025-02-14 22:19:34 +00:00
func getSoftwareEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2022-05-20 16:58:40 +00:00
req := request . ( * getSoftwareRequest )
2024-02-18 13:14:20 +00:00
software , err := svc . SoftwareByID ( ctx , req . ID , req . TeamID , false )
2022-05-20 16:58:40 +00:00
if err != nil {
return getSoftwareResponse { Err : err } , nil
}
return getSoftwareResponse { Software : software } , nil
}
2024-02-18 13:14:20 +00:00
func ( svc * Service ) SoftwareByID ( ctx context . Context , id uint , teamID * uint , includeCVEScores bool ) ( * fleet . Software , error ) {
2024-02-22 22:03:13 +00:00
if err := svc . authz . Authorize ( ctx , & fleet . Host { TeamID : teamID } , fleet . ActionList ) ; err != nil {
2022-05-20 16:58:40 +00:00
return nil , err
}
2024-07-30 17:19:05 +00:00
if teamID != nil && * teamID > 0 {
2024-02-22 22:03:13 +00:00
// This auth check ensures we return 403 if the user doesn't have access to the team
if err := svc . authz . Authorize ( ctx , & fleet . AuthzSoftwareInventory { TeamID : teamID } , fleet . ActionRead ) ; err != nil {
return nil , err
}
2024-02-18 13:14:20 +00:00
exists , err := svc . ds . TeamExists ( ctx , * teamID )
if err != nil {
return nil , ctxerr . Wrap ( ctx , err , "checking if team exists" )
} else if ! exists {
2024-02-22 22:03:13 +00:00
return nil , fleet . NewInvalidArgumentError ( "team_id" , fmt . Sprintf ( "team %d does not exist" , * teamID ) ) .
WithStatus ( http . StatusNotFound )
2024-02-18 13:14:20 +00:00
}
}
2024-02-15 20:22:27 +00:00
vc , ok := viewer . FromContext ( ctx )
if ! ok {
return nil , fleet . ErrNoContext
}
2024-02-21 18:42:21 +00:00
software , err := svc . ds . SoftwareByID ( ctx , id , teamID , includeCVEScores , & fleet . TeamFilter {
2024-02-15 20:22:27 +00:00
User : vc . User ,
IncludeObserver : true ,
} )
2022-05-20 16:58:40 +00:00
if err != nil {
2024-02-22 22:03:13 +00:00
if fleet . IsNotFound ( err ) && teamID == nil {
2024-02-15 20:22:27 +00:00
// here we use a global admin as filter because we want
// to check if the software version exists
filter := fleet . TeamFilter { User : & fleet . User { GlobalRole : ptr . String ( fleet . RoleAdmin ) } }
2024-02-21 18:42:21 +00:00
if _ , err = svc . ds . SoftwareByID ( ctx , id , teamID , includeCVEScores , & filter ) ; err != nil {
2024-02-15 20:22:27 +00:00
return nil , ctxerr . Wrap ( ctx , err , "checked using a global admin" )
}
return nil , fleet . NewPermissionError ( "Error: You don’ t have permission to view specified software. It is installed on hosts that belong to team you don’ t have permissions to view." )
}
return nil , ctxerr . Wrap ( ctx , err , "getting software version by id" )
2022-05-20 16:58:40 +00:00
}
return software , nil
2021-09-14 13:58:48 +00:00
}
2021-12-03 13:54:17 +00:00
/////////////////////////////////////////////////////////////////////////////////
// Count
/////////////////////////////////////////////////////////////////////////////////
type countSoftwareRequest struct {
fleet . SoftwareListOptions
}
type countSoftwareResponse struct {
Count int ` json:"count" `
Err error ` json:"error,omitempty" `
}
2025-02-03 17:23:26 +00:00
func ( r countSoftwareResponse ) Error ( ) error { return r . Err }
2021-12-03 13:54:17 +00:00
2024-10-18 17:38:26 +00:00
// Deprecated: counts are now included directly in the listSoftwareVersionsResponse. This
2023-12-06 14:30:49 +00:00
// endpoint is retained for backwards compatibility.
2025-02-14 22:19:34 +00:00
func countSoftwareEndpoint ( ctx context . Context , request interface { } , svc fleet . Service ) ( fleet . Errorer , error ) {
2021-12-03 13:54:17 +00:00
req := request . ( * countSoftwareRequest )
count , err := svc . CountSoftware ( ctx , req . SoftwareListOptions )
if err != nil {
return countSoftwareResponse { Err : err } , nil
}
return countSoftwareResponse { Count : count } , nil
}
func ( svc Service ) CountSoftware ( ctx context . Context , opt fleet . SoftwareListOptions ) ( int , error ) {
2022-04-18 17:27:30 +00:00
if err := svc . authz . Authorize ( ctx , & fleet . AuthzSoftwareInventory {
TeamID : opt . TeamID ,
} , fleet . ActionRead ) ; err != nil {
2021-12-03 13:54:17 +00:00
return 0 , err
}
2024-08-15 18:36:47 +00:00
lic , err := svc . License ( ctx )
if err != nil {
return 0 , ctxerr . Wrap ( ctx , err , "get license" )
}
// Vulnerability filters are only available in premium
if ! lic . IsPremium ( ) && ( opt . MaximumCVSS > 0 || opt . MinimumCVSS > 0 || opt . KnownExploit ) {
return 0 , fleet . ErrMissingLicense
}
2024-08-19 22:55:59 +00:00
// required for vulnerability filters
if lic . IsPremium ( ) {
opt . IncludeCVEScores = true
}
2021-12-03 13:54:17 +00:00
return svc . ds . CountSoftware ( ctx , opt )
}