mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Allow team maintainers to delete hosts from their teams (#2373)
This commit is contained in:
parent
b2cae6321d
commit
ddc6b300d4
5 changed files with 160 additions and 5 deletions
1
changes/issue-2357-team-maintainer-can-delete-host
Normal file
1
changes/issue-2357-team-maintainer-can-delete-host
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Team maintainers can delete hosts from their teams.
|
||||
|
|
@ -209,6 +209,13 @@ allow {
|
|||
action == read
|
||||
}
|
||||
|
||||
# Team maintainers can write to hosts of their own team
|
||||
allow {
|
||||
object.type == "host"
|
||||
team_role(subject, object.team_id) == maintainer
|
||||
action == write
|
||||
}
|
||||
|
||||
##
|
||||
# Labels
|
||||
##
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ func TestAuthorizeHost(t *testing.T) {
|
|||
{user: teamMaintainer, object: host, action: write, allow: false},
|
||||
{user: teamMaintainer, object: host, action: list, allow: true},
|
||||
{user: teamMaintainer, object: hostTeam1, action: read, allow: true},
|
||||
{user: teamMaintainer, object: hostTeam1, action: write, allow: false},
|
||||
{user: teamMaintainer, object: hostTeam1, action: write, allow: true},
|
||||
{user: teamMaintainer, object: hostTeam2, action: read, allow: false},
|
||||
{user: teamMaintainer, object: hostTeam2, action: write, allow: false},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func (svc Service) GetHost(ctx context.Context, id uint) (*fleet.HostDetail, err
|
|||
}
|
||||
|
||||
func (svc Service) HostByIdentifier(ctx context.Context, identifier string) (*fleet.HostDetail, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionRead); err != nil {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ func (svc Service) GetHostSummary(ctx context.Context) (*fleet.HostSummary, erro
|
|||
}
|
||||
|
||||
func (svc Service) DeleteHost(ctx context.Context, id uint) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionWrite); err != nil {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ func (svc Service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []u
|
|||
// besides global admins permissions to modify team hosts, we will need to
|
||||
// check that the user has permissions for both the source and destination
|
||||
// teams.
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionWrite); err != nil {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt
|
|||
// besides global admins permissions to modify team hosts, we will need to
|
||||
// check that the user has permissions for both the source and destination
|
||||
// teams.
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionWrite); err != nil {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
hostIDs, err := svc.hostIDsFromFilters(ctx, opt, lid)
|
||||
|
|
@ -194,6 +194,10 @@ func (svc Service) hostIDsFromFilters(ctx context.Context, opt fleet.HostListOpt
|
|||
}
|
||||
|
||||
func (svc *Service) RefetchHost(ctx context.Context, id uint) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, err := svc.ds.Host(ctx, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "find host for refetch")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/WatchBeam/clock"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
|
|
@ -210,3 +211,145 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) {
|
|||
|
||||
require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(test.UserAdmin), nil, fleet.HostListOptions{}, nil))
|
||||
}
|
||||
|
||||
func TestHostAuth(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc := newTestService(ds, nil, nil)
|
||||
|
||||
teamHost := &fleet.Host{TeamID: ptr.Uint(1)}
|
||||
globalHost := &fleet.Host{}
|
||||
|
||||
ds.DeleteHostFunc = func(ctx context.Context, hid uint) error {
|
||||
return nil
|
||||
}
|
||||
ds.HostFunc = func(ctx context.Context, id uint) (*fleet.Host, error) {
|
||||
if id == 1 {
|
||||
return teamHost, nil
|
||||
}
|
||||
return globalHost, nil
|
||||
}
|
||||
ds.HostByIdentifierFunc = func(ctx context.Context, identifier string) (*fleet.Host, error) {
|
||||
if identifier == "1" {
|
||||
return teamHost, nil
|
||||
}
|
||||
return globalHost, nil
|
||||
}
|
||||
ds.ListHostsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.HostListOptions) ([]*fleet.Host, error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.LoadHostSoftwareFunc = func(ctx context.Context, host *fleet.Host) error {
|
||||
return nil
|
||||
}
|
||||
ds.ListLabelsForHostFunc = func(ctx context.Context, hid uint) ([]*fleet.Label, error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.ListPacksForHostFunc = func(ctx context.Context, hid uint) (packs []*fleet.Pack, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
ds.AddHostsToTeamFunc = func(ctx context.Context, teamID *uint, hostIDs []uint) error {
|
||||
return nil
|
||||
}
|
||||
ds.SaveHostFunc = func(ctx context.Context, host *fleet.Host) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
user *fleet.User
|
||||
shouldFailGlobalWrite bool
|
||||
shouldFailGlobalRead bool
|
||||
shouldFailTeamWrite bool
|
||||
shouldFailTeamRead bool
|
||||
}{
|
||||
{
|
||||
"global admin",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global maintainer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global observer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team maintainer, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team observer, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team maintainer, DOES NOT belong to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleMaintainer}}},
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"team observer, DOES NOT belong to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleObserver}}},
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
||||
|
||||
_, err := svc.GetHost(ctx, 1)
|
||||
checkAuthErr(t, tt.shouldFailTeamRead, err)
|
||||
|
||||
_, err = svc.HostByIdentifier(ctx, "1")
|
||||
checkAuthErr(t, tt.shouldFailTeamRead, err)
|
||||
|
||||
_, err = svc.GetHost(ctx, 2)
|
||||
checkAuthErr(t, tt.shouldFailGlobalRead, err)
|
||||
|
||||
_, err = svc.HostByIdentifier(ctx, "2")
|
||||
checkAuthErr(t, tt.shouldFailGlobalRead, err)
|
||||
|
||||
err = svc.DeleteHost(ctx, 1)
|
||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||
|
||||
err = svc.DeleteHost(ctx, 2)
|
||||
checkAuthErr(t, tt.shouldFailGlobalWrite, err)
|
||||
|
||||
err = svc.AddHostsToTeam(ctx, ptr.Uint(1), []uint{1})
|
||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||
|
||||
err = svc.AddHostsToTeamByFilter(ctx, ptr.Uint(1), fleet.HostListOptions{}, nil)
|
||||
checkAuthErr(t, tt.shouldFailTeamWrite, err)
|
||||
|
||||
err = svc.RefetchHost(ctx, 1)
|
||||
checkAuthErr(t, tt.shouldFailTeamRead, err)
|
||||
})
|
||||
}
|
||||
|
||||
// List, GetHostSummary, FlushSeenHost work for all
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue