mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
relates to #16052 This adds a team permission check the `GET software/titles/:id` endpoint. If the user should not be able to get the software title if it is not on a host that is on the same team as the user (e.g. software title 1 is on host 1, which is on team 1. A user who is only on team 2 should get a 403 response) The UI is also updated to show the access denied error page when the we receive a 403 response for the software title <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Roberto Dip <dip.jesusr@gmail.com> Co-authored-by: Roberto Dip <me@roperzh.com>
154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"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"`
|
||
}
|
||
|
||
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)
|
||
if err != nil {
|
||
return getSoftwareTitleResponse{Err: err}, nil
|
||
}
|
||
|
||
return getSoftwareTitleResponse{SoftwareTitle: software}, nil
|
||
}
|
||
|
||
func (svc *Service) SoftwareTitleByID(ctx context.Context, id uint) (*fleet.SoftwareTitle, error) {
|
||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
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, fleet.TeamFilter{
|
||
User: vc.User,
|
||
IncludeObserver: true,
|
||
})
|
||
if err != nil {
|
||
if fleet.IsNotFound((err)) {
|
||
// 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, filter)
|
||
if err != nil {
|
||
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 title by id")
|
||
}
|
||
|
||
return software, nil
|
||
}
|