fix: prevent creating teams with reserved team names (#21727)

> Related issue: #21246

# 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] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Jahziel Villasana-Espinoza 2024-09-05 17:44:09 -04:00 committed by GitHub
parent f186eedceb
commit 557c5d102f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 96 additions and 2 deletions

View file

@ -0,0 +1,2 @@
- Prevents teams with the name "All teams" or "No team" from being created (these are reserved team
names in Fleet).

View file

@ -395,6 +395,27 @@ software:
require.Error(t, err)
assert.Contains(t, err.Error(), "'name' is required")
// reserved team name; should error in both dry run and real
t.Setenv("TEST_TEAM_NAME", "no TEam")
_, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
require.Error(t, err)
assert.Contains(t, err.Error(), `"No team" is a reserved team name`)
t.Setenv("TEST_TEAM_NAME", "no TEam")
_, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name()})
require.Error(t, err)
assert.Contains(t, err.Error(), `"No team" is a reserved team name`)
t.Setenv("TEST_TEAM_NAME", "All teams")
_, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
require.Error(t, err)
assert.Contains(t, err.Error(), `"All teams" is a reserved team name`)
t.Setenv("TEST_TEAM_NAME", "All TEAMS")
_, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name()})
require.Error(t, err)
assert.Contains(t, err.Error(), `"All teams" is a reserved team name`)
// Dry run
t.Setenv("TEST_TEAM_NAME", teamName)
_ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name(), "--dry-run"})

View file

@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server"
@ -73,6 +74,13 @@ func (svc *Service) NewTeam(ctx context.Context, p fleet.TeamPayload) (*fleet.Te
if *p.Name == "" {
return nil, fleet.NewInvalidArgumentError("name", "may not be empty")
}
l := strings.ToLower(*p.Name)
if l == strings.ToLower(fleet.ReservedNameAllTeams) {
return nil, fleet.NewInvalidArgumentError("name", `"All teams" is a reserved team name`)
}
if l == strings.ToLower(fleet.ReservedNameNoTeam) {
return nil, fleet.NewInvalidArgumentError("name", `"No team" is a reserved team name`)
}
team.Name = *p.Name
if p.Description != nil {
@ -129,6 +137,13 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T
if *payload.Name == "" {
return nil, fleet.NewInvalidArgumentError("name", "may not be empty")
}
l := strings.ToLower(*payload.Name)
if l == strings.ToLower(fleet.ReservedNameAllTeams) {
return nil, fleet.NewInvalidArgumentError("name", `"All teams" is a reserved team name`)
}
if l == strings.ToLower(fleet.ReservedNameNoTeam) {
return nil, fleet.NewInvalidArgumentError("name", `"No team" is a reserved team name`)
}
team.Name = *payload.Name
}
if payload.Description != nil {
@ -860,6 +875,14 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
}
}
l := strings.ToLower(spec.Name)
if l == strings.ToLower(fleet.ReservedNameAllTeams) {
return nil, fleet.NewInvalidArgumentError("name", `"All teams" is a reserved team name`)
}
if l == strings.ToLower(fleet.ReservedNameNoTeam) {
return nil, fleet.NewInvalidArgumentError("name", `"No team" is a reserved team name`)
}
var team *fleet.Team
// If filename is provided, try to find the team by filename first.
// This is needed in case user is trying to modify the team name.
@ -883,6 +906,7 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
}
}
}
var create bool
if team == nil {
team, err = svc.ds.TeamByName(ctx, spec.Name)

View file

@ -337,11 +337,18 @@ const TeamDetailsWrapper = ({
setBackendValidators({
name: "A team with this name already exists",
});
} else if (errorObject.base.includes("all teams")) {
setBackendValidators({
name: `"All teams" is a reserved team name. Please try another name.`,
});
} else if (errorObject.base.includes("no team")) {
setBackendValidators({
name: `"No team" is a reserved team name. Please try another name.`,
});
} else {
renderFlash("error", "Could not create team. Please try again.");
}
} finally {
toggleRenameTeamModal();
setIsUpdatingTeams(false);
}
},

View file

@ -116,6 +116,14 @@ const TeamManagementPage = (): JSX.Element => {
setBackendValidators({
name: "A team with this name already exists",
});
} else if (createError.data.errors[0].reason.includes("all teams")) {
setBackendValidators({
name: `"All teams" is a reserved team name. Please try another name.`,
});
} else if (createError.data.errors[0].reason.includes("no team")) {
setBackendValidators({
name: `"No team" is a reserved team name. Please try another name.`,
});
} else {
renderFlash("error", "Could not create team. Please try again.");
toggleCreateTeamModal();
@ -185,6 +193,16 @@ const TeamManagementPage = (): JSX.Element => {
setBackendValidators({
name: "A team with this name already exists",
});
} else if (
updateError.data.errors[0].reason.includes("all teams")
) {
setBackendValidators({
name: `"All teams" is a reserved team name.`,
});
} else if (updateError.data.errors[0].reason.includes("no team")) {
setBackendValidators({
name: `"No team" is a reserved team name. Please try another name.`,
});
} else {
renderFlash(
"error",

View file

@ -1120,6 +1120,20 @@ func (s *integrationEnterpriseTestSuite) TestTeamEndpoints() {
tmResp.Team = nil
s.DoJSON("POST", "/api/latest/fleet/teams", team2, http.StatusConflict, &tmResp)
// create a team with reserved team names; should be case-insensitive
teamReserved := &fleet.Team{
Name: "no TeAm",
Description: "description",
Secrets: []*fleet.EnrollSecret{{Secret: "foobar"}},
}
r := s.Do("POST", "/api/latest/fleet/teams", teamReserved, http.StatusUnprocessableEntity)
require.Contains(t, extractServerErrorText(r.Body), `"No team" is a reserved team name`)
teamReserved.Name = "AlL TeaMS"
r = s.Do("POST", "/api/latest/fleet/teams", teamReserved, http.StatusUnprocessableEntity)
require.Contains(t, extractServerErrorText(r.Body), `"All teams" is a reserved team name`)
// create a team with too many secrets
team3 := &fleet.Team{
Name: name + "lots_of_secrets",
@ -1219,6 +1233,13 @@ func (s *integrationEnterpriseTestSuite) TestTeamEndpoints() {
modifyExpiry.HostExpirySettings.HostExpiryWindow = 0
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", tm1ID), modifyExpiry, http.StatusUnprocessableEntity, &tmResp)
// try to rename to reserved names
r = s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", tm1ID), fleet.TeamPayload{Name: ptr.String("no TEAM")}, http.StatusUnprocessableEntity)
require.Contains(t, extractServerErrorText(r.Body), `"No team" is a reserved team name`)
r = s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", tm1ID), fleet.TeamPayload{Name: ptr.String("ALL teAMs")}, http.StatusUnprocessableEntity)
require.Contains(t, extractServerErrorText(r.Body), `"All teams" is a reserved team name`)
// Modify team's calendar config
modifyCalendar := fleet.TeamPayload{
Integrations: &fleet.TeamIntegrations{

View file

@ -5,11 +5,12 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
"golang.org/x/text/unicode/norm"
"io"
"net/http"
"net/url"
"golang.org/x/text/unicode/norm"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)