Allow team admins/maintainers to view Fleet maintained apps (#24516)

For #23305.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#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
This commit is contained in:
Ian Littman 2024-12-09 08:29:08 -06:00 committed by GitHub
parent 79565cba44
commit 4af18cd136
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 102 additions and 3 deletions

View file

@ -0,0 +1 @@
* Fixed missing read permission for team maintainers and admins on Fleet maintained apps

View file

@ -175,9 +175,9 @@ func (svc *Service) ListFleetMaintainedApps(ctx context.Context, teamID uint, op
}
func (svc *Service) GetFleetMaintainedApp(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) {
if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{
TeamID: nil,
}, fleet.ActionRead); err != nil {
// Special case auth for maintained apps (vs. normal installers) as maintained apps are not scoped to a team;
// use SoftwareInstaller for authorization elsewhere.
if err := svc.authz.Authorize(ctx, &fleet.MaintainedApp{}, fleet.ActionRead); err != nil {
return nil, err
}

View file

@ -0,0 +1,79 @@
package service
import (
"context"
"testing"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/stretchr/testify/require"
)
func TestGetMaintainedAppAuth(t *testing.T) {
t.Parallel()
ds := new(mock.Store)
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.GetMaintainedAppByIDFunc = func(ctx context.Context, appID uint) (*fleet.MaintainedApp, error) {
return &fleet.MaintainedApp{}, nil
}
authorizer, err := authz.NewAuthorizer()
require.NoError(t, err)
svc := &Service{authz: authorizer, ds: ds}
testCases := []struct {
name string
user *fleet.User
shouldFail bool
}{
{
"global admin",
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
false,
},
{
"global maintainer",
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
false,
},
{
"global observer",
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
true,
},
{
"team admin",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
false,
},
{
"team maintainer",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
false,
},
{
"team observer",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
true,
},
}
var forbiddenError *authz.Forbidden
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
_, err := svc.GetFleetMaintainedApp(ctx, 123)
if tt.shouldFail {
require.Error(t, err)
require.ErrorAs(t, err, &forbiddenError)
} else {
require.NoError(t, err)
}
})
}
}

View file

@ -643,6 +643,20 @@ allow {
action == read
}
# Global admins and maintainers can read all maintained apps.
allow {
object.type == "maintained_app"
subject.global_role == [admin, maintainer][_]
action == read
}
# Team admins and maintainers can read all maintained apps (no team constraint, unlike installers)
allow {
object.type == "maintained_app"
team_role(subject, subject.teams[_].id) == [admin, maintainer][_]
action == read
}
# Global admins and maintainers can read any installable entity (software installer or VPP app)
allow {
object.type == "installable_entity"

View file

@ -22,3 +22,8 @@ type MaintainedApp struct {
// UpdatedAt is the timestamp when the fleet maintained app data was last updated.
UpdatedAt *time.Time `json:"-" db:"updated_at"`
}
// AuthzType implements authz.AuthzTyper.
func (s *MaintainedApp) AuthzType() string {
return "maintained_app"
}