Realm settings(Client policies): Update client-scopes condition to match new design (#1575)
This commit is contained in:
parent
d97ffecc29
commit
999b502d44
13 changed files with 523 additions and 69 deletions
|
@ -118,7 +118,7 @@ describe("Realm settings tests", () => {
|
|||
masthead.checkNotificationMessage("Realm successfully updated");
|
||||
});
|
||||
|
||||
it.skip("Go to login tab", () => {
|
||||
it("Go to login tab", () => {
|
||||
sidebarPage.goToRealmSettings();
|
||||
cy.findByTestId("rs-login-tab").click();
|
||||
realmSettingsPage.toggleSwitch(realmSettingsPage.userRegSwitch);
|
||||
|
@ -126,7 +126,7 @@ describe("Realm settings tests", () => {
|
|||
realmSettingsPage.toggleSwitch(realmSettingsPage.rememberMeSwitch);
|
||||
});
|
||||
|
||||
it.skip("Check login tab values", () => {
|
||||
it("Check login tab values", () => {
|
||||
sidebarPage.goToRealmSettings();
|
||||
cy.findByTestId("rs-login-tab").click();
|
||||
|
||||
|
@ -605,20 +605,32 @@ describe("Realm settings tests", () => {
|
|||
realmSettingsPage.shouldCancelAddingCondition();
|
||||
});
|
||||
|
||||
it("Should add a new condition to a client profile", () => {
|
||||
realmSettingsPage.shouldAddCondition();
|
||||
it("Should add a new client-roles condition to a client profile", () => {
|
||||
realmSettingsPage.shouldAddClientRolesCondition();
|
||||
});
|
||||
|
||||
it("Should edit the condition of a client profile", () => {
|
||||
realmSettingsPage.shouldEditCondition();
|
||||
it("Should add a new client-scopes condition to a client profile", () => {
|
||||
realmSettingsPage.shouldAddClientScopesCondition();
|
||||
});
|
||||
|
||||
it("Should edit the client-roles condition of a client profile", () => {
|
||||
realmSettingsPage.shouldEditClientRolesCondition();
|
||||
});
|
||||
|
||||
it("Should edit the client-scopes condition of a client profile", () => {
|
||||
realmSettingsPage.shouldEditClientScopesCondition();
|
||||
});
|
||||
|
||||
it("Should cancel deleting condition from a client profile", () => {
|
||||
realmSettingsPage.shouldCancelDeletingCondition();
|
||||
});
|
||||
|
||||
it("Should delete condition from a client profile", () => {
|
||||
realmSettingsPage.shouldDeleteCondition();
|
||||
it("Should delete client-roles condition from a client profile", () => {
|
||||
realmSettingsPage.shouldDeleteClientRolesCondition();
|
||||
});
|
||||
|
||||
it("Should delete client-scopes condition from a client profile", () => {
|
||||
realmSettingsPage.shouldDeleteClientScopesCondition();
|
||||
});
|
||||
|
||||
it("Check cancelling the client policy deletion", () => {
|
||||
|
|
|
@ -197,6 +197,8 @@ export default class RealmSettingsPage {
|
|||
private addConditionCancelBtn = "addCondition-cancelBtn";
|
||||
private addConditionSaveBtn = "addCondition-saveBtn";
|
||||
private conditionTypeLink = "condition-type-link";
|
||||
private clientRolesConditionLink = "client-roles-condition-link";
|
||||
private clientScopesConditionLink = "client-scopes-condition-link";
|
||||
private eventListenersFormLabel = ".pf-c-form__label-text";
|
||||
private eventListenersDrpDwn = ".pf-c-select.kc_eventListeners_select";
|
||||
private eventListenersSaveBtn = "saveEventListenerBtn";
|
||||
|
@ -208,6 +210,9 @@ export default class RealmSettingsPage {
|
|||
".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
|
||||
private eventListenerRemove = '[data-ouia-component-id="Remove"]';
|
||||
private roleSelect = ".pf-c-select.kc-role-select";
|
||||
private selectScopeButton = "select-scope-button";
|
||||
private deleteClientRolesCondition = "delete-client-roles-condition";
|
||||
private deleteClientScopesCondition = "delete-client-scopes-condition";
|
||||
|
||||
selectLoginThemeType(themeType: string) {
|
||||
cy.get(this.selectLoginTheme).click();
|
||||
|
@ -966,7 +971,7 @@ export default class RealmSettingsPage {
|
|||
);
|
||||
}
|
||||
|
||||
shouldAddCondition() {
|
||||
shouldAddClientRolesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
cy.findByTestId(this.addCondition).click();
|
||||
cy.get(this.addConditionDrpDwn).click();
|
||||
|
@ -989,10 +994,37 @@ export default class RealmSettingsPage {
|
|||
cy.get('ul[class*="pf-c-data-list"]').should("have.text", "client-roles");
|
||||
}
|
||||
|
||||
shouldEditCondition() {
|
||||
addClientScopes() {
|
||||
cy.findByTestId(this.selectScopeButton).click();
|
||||
cy.get(".pf-c-table__check > input[name=checkrow0]").click();
|
||||
cy.get(".pf-c-table__check > input[name=checkrow1]").click();
|
||||
cy.get(".pf-c-table__check > input[name=checkrow2]").click();
|
||||
|
||||
cy.findByTestId("modalConfirm").contains("Add").click();
|
||||
}
|
||||
|
||||
shouldAddClientScopesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
cy.findByTestId(this.addCondition).click();
|
||||
cy.get(this.addConditionDrpDwn).click();
|
||||
cy.findByTestId(this.addConditionDrpDwnOption)
|
||||
.contains("client-scopes")
|
||||
.click();
|
||||
|
||||
this.addClientScopes();
|
||||
|
||||
cy.findByTestId(this.addConditionSaveBtn).click();
|
||||
cy.get(this.alertMessage).should(
|
||||
"be.visible",
|
||||
"Success! Condition created successfully"
|
||||
);
|
||||
cy.get('ul[class*="pf-c-data-list"]').contains("client-scopes");
|
||||
}
|
||||
|
||||
shouldEditClientRolesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
|
||||
cy.findByTestId(this.conditionTypeLink).contains("client-roles").click();
|
||||
cy.findByTestId(this.clientRolesConditionLink).click();
|
||||
|
||||
cy.get(this.roleSelect).click();
|
||||
cy.get(this.roleSelect).contains("create-client").click();
|
||||
|
@ -1006,26 +1038,53 @@ export default class RealmSettingsPage {
|
|||
);
|
||||
}
|
||||
|
||||
shouldEditClientScopesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
|
||||
cy.findByTestId(this.clientScopesConditionLink).click();
|
||||
|
||||
cy.wait(200);
|
||||
|
||||
this.addClientScopes();
|
||||
|
||||
cy.findByTestId(this.addConditionSaveBtn).click();
|
||||
cy.get(this.alertMessage).should(
|
||||
"be.visible",
|
||||
"Success! Condition updated successfully"
|
||||
);
|
||||
}
|
||||
|
||||
shouldCancelDeletingCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
cy.get('svg[class*="kc-conditionType-trash-icon"]').click();
|
||||
cy.findByTestId(this.deleteClientRolesCondition).click();
|
||||
cy.get(this.deleteDialogTitle).contains("Delete condition?");
|
||||
cy.get(this.deleteDialogBodyText).contains(
|
||||
"This action will permanently delete client-roles. This cannot be undone."
|
||||
);
|
||||
cy.findByTestId("modalConfirm").contains("Delete");
|
||||
cy.get(this.deleteDialogCancelBtn).contains("Cancel").click();
|
||||
cy.get('ul[class*="pf-c-data-list"]').should("have.text", "client-roles");
|
||||
cy.get('ul[class*="pf-c-data-list"]').contains("client-roles");
|
||||
}
|
||||
|
||||
shouldDeleteCondition() {
|
||||
shouldDeleteClientRolesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
cy.get('svg[class*="kc-conditionType-trash-icon"]').click();
|
||||
cy.findByTestId(this.deleteClientRolesCondition).click();
|
||||
cy.get(this.deleteDialogTitle).contains("Delete condition?");
|
||||
cy.get(this.deleteDialogBodyText).contains(
|
||||
"This action will permanently delete client-roles. This cannot be undone."
|
||||
);
|
||||
cy.findByTestId("modalConfirm").contains("Delete").click();
|
||||
cy.get('ul[class*="pf-c-data-list"]').contains("client-scopes");
|
||||
}
|
||||
|
||||
shouldDeleteClientScopesCondition() {
|
||||
cy.get(this.clientPolicy).click();
|
||||
cy.findByTestId(this.deleteClientScopesCondition).click();
|
||||
cy.get(this.deleteDialogTitle).contains("Delete condition?");
|
||||
cy.get(this.deleteDialogBodyText).contains(
|
||||
"This action will permanently delete client-scopes. This cannot be undone."
|
||||
);
|
||||
cy.findByTestId("modalConfirm").contains("Delete").click();
|
||||
cy.get('h6[class*="kc-emptyConditions"]').should(
|
||||
"have.text",
|
||||
"No conditions configured"
|
||||
|
|
|
@ -19,7 +19,7 @@ import { useAlerts } from "../components/alert/Alerts";
|
|||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { upperCaseFormatter, emptyFormatter } from "../util";
|
||||
import { emptyFormatter } from "../util";
|
||||
import {
|
||||
CellDropdown,
|
||||
ClientScope,
|
||||
|
@ -44,6 +44,7 @@ import {
|
|||
typeFilter,
|
||||
} from "./details/SearchFilter";
|
||||
import type { Row } from "../clients/scopes/ClientScopes";
|
||||
import { getProtocolName } from "../clients/utils";
|
||||
|
||||
export default function ClientScopesSection() {
|
||||
const { realm } = useRealm();
|
||||
|
@ -267,7 +268,8 @@ export default function ClientScopesSection() {
|
|||
{
|
||||
name: "protocol",
|
||||
displayKey: "client-scopes:protocol",
|
||||
cellFormatters: [upperCaseFormatter()],
|
||||
cellRenderer: (client) =>
|
||||
getProtocolName(t, client.protocol ?? "openid-connect"),
|
||||
transforms: [cellWidth(15)],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -458,6 +458,7 @@ export default function ClientDetails() {
|
|||
title={<TabTitleText>{t("setup")}</TabTitleText>}
|
||||
>
|
||||
<ClientScopes
|
||||
clientName={client.clientId!}
|
||||
clientId={clientId}
|
||||
protocol={client!.protocol!}
|
||||
/>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
export default {
|
||||
clients: {
|
||||
protocol: {
|
||||
protocolTypes: {
|
||||
openIdConnect: "OpenID Connect",
|
||||
saml: "SAML",
|
||||
all: "All",
|
||||
},
|
||||
protocol: "Protocol",
|
||||
clientType: "Client type",
|
||||
clientAuthorization: "Authorization",
|
||||
implicitFlow: "Implicit flow",
|
||||
|
@ -27,7 +29,7 @@ export default {
|
|||
"You haven't created any roles for this client. Create a role to get started.",
|
||||
clientScopes: "Client scopes",
|
||||
addClientScope: "Add client scope",
|
||||
addClientScopesTo: "Add client scopes to {{clientId}}",
|
||||
addClientScopesTo: "Add client scopes to {{clientName}}",
|
||||
clientScopeRemoveSuccess: "Scope mapping successfully removed",
|
||||
clientScopeRemoveError: "Could not remove the scope mapping {{error}}",
|
||||
clientScopeSuccess: "Scope mapping successfully updated",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
|
@ -8,8 +8,17 @@ import {
|
|||
Modal,
|
||||
ModalVariant,
|
||||
DropdownDirection,
|
||||
DropdownItem,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
SelectDirection,
|
||||
} from "@patternfly/react-core";
|
||||
import { CaretUpIcon } from "@patternfly/react-icons";
|
||||
import {
|
||||
CaretDownIcon,
|
||||
CaretUpIcon,
|
||||
FilterIcon,
|
||||
} from "@patternfly/react-icons";
|
||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
|
||||
import {
|
||||
|
@ -19,27 +28,65 @@ import {
|
|||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||
|
||||
import "./client-scopes.css";
|
||||
import { getProtocolName } from "../utils";
|
||||
|
||||
export type AddScopeDialogProps = {
|
||||
clientScopes: ClientScopeRepresentation[];
|
||||
clientName?: string;
|
||||
open: boolean;
|
||||
toggleDialog: () => void;
|
||||
onAdd: (
|
||||
scopes: { scope: ClientScopeRepresentation; type: ClientScopeType }[]
|
||||
scopes: { scope: ClientScopeRepresentation; type?: ClientScopeType }[]
|
||||
) => void;
|
||||
isClientScopesConditionType?: boolean;
|
||||
};
|
||||
|
||||
enum FilterType {
|
||||
Name = "Name",
|
||||
Protocol = "Protocol",
|
||||
}
|
||||
|
||||
enum ProtocolType {
|
||||
All = "All",
|
||||
SAML = "SAML",
|
||||
OpenIDConnect = "OpenID Connect",
|
||||
}
|
||||
|
||||
export const AddScopeDialog = ({
|
||||
clientScopes,
|
||||
clientName,
|
||||
open,
|
||||
toggleDialog,
|
||||
onAdd,
|
||||
isClientScopesConditionType,
|
||||
}: AddScopeDialogProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const [addToggle, setAddToggle] = useState(false);
|
||||
const [rows, setRows] = useState<ClientScopeRepresentation[]>([]);
|
||||
const [filterType, setFilterType] = useState(FilterType.Name);
|
||||
const [protocolType, setProtocolType] = useState(ProtocolType.All);
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(key + 1);
|
||||
|
||||
const loader = () => Promise.resolve(clientScopes);
|
||||
const [isFilterTypeDropdownOpen, setIsFilterTypeDropdownOpen] =
|
||||
useState(false);
|
||||
|
||||
const [isProtocolTypeDropdownOpen, setIsProtocolTypeDropdownOpen] =
|
||||
useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [filterType, protocolType]);
|
||||
|
||||
const loader = async () => {
|
||||
if (protocolType === ProtocolType.OpenIDConnect) {
|
||||
return clientScopes.filter((item) => item.protocol === "openid-connect");
|
||||
} else if (protocolType === ProtocolType.SAML) {
|
||||
return clientScopes.filter((item) => item.protocol === "saml");
|
||||
}
|
||||
|
||||
return clientScopes;
|
||||
};
|
||||
|
||||
const action = (scope: ClientScopeType) => {
|
||||
const scopes = rows.map((row) => {
|
||||
|
@ -50,55 +97,213 @@ export const AddScopeDialog = ({
|
|||
toggleDialog();
|
||||
};
|
||||
|
||||
const onFilterTypeDropdownToggle = () => {
|
||||
setIsFilterTypeDropdownOpen(!isFilterTypeDropdownOpen);
|
||||
};
|
||||
|
||||
const onProtocolTypeDropdownToggle = () => {
|
||||
setIsProtocolTypeDropdownOpen(!isProtocolTypeDropdownOpen);
|
||||
};
|
||||
|
||||
const onFilterTypeDropdownSelect = (filterType: string) => {
|
||||
if (filterType === FilterType.Name) {
|
||||
setFilterType(FilterType.Protocol);
|
||||
} else if (filterType === FilterType.Protocol) {
|
||||
setFilterType(FilterType.Name);
|
||||
}
|
||||
|
||||
setIsFilterTypeDropdownOpen(!isFilterTypeDropdownOpen);
|
||||
};
|
||||
|
||||
const onProtocolTypeDropdownSelect = (protocolType: string) => {
|
||||
if (protocolType === ProtocolType.SAML) {
|
||||
setProtocolType(ProtocolType.SAML);
|
||||
} else if (protocolType === ProtocolType.OpenIDConnect) {
|
||||
setProtocolType(ProtocolType.OpenIDConnect);
|
||||
} else if (protocolType === ProtocolType.All) {
|
||||
setProtocolType(ProtocolType.All);
|
||||
}
|
||||
|
||||
setIsProtocolTypeDropdownOpen(!isProtocolTypeDropdownOpen);
|
||||
};
|
||||
|
||||
const protocolTypeOptions = [
|
||||
<SelectOption key={1} value={ProtocolType.SAML}>
|
||||
{t("protocolTypes.saml")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={2} value={ProtocolType.OpenIDConnect}>
|
||||
{t("protocolTypes.openIdConnect")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={3} value={ProtocolType.All} isPlaceholder>
|
||||
{t("protocolTypes.all")}
|
||||
</SelectOption>,
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
variant={ModalVariant.medium}
|
||||
title={t("addClientScopesTo", { clientId: "test" })}
|
||||
title={
|
||||
isClientScopesConditionType
|
||||
? t("addClientScope")
|
||||
: t("addClientScopesTo", { clientName })
|
||||
}
|
||||
isOpen={open}
|
||||
onClose={toggleDialog}
|
||||
actions={[
|
||||
<Dropdown
|
||||
className="keycloak__client-scopes-add__add-dropdown"
|
||||
id="add-dropdown"
|
||||
key="add-dropdown"
|
||||
direction={DropdownDirection.up}
|
||||
isOpen={addToggle}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
isDisabled={rows.length === 0}
|
||||
onToggle={() => setAddToggle(!addToggle)}
|
||||
isPrimary
|
||||
toggleIndicator={CaretUpIcon}
|
||||
id="add-scope-toggle"
|
||||
>
|
||||
{t("common:add")}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={clientScopeTypesDropdown(t, action)}
|
||||
/>,
|
||||
<Button
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
setRows([]);
|
||||
toggleDialog();
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
actions={
|
||||
isClientScopesConditionType
|
||||
? [
|
||||
<Button
|
||||
id="modal-add"
|
||||
data-testid="modalConfirm"
|
||||
key="add"
|
||||
variant={ButtonVariant.primary}
|
||||
onClick={() => {
|
||||
const scopes = rows.map((scope) => ({ scope }));
|
||||
onAdd(scopes);
|
||||
toggleDialog();
|
||||
}}
|
||||
>
|
||||
{t("common:add")}
|
||||
</Button>,
|
||||
<Button
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
setRows([]);
|
||||
toggleDialog();
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]
|
||||
: [
|
||||
<Dropdown
|
||||
className="keycloak__client-scopes-add__add-dropdown"
|
||||
id="add-dropdown"
|
||||
key="add-dropdown"
|
||||
direction={DropdownDirection.up}
|
||||
isOpen={addToggle}
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
isDisabled={rows.length === 0}
|
||||
onToggle={() => setAddToggle(!addToggle)}
|
||||
isPrimary
|
||||
toggleIndicator={CaretUpIcon}
|
||||
id="add-scope-toggle"
|
||||
>
|
||||
{t("common:add")}
|
||||
</DropdownToggle>
|
||||
}
|
||||
dropdownItems={clientScopeTypesDropdown(t, action)}
|
||||
/>,
|
||||
<Button
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
setRows([]);
|
||||
toggleDialog();
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]
|
||||
}
|
||||
>
|
||||
<KeycloakDataTable
|
||||
loader={loader}
|
||||
ariaLabelKey="client-scopes:chooseAMapperType"
|
||||
searchPlaceholderKey="client-scopes:searchFor"
|
||||
searchPlaceholderKey={
|
||||
filterType === FilterType.Name ? "client-scopes:searchFor" : undefined
|
||||
}
|
||||
searchTypeComponent={
|
||||
<Dropdown
|
||||
onSelect={() => {
|
||||
onFilterTypeDropdownSelect(filterType);
|
||||
}}
|
||||
data-testid="filter-type-dropdown"
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
id="toggle-id-9"
|
||||
onToggle={onFilterTypeDropdownToggle}
|
||||
toggleIndicator={CaretDownIcon}
|
||||
icon={<FilterIcon />}
|
||||
>
|
||||
{filterType}
|
||||
</DropdownToggle>
|
||||
}
|
||||
isOpen={isFilterTypeDropdownOpen}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
data-testid="filter-type-dropdown-item"
|
||||
key="filter-type"
|
||||
>
|
||||
{filterType === FilterType.Name
|
||||
? t("protocol")
|
||||
: t("common:name")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
key={key}
|
||||
toolbarItem={
|
||||
filterType === FilterType.Protocol && (
|
||||
<>
|
||||
<Dropdown
|
||||
onSelect={() => {
|
||||
onFilterTypeDropdownSelect(filterType);
|
||||
}}
|
||||
data-testid="filter-type-dropdown"
|
||||
toggle={
|
||||
<DropdownToggle
|
||||
id="toggle-id-9"
|
||||
onToggle={onFilterTypeDropdownToggle}
|
||||
toggleIndicator={CaretDownIcon}
|
||||
icon={<FilterIcon />}
|
||||
>
|
||||
{filterType}
|
||||
</DropdownToggle>
|
||||
}
|
||||
isOpen={isFilterTypeDropdownOpen}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
data-testid="filter-type-dropdown-item"
|
||||
key="filter-type"
|
||||
>
|
||||
{t("common:name")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
variant={SelectVariant.single}
|
||||
className="kc-protocolType-select"
|
||||
aria-label="Select Input"
|
||||
onToggle={onProtocolTypeDropdownToggle}
|
||||
onSelect={(_, value) =>
|
||||
onProtocolTypeDropdownSelect(value.toString())
|
||||
}
|
||||
selections={protocolType}
|
||||
isOpen={isProtocolTypeDropdownOpen}
|
||||
direction={SelectDirection.down}
|
||||
>
|
||||
{protocolTypeOptions}
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
}
|
||||
canSelectAll
|
||||
onSelect={(rows) => setRows(rows)}
|
||||
columns={[
|
||||
{
|
||||
name: "name",
|
||||
},
|
||||
{
|
||||
name: "protocol",
|
||||
displayKey: "clients:protocol",
|
||||
cellRenderer: (client) =>
|
||||
getProtocolName(t, client.protocol ?? "openid-connect"),
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
},
|
||||
|
|
|
@ -38,6 +38,7 @@ import { ChangeTypeDropdown } from "../../client-scopes/ChangeTypeDropdown";
|
|||
export type ClientScopesProps = {
|
||||
clientId: string;
|
||||
protocol: string;
|
||||
clientName: string;
|
||||
};
|
||||
|
||||
export type Row = ClientScopeRepresentation & {
|
||||
|
@ -45,7 +46,11 @@ export type Row = ClientScopeRepresentation & {
|
|||
description?: string;
|
||||
};
|
||||
|
||||
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||
export const ClientScopes = ({
|
||||
clientId,
|
||||
protocol,
|
||||
clientName,
|
||||
}: ClientScopesProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
@ -135,6 +140,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
{rest && (
|
||||
<AddScopeDialog
|
||||
clientScopes={rest}
|
||||
clientName={clientName!}
|
||||
open={addDialogOpen}
|
||||
toggleDialog={() => setAddDialogOpen(!addDialogOpen)}
|
||||
onAdd={async (scopes) => {
|
||||
|
@ -146,7 +152,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|||
adminClient,
|
||||
clientId,
|
||||
scope.scope,
|
||||
scope.type
|
||||
scope.type!
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -5,4 +5,8 @@
|
|||
|
||||
.keycloak__client-scopes-add__add-dropdown {
|
||||
margin-right: var(--pf-global--spacer--md);
|
||||
}
|
||||
|
||||
.kc-protocolType-select {
|
||||
max-width: 25%;
|
||||
}
|
|
@ -12,9 +12,9 @@ export const isRealmClient = (client: ClientRepresentation) => !client.protocol;
|
|||
export const getProtocolName = (t: TFunction<"clients">, protocol: string) => {
|
||||
switch (protocol) {
|
||||
case "openid-connect":
|
||||
return t("clients:protocol:openIdConnect");
|
||||
return t("clients:protocolTypes:openIdConnect");
|
||||
case "saml":
|
||||
return t("clients:protocol:saml");
|
||||
return t("clients:protocolTypes:saml");
|
||||
}
|
||||
|
||||
return protocol;
|
||||
|
|
129
src/components/dynamic/MultivaluedScopesComponent.tsx
Normal file
129
src/components/dynamic/MultivaluedScopesComponent.tsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipGroup,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../help-enabler/HelpItem";
|
||||
import type { ComponentProps } from "./components";
|
||||
import { AddScopeDialog } from "../../clients/scopes/AddScopeDialog";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
import { useParams } from "react-router";
|
||||
import type { EditClientPolicyConditionParams } from "../../realm-settings/routes/EditCondition";
|
||||
|
||||
export const MultivaluedScopesComponent = ({
|
||||
defaultValue,
|
||||
name,
|
||||
}: ComponentProps) => {
|
||||
const { t } = useTranslation("dynamic");
|
||||
const { control } = useFormContext();
|
||||
const { conditionName } = useParams<EditClientPolicyConditionParams>();
|
||||
const adminClient = useAdminClient();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [clientScopes, setClientScopes] = useState<ClientScopeRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.clientScopes.find(),
|
||||
(clientScopes) => {
|
||||
setClientScopes(clientScopes);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const toggleModal = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={t("realm-settings:clientScopesCondition")}
|
||||
id="expected-scopes"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("realm-settings-help:clientScopesConditionTooltip")}
|
||||
forLabel={t("clientScopes")}
|
||||
forID={name!}
|
||||
/>
|
||||
}
|
||||
fieldId={name!}
|
||||
>
|
||||
<Controller
|
||||
name={`config.scopes`}
|
||||
control={control}
|
||||
defaultValue={[defaultValue]}
|
||||
rules={{ required: true }}
|
||||
render={({ onChange, value }) => {
|
||||
return (
|
||||
<>
|
||||
{open && (
|
||||
<AddScopeDialog
|
||||
clientScopes={clientScopes.filter(
|
||||
(scope) => !value.includes(scope.name!)
|
||||
)}
|
||||
isClientScopesConditionType
|
||||
open={open}
|
||||
toggleDialog={() => setOpen(!open)}
|
||||
onAdd={(scopes) => {
|
||||
onChange([
|
||||
...value,
|
||||
...scopes
|
||||
.map((scope) => scope.scope)
|
||||
.map((item) => item.name!),
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{value.length === 0 && !conditionName && (
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-scopes"
|
||||
value={value}
|
||||
data-testid="client-scope-input"
|
||||
name="config.client-scopes"
|
||||
isDisabled
|
||||
/>
|
||||
)}
|
||||
<ChipGroup
|
||||
className="kc-client-scopes-chip-group"
|
||||
isClosable
|
||||
onClick={() => {
|
||||
onChange([]);
|
||||
}}
|
||||
>
|
||||
{value.map((currentChip: string) => (
|
||||
<Chip
|
||||
key={currentChip}
|
||||
onClick={() => {
|
||||
onChange(
|
||||
value.filter((item: string) => item !== currentChip)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{currentChip}
|
||||
</Chip>
|
||||
))}
|
||||
</ChipGroup>
|
||||
<Button
|
||||
data-testid="select-scope-button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
toggleModal();
|
||||
}}
|
||||
>
|
||||
{t("common:select")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
|
@ -34,12 +34,12 @@ import {
|
|||
convertToMultiline,
|
||||
toValue,
|
||||
} from "../components/multi-line-input/MultiLineInput";
|
||||
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
|
||||
import {
|
||||
COMPONENTS,
|
||||
isValidComponentType,
|
||||
} from "../components/dynamic/components";
|
||||
|
||||
import { MultivaluedScopesComponent } from "../components/dynamic/MultivaluedScopesComponent";
|
||||
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
|
||||
export type ItemType = { value: string };
|
||||
|
||||
type ConfigProperty = ConfigPropertyRepresentation & {
|
||||
|
@ -54,6 +54,7 @@ export default function NewClientPolicyCondition() {
|
|||
|
||||
const [openConditionType, setOpenConditionType] = useState(false);
|
||||
const [policies, setPolicies] = useState<ClientPolicyRepresentation[]>([]);
|
||||
|
||||
const [condition, setCondition] = useState<
|
||||
ClientPolicyConditionRepresentation[]
|
||||
>([]);
|
||||
|
@ -87,9 +88,15 @@ export default function NewClientPolicyCondition() {
|
|||
|
||||
Object.entries(condition.configuration!).map(([key, value]) => {
|
||||
const formKey = `config.${key}`;
|
||||
|
||||
const property = properties.find((p) => p.name === key);
|
||||
if (property?.type === "MultivaluedString") {
|
||||
if (
|
||||
property?.type === "MultivaluedString" &&
|
||||
property.name !== "scopes"
|
||||
) {
|
||||
form.setValue(formKey, convertToMultiline(value));
|
||||
} else if (property?.name === "client-scopes") {
|
||||
form.setValue("config.scopes", value);
|
||||
} else {
|
||||
form.setValue(formKey, value);
|
||||
}
|
||||
|
@ -98,8 +105,10 @@ export default function NewClientPolicyCondition() {
|
|||
|
||||
useFetch(
|
||||
() => adminClient.clientPolicies.listPolicies(),
|
||||
|
||||
(policies) => {
|
||||
setPolicies(policies.policies ?? []);
|
||||
|
||||
if (conditionName) {
|
||||
const currentPolicy = policies.policies?.find(
|
||||
(item) => item.name === policyName
|
||||
|
@ -124,13 +133,14 @@ export default function NewClientPolicyCondition() {
|
|||
const save = async (configPolicy: ConfigProperty) => {
|
||||
const configValues = configPolicy.config;
|
||||
|
||||
const writeConfig = () =>
|
||||
conditionProperties.reduce((r: any, p) => {
|
||||
p.type === "MultivaluedString"
|
||||
const writeConfig = () => {
|
||||
return conditionProperties.reduce((r: any, p) => {
|
||||
p.type === "MultivaluedString" && p.name !== "scopes"
|
||||
? (r[p.name!] = toValue(configValues[p.name!]))
|
||||
: (r[p.name!] = configValues[p.name!]);
|
||||
return r;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const updatedPolicies = policies.map((policy) => {
|
||||
if (policy.name !== policyName) {
|
||||
|
@ -284,6 +294,17 @@ export default function NewClientPolicyCondition() {
|
|||
conditionName === "client-roles")
|
||||
) {
|
||||
return <MultivaluedRoleComponent {...property} />;
|
||||
} else if (
|
||||
property.name === "scopes" &&
|
||||
(conditionType === "client-scopes" ||
|
||||
conditionName === "client-scopes")
|
||||
) {
|
||||
return (
|
||||
<MultivaluedScopesComponent
|
||||
defaultValue="offline_access"
|
||||
{...property}
|
||||
/>
|
||||
);
|
||||
} else if (isValidComponentType(componentType)) {
|
||||
const Component = COMPONENTS[componentType];
|
||||
return <Component key={property.name} {...property} />;
|
||||
|
|
|
@ -551,7 +551,7 @@ export default function NewClientPolicyForm() {
|
|||
0 ? (
|
||||
<Link
|
||||
key={condition.condition}
|
||||
data-testid="condition-type-link"
|
||||
data-testid={`${condition.condition}-condition-link`}
|
||||
to={toEditClientPolicyCondition({
|
||||
realm,
|
||||
conditionName: condition.condition!,
|
||||
|
@ -581,7 +581,7 @@ export default function NewClientPolicyForm() {
|
|||
icon={
|
||||
<TrashIcon
|
||||
className="kc-conditionType-trash-icon"
|
||||
data-testid="deleteClientProfileDropdown"
|
||||
data-testid={`delete-${condition.condition}-condition`}
|
||||
onClick={() => {
|
||||
toggleDeleteConditionDialog();
|
||||
setConditionToDelete({
|
||||
|
|
|
@ -252,3 +252,16 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
|
|||
.kc_eventListeners_select {
|
||||
width: 35rem;
|
||||
}
|
||||
|
||||
input#kc-scopes {
|
||||
width: 630px;
|
||||
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.kc-client-scopes-chip-group {
|
||||
margin-right: var(--pf-global--spacer--2xl);
|
||||
max-width: 585px;
|
||||
min-width: 585px;
|
||||
padding-left: none;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue