mirror of https://github.com/grafana/grafana.git
SCIM - Allow groups to be deleted if SCIM group sync is disabled (#111888)
* Load SCIM configuration before running changes in teams * Update tests * Allow to delete groups if SCIM group sync is disabled * Update docs
This commit is contained in:
parent
9bde3267bb
commit
f01b1131e7
|
@ -223,7 +223,7 @@ Team provisioning requires `group_sync_enabled = true` in the SCIM configuration
|
|||
{{< /admonition >}}
|
||||
|
||||
{{< admonition type="warning" >}}
|
||||
Teams provisioned through SCIM cannot be deleted manually from Grafana - they can only be deleted by removing their corresponding groups from the identity provider.
|
||||
Teams provisioned through SCIM cannot be deleted manually from Grafana - they can only be deleted by removing their corresponding groups from the identity provider. Optionally, you can disable SCIM group sync to allow manual deletion of teams.
|
||||
{{< /admonition >}}
|
||||
|
||||
For detailed configuration steps specific to the identity provider, see:
|
||||
|
|
|
@ -431,7 +431,8 @@ func (tapi *TeamAPI) validateTeam(c *contextmodel.ReqContext, teamID int64, prov
|
|||
return response.Error(http.StatusInternalServerError, "Failed to get Team", err)
|
||||
}
|
||||
|
||||
if teamDTO.IsProvisioned {
|
||||
isGroupSyncEnabled := tapi.cfg.Raw.Section("auth.scim").Key("group_sync_enabled").MustBool(false)
|
||||
if isGroupSyncEnabled && teamDTO.IsProvisioned {
|
||||
return response.Error(http.StatusBadRequest, provisionedMessage, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -299,7 +299,8 @@ func (tapi *TeamAPI) removeTeamMember(c *contextmodel.ReqContext) response.Respo
|
|||
return response.Error(http.StatusInternalServerError, "Failed to get Team", err)
|
||||
}
|
||||
|
||||
if existingTeam.IsProvisioned {
|
||||
isGroupSyncEnabled := tapi.cfg.Raw.Section("auth.scim").Key("group_sync_enabled").MustBool(false)
|
||||
if isGroupSyncEnabled && existingTeam.IsProvisioned {
|
||||
return response.Error(http.StatusBadRequest, "Team memberships cannot be updated for provisioned teams", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -169,13 +169,15 @@ func TestUpdateTeamMembersAPIEndpoint(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestUpdateTeamMembersFromProvisionedTeam(t *testing.T) {
|
||||
func TestUpdateTeamMembersFromProvisionedTeamWhenGroupSyncIsEnabled(t *testing.T) {
|
||||
server := SetupAPITestServer(t, &teamtest.FakeService{
|
||||
ExpectedIsMember: true,
|
||||
ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001", IsProvisioned: true},
|
||||
}, func(tapi *TeamAPI) {
|
||||
tapi.cfg.Raw.Section("auth.scim").Key("group_sync_enabled").SetValue("true")
|
||||
})
|
||||
|
||||
t.Run("should not be able to update team member from a provisioned team", func(t *testing.T) {
|
||||
t.Run("should not be able to update team member from a provisioned team if team sync is enabled", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewRequest(http.MethodPut, "/api/teams/1/members/1", strings.NewReader("{\"permission\": 1}")),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
|
||||
|
@ -186,7 +188,7 @@ func TestUpdateTeamMembersFromProvisionedTeam(t *testing.T) {
|
|||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
|
||||
t.Run("should not be able to update team member from a provisioned team by team UID", func(t *testing.T) {
|
||||
t.Run("should not be able to update team member from a provisioned team by team UID if team sync is enabled", func(t *testing.T) {
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewRequest(http.MethodPut, "/api/teams/a00001/members/1", strings.NewReader("{\"permission\": 1}")),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
|
||||
|
@ -198,6 +200,27 @@ func TestUpdateTeamMembersFromProvisionedTeam(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestUpdateTeamMembersFromProvisionedTeamWhenGroupSyncIsDisabled(t *testing.T) {
|
||||
t.Run("should be able to delete team member from a provisioned team when SCIM group sync is disabled", func(t *testing.T) {
|
||||
server := SetupAPITestServer(t, nil, func(hs *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{
|
||||
ExpectedIsMember: true,
|
||||
ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001", IsProvisioned: true},
|
||||
}
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
})
|
||||
|
||||
req := webtest.RequestWithSignedInUser(
|
||||
server.NewRequest(http.MethodDelete, "/api/teams/1/members/1", nil),
|
||||
authedUserWithPermissions(1, 1, []accesscontrol.Permission{{Action: accesscontrol.ActionTeamsPermissionsWrite, Scope: "teams:id:1"}}),
|
||||
)
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteTeamMembersAPIEndpoint(t *testing.T) {
|
||||
server := SetupAPITestServer(t, nil, func(hs *TeamAPI) {
|
||||
hs.teamService = &teamtest.FakeService{
|
||||
|
@ -236,6 +259,8 @@ func TestDeleteTeamMembersFromProvisionedTeam(t *testing.T) {
|
|||
ExpectedTeamDTO: &team.TeamDTO{ID: 1, UID: "a00001", IsProvisioned: true},
|
||||
}
|
||||
hs.teamPermissionsService = &actest.FakePermissionsService{}
|
||||
}, func(hs *TeamAPI) {
|
||||
hs.cfg.Raw.Section("auth.scim").Key("group_sync_enabled").SetValue("true")
|
||||
})
|
||||
|
||||
t.Run("should not be able to delete team member from a provisioned team", func(t *testing.T) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { connect, ConnectedProps } from 'react-redux';
|
|||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config, getBackendSrv } from '@grafana/runtime';
|
||||
import {
|
||||
Avatar,
|
||||
CellProps,
|
||||
|
@ -65,6 +66,7 @@ export const TeamList = ({
|
|||
changeSort,
|
||||
}: Props) => {
|
||||
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
||||
const [scimGroupSyncEnabled, setScimGroupSyncEnabled] = useState(false);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -77,6 +79,25 @@ export const TeamList = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const checkSCIMSettings = async () => {
|
||||
if (!config.featureToggles.enableSCIM) {
|
||||
setScimGroupSyncEnabled(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const scimSettings = await getBackendSrv().get(
|
||||
`/apis/scim.grafana.app/v0alpha1/namespaces/${config.namespace}/config`
|
||||
);
|
||||
setScimGroupSyncEnabled(scimSettings?.items[0]?.spec?.enableGroupSync || false);
|
||||
} catch {
|
||||
setScimGroupSyncEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkSCIMSettings();
|
||||
}, []);
|
||||
|
||||
const canCreate = contextSrv.hasPermission(AccessControlAction.ActionTeamsCreate);
|
||||
const displayRolePicker = shouldDisplayRolePicker();
|
||||
|
||||
|
@ -198,7 +219,7 @@ export const TeamList = ({
|
|||
const canReadTeam = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsRead, original);
|
||||
const canDelete =
|
||||
contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsDelete, original) &&
|
||||
!original.isProvisioned;
|
||||
(!scimGroupSyncEnabled || !original.isProvisioned);
|
||||
return (
|
||||
<Stack direction="row" justifyContent="flex-end" gap={2}>
|
||||
{canReadTeam && (
|
||||
|
@ -226,7 +247,7 @@ export const TeamList = ({
|
|||
},
|
||||
},
|
||||
],
|
||||
[displayRolePicker, hasFetched, rolesLoading, roleOptions, deleteTeam, styles]
|
||||
[displayRolePicker, hasFetched, rolesLoading, roleOptions, deleteTeam, styles, scimGroupSyncEnabled]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue