fleet/server/service/software_titles.go
Victor Lyuboslavsky f5f0797083
Fixing tests. (#17073)
Fixed failing tests after recent merge with main.
Also includes updated migration date.
2024-02-22 16:03:13 -06:00

172 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"fmt"
"net/http"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
)
/////////////////////////////////////////////////////////////////////////////////
// List Software Titles
/////////////////////////////////////////////////////////////////////////////////
type listSoftwareTitlesRequest struct {
fleet.SoftwareTitleListOptions
}
type listSoftwareTitlesResponse struct {
Meta *fleet.PaginationMetadata `json:"meta"`
Count int `json:"count"`
CountsUpdatedAt *time.Time `json:"counts_updated_at"`
SoftwareTitles []fleet.SoftwareTitle `json:"software_titles,omitempty"`
Err error `json:"error,omitempty"`
}
func (r listSoftwareTitlesResponse) error() error { return r.Err }
func listSoftwareTitlesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*listSoftwareTitlesRequest)
titles, count, meta, err := svc.ListSoftwareTitles(ctx, req.SoftwareTitleListOptions)
if err != nil {
return listSoftwareTitlesResponse{Err: err}, nil
}
var latest time.Time
for _, sw := range titles {
if !sw.CountsUpdatedAt.IsZero() && sw.CountsUpdatedAt.After(latest) {
latest = sw.CountsUpdatedAt
}
}
listResp := listSoftwareTitlesResponse{
SoftwareTitles: titles,
Count: count,
Meta: meta,
}
if !latest.IsZero() {
listResp.CountsUpdatedAt = &latest
}
return listResp, nil
}
func (svc *Service) ListSoftwareTitles(
ctx context.Context,
opt fleet.SoftwareTitleListOptions,
) ([]fleet.SoftwareTitle, int, *fleet.PaginationMetadata, error) {
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{
TeamID: opt.TeamID,
}, fleet.ActionRead); err != nil {
return nil, 0, nil, err
}
if opt.TeamID != nil && *opt.TeamID != 0 {
lic, err := svc.License(ctx)
if err != nil {
return nil, 0, nil, ctxerr.Wrap(ctx, err, "get license")
}
if !lic.IsPremium() {
return nil, 0, nil, fleet.ErrMissingLicense
}
}
// always include metadata for software titles
opt.ListOptions.IncludeMetadata = true
// cursor-based pagination is not supported for software titles
opt.ListOptions.After = ""
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, 0, nil, fleet.ErrNoContext
}
titles, count, meta, err := svc.ds.ListSoftwareTitles(ctx, opt, fleet.TeamFilter{
User: vc.User,
IncludeObserver: true,
TeamID: opt.TeamID,
})
if err != nil {
return nil, 0, nil, err
}
return titles, count, meta, nil
}
/////////////////////////////////////////////////////////////////////////////////
// Get a Software Title
/////////////////////////////////////////////////////////////////////////////////
type getSoftwareTitleRequest struct {
ID uint `url:"id"`
TeamID *uint `query:"team_id,optional"`
}
type getSoftwareTitleResponse struct {
SoftwareTitle *fleet.SoftwareTitle `json:"software_title,omitempty"`
Err error `json:"error,omitempty"`
}
func (r getSoftwareTitleResponse) error() error { return r.Err }
func getSoftwareTitleEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*getSoftwareTitleRequest)
software, err := svc.SoftwareTitleByID(ctx, req.ID, req.TeamID)
if err != nil {
return getSoftwareTitleResponse{Err: err}, nil
}
return getSoftwareTitleResponse{SoftwareTitle: software}, nil
}
func (svc *Service) SoftwareTitleByID(ctx context.Context, id uint, teamID *uint) (*fleet.SoftwareTitle, error) {
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionList); err != nil {
return nil, err
}
if teamID != nil {
// 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
}
exists, err := svc.ds.TeamExists(ctx, *teamID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "checking if team exists")
} else if !exists {
return nil, fleet.NewInvalidArgumentError("team_id", fmt.Sprintf("team %d does not exist", *teamID)).
WithStatus(http.StatusNotFound)
}
}
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, fleet.ErrNoContext
}
// get software by id including team_id data from software_title_host_counts
software, err := svc.ds.SoftwareTitleByID(ctx, id, teamID, fleet.TeamFilter{
User: vc.User,
IncludeObserver: true,
})
if err != nil {
if fleet.IsNotFound(err) && teamID == nil {
// here we use a global admin as filter because we want to check if the software exists
filter := fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}}
_, err = svc.ds.SoftwareTitleByID(ctx, id, nil, filter)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "checked using a global admin")
}
return nil, fleet.NewPermissionError("Error: You dont have permission to view specified software. It is installed on hosts that belong to team you dont have permissions to view.")
}
return nil, ctxerr.Wrap(ctx, err, "getting software title by id")
}
return software, nil
}