Group count and PartialExport permission fixes
Closes https://github.com/keycloak/keycloak/issues/12171
This commit is contained in:
parent
c23e1e0e2b
commit
7deb4ca545
4 changed files with 25 additions and 29 deletions
|
@ -135,3 +135,6 @@ Stream<GroupModel> getTopLevelGroupsStream(RealmModel realm,
|
||||||
|
|
||||||
Endpoint `GET {keycloak server}/realms/{realm}/groups/{group_id}/children` added as a way to get subgroups of specific groups that support pagination
|
Endpoint `GET {keycloak server}/realms/{realm}/groups/{group_id}/children` added as a way to get subgroups of specific groups that support pagination
|
||||||
|
|
||||||
|
= Partial export requires manage-realm permission
|
||||||
|
|
||||||
|
The endpoint `POST {keycloak server}/realms/{realm}/partial-export` and the corresponding action in the admin console now require `manage-realm` permission for execution instead of `view-realm`. This endpoint exports the realm configuration into a JSON file and the new permission is more appropriate. The parameters `exportGroupsAndRoles` and `exportClients`, which include the realm groups/roles and clients in the export respectively, continue managing the same permissions (`query-groups` and `view-clients`).
|
|
@ -13,6 +13,7 @@ import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useAccess } from "../context/access/Access";
|
||||||
|
|
||||||
import { adminClient } from "../admin-client";
|
import { adminClient } from "../admin-client";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
@ -71,6 +72,9 @@ const RealmSettingsHeader = ({
|
||||||
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
||||||
const [partialExportOpen, setPartialExportOpen] = useState(false);
|
const [partialExportOpen, setPartialExportOpen] = useState(false);
|
||||||
|
|
||||||
|
const { hasAccess } = useAccess();
|
||||||
|
const canManageRealm = hasAccess("manage-realm");
|
||||||
|
|
||||||
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
|
||||||
titleKey: "disableConfirmTitle",
|
titleKey: "disableConfirmTitle",
|
||||||
messageKey: "disableConfirmRealm",
|
messageKey: "disableConfirmRealm",
|
||||||
|
@ -120,6 +124,7 @@ const RealmSettingsHeader = ({
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="import"
|
key="import"
|
||||||
data-testid="openPartialImportModal"
|
data-testid="openPartialImportModal"
|
||||||
|
isDisabled={!canManageRealm}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPartialImportOpen(true);
|
setPartialImportOpen(true);
|
||||||
}}
|
}}
|
||||||
|
@ -129,16 +134,22 @@ const RealmSettingsHeader = ({
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="export"
|
key="export"
|
||||||
data-testid="openPartialExportModal"
|
data-testid="openPartialExportModal"
|
||||||
|
isDisabled={!canManageRealm}
|
||||||
onClick={() => setPartialExportOpen(true)}
|
onClick={() => setPartialExportOpen(true)}
|
||||||
>
|
>
|
||||||
{t("partialExport")}
|
{t("partialExport")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
<DropdownSeparator key="separator" />,
|
<DropdownSeparator key="separator" />,
|
||||||
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
|
<DropdownItem
|
||||||
|
key="delete"
|
||||||
|
isDisabled={!canManageRealm}
|
||||||
|
onClick={toggleDeleteDialog}
|
||||||
|
>
|
||||||
{t("delete")}
|
{t("delete")}
|
||||||
</DropdownItem>,
|
</DropdownItem>,
|
||||||
]}
|
]}
|
||||||
isEnabled={value}
|
isEnabled={value}
|
||||||
|
isReadOnly={!canManageRealm}
|
||||||
onToggle={(value) => {
|
onToggle={(value) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
toggleDisableDialog();
|
toggleDisableDialog();
|
||||||
|
@ -181,7 +192,7 @@ export const RealmSettingsTabs = ({
|
||||||
convertToFormValues(r, setValue);
|
convertToFormValues(r, setValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(setupForm, []);
|
useEffect(setupForm, [setValue, realm]);
|
||||||
|
|
||||||
const save = async (r: RealmRepresentation) => {
|
const save = async (r: RealmRepresentation) => {
|
||||||
r = convertFormValuesToObject(r);
|
r = convertFormValuesToObject(r);
|
||||||
|
|
|
@ -1160,7 +1160,7 @@ public class RealmAdminResource {
|
||||||
@Operation( summary = "Partial export of existing realm into a JSON file.")
|
@Operation( summary = "Partial export of existing realm into a JSON file.")
|
||||||
public Response partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
|
public Response partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
|
||||||
@QueryParam("exportClients") Boolean exportClients) {
|
@QueryParam("exportClients") Boolean exportClients) {
|
||||||
auth.realm().requireViewRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
boolean groupsAndRolesExported = exportGroupsAndRoles != null && exportGroupsAndRoles;
|
boolean groupsAndRolesExported = exportGroupsAndRoles != null && exportGroupsAndRoles;
|
||||||
boolean clientsExported = exportClients != null && exportClients;
|
boolean clientsExported = exportClients != null && exportClients;
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
builder.user(UserBuilder.create()
|
builder.user(UserBuilder.create()
|
||||||
.username("multi")
|
.username("multi")
|
||||||
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.QUERY_GROUPS)
|
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.QUERY_GROUPS)
|
||||||
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_REALM)
|
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.MANAGE_REALM)
|
||||||
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_CLIENTS)
|
.role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_CLIENTS)
|
||||||
.addPassword("password"));
|
.addPassword("password"));
|
||||||
|
|
||||||
|
@ -1345,6 +1345,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
realm.groups().groups();
|
realm.groups().groups();
|
||||||
}
|
}
|
||||||
}, Resource.USER, false);
|
}, Resource.USER, false);
|
||||||
|
invoke(realm -> realm.groups().count(), Resource.USER, false);
|
||||||
invoke(new InvocationWithResponse() {
|
invoke(new InvocationWithResponse() {
|
||||||
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
||||||
GroupRepresentation group = new GroupRepresentation();
|
GroupRepresentation group = new GroupRepresentation();
|
||||||
|
@ -1800,31 +1801,12 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
// re-enable as part of https://github.com/keycloak/keycloak/issues/14291
|
// re-enable as part of https://github.com/keycloak/keycloak/issues/14291
|
||||||
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE);
|
||||||
|
|
||||||
invoke(new Invocation() {
|
invoke(realm -> realm.partialExport(false, false), clients.get("view-realm"), false);
|
||||||
public void invoke(RealmResource realm) {
|
invoke(realm -> realm.partialExport(false, false), clients.get("manage-realm"), true);
|
||||||
realm.partialExport(false, false);
|
invoke(realm -> realm.partialExport(true, false), clients.get("manage-realm"), false);
|
||||||
}
|
invoke(realm -> realm.partialExport(false, true), clients.get("manage-realm"), false);
|
||||||
}, clients.get("view-realm"), true);
|
invoke(realm -> realm.partialExport(true, true), clients.get("multi"), true);
|
||||||
invoke(new Invocation() {
|
invoke(realm -> realm.partialExport(false, false), clients.get("none"), false);
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.partialExport(true, true);
|
|
||||||
}
|
|
||||||
}, clients.get("multi"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.partialExport(true, false);
|
|
||||||
}
|
|
||||||
}, clients.get("view-realm"), false);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.partialExport(false, true);
|
|
||||||
}
|
|
||||||
}, clients.get("view-realm"), false);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.partialExport(false, false);
|
|
||||||
}
|
|
||||||
}, clients.get("none"), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue