mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
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:
parent
f186eedceb
commit
557c5d102f
7 changed files with 96 additions and 2 deletions
2
changes/21264-fix-reserved-team-names
Normal file
2
changes/21264-fix-reserved-team-names
Normal 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).
|
||||
|
|
@ -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"})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue