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");
|
masthead.checkNotificationMessage("Realm successfully updated");
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip("Go to login tab", () => {
|
it("Go to login tab", () => {
|
||||||
sidebarPage.goToRealmSettings();
|
sidebarPage.goToRealmSettings();
|
||||||
cy.findByTestId("rs-login-tab").click();
|
cy.findByTestId("rs-login-tab").click();
|
||||||
realmSettingsPage.toggleSwitch(realmSettingsPage.userRegSwitch);
|
realmSettingsPage.toggleSwitch(realmSettingsPage.userRegSwitch);
|
||||||
|
@ -126,7 +126,7 @@ describe("Realm settings tests", () => {
|
||||||
realmSettingsPage.toggleSwitch(realmSettingsPage.rememberMeSwitch);
|
realmSettingsPage.toggleSwitch(realmSettingsPage.rememberMeSwitch);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip("Check login tab values", () => {
|
it("Check login tab values", () => {
|
||||||
sidebarPage.goToRealmSettings();
|
sidebarPage.goToRealmSettings();
|
||||||
cy.findByTestId("rs-login-tab").click();
|
cy.findByTestId("rs-login-tab").click();
|
||||||
|
|
||||||
|
@ -605,20 +605,32 @@ describe("Realm settings tests", () => {
|
||||||
realmSettingsPage.shouldCancelAddingCondition();
|
realmSettingsPage.shouldCancelAddingCondition();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should add a new condition to a client profile", () => {
|
it("Should add a new client-roles condition to a client profile", () => {
|
||||||
realmSettingsPage.shouldAddCondition();
|
realmSettingsPage.shouldAddClientRolesCondition();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should edit the condition of a client profile", () => {
|
it("Should add a new client-scopes condition to a client profile", () => {
|
||||||
realmSettingsPage.shouldEditCondition();
|
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", () => {
|
it("Should cancel deleting condition from a client profile", () => {
|
||||||
realmSettingsPage.shouldCancelDeletingCondition();
|
realmSettingsPage.shouldCancelDeletingCondition();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should delete condition from a client profile", () => {
|
it("Should delete client-roles condition from a client profile", () => {
|
||||||
realmSettingsPage.shouldDeleteCondition();
|
realmSettingsPage.shouldDeleteClientRolesCondition();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should delete client-scopes condition from a client profile", () => {
|
||||||
|
realmSettingsPage.shouldDeleteClientScopesCondition();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Check cancelling the client policy deletion", () => {
|
it("Check cancelling the client policy deletion", () => {
|
||||||
|
|
|
@ -197,6 +197,8 @@ export default class RealmSettingsPage {
|
||||||
private addConditionCancelBtn = "addCondition-cancelBtn";
|
private addConditionCancelBtn = "addCondition-cancelBtn";
|
||||||
private addConditionSaveBtn = "addCondition-saveBtn";
|
private addConditionSaveBtn = "addCondition-saveBtn";
|
||||||
private conditionTypeLink = "condition-type-link";
|
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 eventListenersFormLabel = ".pf-c-form__label-text";
|
||||||
private eventListenersDrpDwn = ".pf-c-select.kc_eventListeners_select";
|
private eventListenersDrpDwn = ".pf-c-select.kc_eventListeners_select";
|
||||||
private eventListenersSaveBtn = "saveEventListenerBtn";
|
private eventListenersSaveBtn = "saveEventListenerBtn";
|
||||||
|
@ -208,6 +210,9 @@ export default class RealmSettingsPage {
|
||||||
".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
|
".pf-c-button.pf-c-select__toggle-button.pf-m-plain";
|
||||||
private eventListenerRemove = '[data-ouia-component-id="Remove"]';
|
private eventListenerRemove = '[data-ouia-component-id="Remove"]';
|
||||||
private roleSelect = ".pf-c-select.kc-role-select";
|
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) {
|
selectLoginThemeType(themeType: string) {
|
||||||
cy.get(this.selectLoginTheme).click();
|
cy.get(this.selectLoginTheme).click();
|
||||||
|
@ -966,7 +971,7 @@ export default class RealmSettingsPage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldAddCondition() {
|
shouldAddClientRolesCondition() {
|
||||||
cy.get(this.clientPolicy).click();
|
cy.get(this.clientPolicy).click();
|
||||||
cy.findByTestId(this.addCondition).click();
|
cy.findByTestId(this.addCondition).click();
|
||||||
cy.get(this.addConditionDrpDwn).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");
|
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.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).click();
|
||||||
cy.get(this.roleSelect).contains("create-client").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() {
|
shouldCancelDeletingCondition() {
|
||||||
cy.get(this.clientPolicy).click();
|
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.deleteDialogTitle).contains("Delete condition?");
|
||||||
cy.get(this.deleteDialogBodyText).contains(
|
cy.get(this.deleteDialogBodyText).contains(
|
||||||
"This action will permanently delete client-roles. This cannot be undone."
|
"This action will permanently delete client-roles. This cannot be undone."
|
||||||
);
|
);
|
||||||
cy.findByTestId("modalConfirm").contains("Delete");
|
cy.findByTestId("modalConfirm").contains("Delete");
|
||||||
cy.get(this.deleteDialogCancelBtn).contains("Cancel").click();
|
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(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.deleteDialogTitle).contains("Delete condition?");
|
||||||
cy.get(this.deleteDialogBodyText).contains(
|
cy.get(this.deleteDialogBodyText).contains(
|
||||||
"This action will permanently delete client-roles. This cannot be undone."
|
"This action will permanently delete client-roles. This cannot be undone."
|
||||||
);
|
);
|
||||||
cy.findByTestId("modalConfirm").contains("Delete").click();
|
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(
|
cy.get('h6[class*="kc-emptyConditions"]').should(
|
||||||
"have.text",
|
"have.text",
|
||||||
"No conditions configured"
|
"No conditions configured"
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { upperCaseFormatter, emptyFormatter } from "../util";
|
import { emptyFormatter } from "../util";
|
||||||
import {
|
import {
|
||||||
CellDropdown,
|
CellDropdown,
|
||||||
ClientScope,
|
ClientScope,
|
||||||
|
@ -44,6 +44,7 @@ import {
|
||||||
typeFilter,
|
typeFilter,
|
||||||
} from "./details/SearchFilter";
|
} from "./details/SearchFilter";
|
||||||
import type { Row } from "../clients/scopes/ClientScopes";
|
import type { Row } from "../clients/scopes/ClientScopes";
|
||||||
|
import { getProtocolName } from "../clients/utils";
|
||||||
|
|
||||||
export default function ClientScopesSection() {
|
export default function ClientScopesSection() {
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
@ -267,7 +268,8 @@ export default function ClientScopesSection() {
|
||||||
{
|
{
|
||||||
name: "protocol",
|
name: "protocol",
|
||||||
displayKey: "client-scopes:protocol",
|
displayKey: "client-scopes:protocol",
|
||||||
cellFormatters: [upperCaseFormatter()],
|
cellRenderer: (client) =>
|
||||||
|
getProtocolName(t, client.protocol ?? "openid-connect"),
|
||||||
transforms: [cellWidth(15)],
|
transforms: [cellWidth(15)],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -458,6 +458,7 @@ export default function ClientDetails() {
|
||||||
title={<TabTitleText>{t("setup")}</TabTitleText>}
|
title={<TabTitleText>{t("setup")}</TabTitleText>}
|
||||||
>
|
>
|
||||||
<ClientScopes
|
<ClientScopes
|
||||||
|
clientName={client.clientId!}
|
||||||
clientId={clientId}
|
clientId={clientId}
|
||||||
protocol={client!.protocol!}
|
protocol={client!.protocol!}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
export default {
|
export default {
|
||||||
clients: {
|
clients: {
|
||||||
protocol: {
|
protocolTypes: {
|
||||||
openIdConnect: "OpenID Connect",
|
openIdConnect: "OpenID Connect",
|
||||||
saml: "SAML",
|
saml: "SAML",
|
||||||
|
all: "All",
|
||||||
},
|
},
|
||||||
|
protocol: "Protocol",
|
||||||
clientType: "Client type",
|
clientType: "Client type",
|
||||||
clientAuthorization: "Authorization",
|
clientAuthorization: "Authorization",
|
||||||
implicitFlow: "Implicit flow",
|
implicitFlow: "Implicit flow",
|
||||||
|
@ -27,7 +29,7 @@ export default {
|
||||||
"You haven't created any roles for this client. Create a role to get started.",
|
"You haven't created any roles for this client. Create a role to get started.",
|
||||||
clientScopes: "Client scopes",
|
clientScopes: "Client scopes",
|
||||||
addClientScope: "Add client scope",
|
addClientScope: "Add client scope",
|
||||||
addClientScopesTo: "Add client scopes to {{clientId}}",
|
addClientScopesTo: "Add client scopes to {{clientName}}",
|
||||||
clientScopeRemoveSuccess: "Scope mapping successfully removed",
|
clientScopeRemoveSuccess: "Scope mapping successfully removed",
|
||||||
clientScopeRemoveError: "Could not remove the scope mapping {{error}}",
|
clientScopeRemoveError: "Could not remove the scope mapping {{error}}",
|
||||||
clientScopeSuccess: "Scope mapping successfully updated",
|
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 { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -8,8 +8,17 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
ModalVariant,
|
ModalVariant,
|
||||||
DropdownDirection,
|
DropdownDirection,
|
||||||
|
DropdownItem,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
SelectDirection,
|
||||||
} from "@patternfly/react-core";
|
} 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 type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -19,27 +28,65 @@ import {
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
|
||||||
import "./client-scopes.css";
|
import "./client-scopes.css";
|
||||||
|
import { getProtocolName } from "../utils";
|
||||||
|
|
||||||
export type AddScopeDialogProps = {
|
export type AddScopeDialogProps = {
|
||||||
clientScopes: ClientScopeRepresentation[];
|
clientScopes: ClientScopeRepresentation[];
|
||||||
|
clientName?: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
toggleDialog: () => void;
|
toggleDialog: () => void;
|
||||||
onAdd: (
|
onAdd: (
|
||||||
scopes: { scope: ClientScopeRepresentation; type: ClientScopeType }[]
|
scopes: { scope: ClientScopeRepresentation; type?: ClientScopeType }[]
|
||||||
) => void;
|
) => void;
|
||||||
|
isClientScopesConditionType?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FilterType {
|
||||||
|
Name = "Name",
|
||||||
|
Protocol = "Protocol",
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProtocolType {
|
||||||
|
All = "All",
|
||||||
|
SAML = "SAML",
|
||||||
|
OpenIDConnect = "OpenID Connect",
|
||||||
|
}
|
||||||
|
|
||||||
export const AddScopeDialog = ({
|
export const AddScopeDialog = ({
|
||||||
clientScopes,
|
clientScopes,
|
||||||
|
clientName,
|
||||||
open,
|
open,
|
||||||
toggleDialog,
|
toggleDialog,
|
||||||
onAdd,
|
onAdd,
|
||||||
|
isClientScopesConditionType,
|
||||||
}: AddScopeDialogProps) => {
|
}: AddScopeDialogProps) => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
const [addToggle, setAddToggle] = useState(false);
|
const [addToggle, setAddToggle] = useState(false);
|
||||||
const [rows, setRows] = useState<ClientScopeRepresentation[]>([]);
|
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 action = (scope: ClientScopeType) => {
|
||||||
const scopes = rows.map((row) => {
|
const scopes = rows.map((row) => {
|
||||||
|
@ -50,13 +97,87 @@ export const AddScopeDialog = ({
|
||||||
toggleDialog();
|
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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
variant={ModalVariant.medium}
|
variant={ModalVariant.medium}
|
||||||
title={t("addClientScopesTo", { clientId: "test" })}
|
title={
|
||||||
|
isClientScopesConditionType
|
||||||
|
? t("addClientScope")
|
||||||
|
: t("addClientScopesTo", { clientName })
|
||||||
|
}
|
||||||
isOpen={open}
|
isOpen={open}
|
||||||
onClose={toggleDialog}
|
onClose={toggleDialog}
|
||||||
actions={[
|
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
|
<Dropdown
|
||||||
className="keycloak__client-scopes-add__add-dropdown"
|
className="keycloak__client-scopes-add__add-dropdown"
|
||||||
id="add-dropdown"
|
id="add-dropdown"
|
||||||
|
@ -87,18 +208,102 @@ export const AddScopeDialog = ({
|
||||||
>
|
>
|
||||||
{t("common:cancel")}
|
{t("common:cancel")}
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<KeycloakDataTable
|
<KeycloakDataTable
|
||||||
loader={loader}
|
loader={loader}
|
||||||
ariaLabelKey="client-scopes:chooseAMapperType"
|
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
|
canSelectAll
|
||||||
onSelect={(rows) => setRows(rows)}
|
onSelect={(rows) => setRows(rows)}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "protocol",
|
||||||
|
displayKey: "clients:protocol",
|
||||||
|
cellRenderer: (client) =>
|
||||||
|
getProtocolName(t, client.protocol ?? "openid-connect"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,6 +38,7 @@ import { ChangeTypeDropdown } from "../../client-scopes/ChangeTypeDropdown";
|
||||||
export type ClientScopesProps = {
|
export type ClientScopesProps = {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
|
clientName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Row = ClientScopeRepresentation & {
|
export type Row = ClientScopeRepresentation & {
|
||||||
|
@ -45,7 +46,11 @@ export type Row = ClientScopeRepresentation & {
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
export const ClientScopes = ({
|
||||||
|
clientId,
|
||||||
|
protocol,
|
||||||
|
clientName,
|
||||||
|
}: ClientScopesProps) => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
|
@ -135,6 +140,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||||
{rest && (
|
{rest && (
|
||||||
<AddScopeDialog
|
<AddScopeDialog
|
||||||
clientScopes={rest}
|
clientScopes={rest}
|
||||||
|
clientName={clientName!}
|
||||||
open={addDialogOpen}
|
open={addDialogOpen}
|
||||||
toggleDialog={() => setAddDialogOpen(!addDialogOpen)}
|
toggleDialog={() => setAddDialogOpen(!addDialogOpen)}
|
||||||
onAdd={async (scopes) => {
|
onAdd={async (scopes) => {
|
||||||
|
@ -146,7 +152,7 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||||
adminClient,
|
adminClient,
|
||||||
clientId,
|
clientId,
|
||||||
scope.scope,
|
scope.scope,
|
||||||
scope.type
|
scope.type!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,3 +6,7 @@
|
||||||
.keycloak__client-scopes-add__add-dropdown {
|
.keycloak__client-scopes-add__add-dropdown {
|
||||||
margin-right: var(--pf-global--spacer--md);
|
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) => {
|
export const getProtocolName = (t: TFunction<"clients">, protocol: string) => {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case "openid-connect":
|
case "openid-connect":
|
||||||
return t("clients:protocol:openIdConnect");
|
return t("clients:protocolTypes:openIdConnect");
|
||||||
case "saml":
|
case "saml":
|
||||||
return t("clients:protocol:saml");
|
return t("clients:protocolTypes:saml");
|
||||||
}
|
}
|
||||||
|
|
||||||
return protocol;
|
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,
|
convertToMultiline,
|
||||||
toValue,
|
toValue,
|
||||||
} from "../components/multi-line-input/MultiLineInput";
|
} from "../components/multi-line-input/MultiLineInput";
|
||||||
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
|
|
||||||
import {
|
import {
|
||||||
COMPONENTS,
|
COMPONENTS,
|
||||||
isValidComponentType,
|
isValidComponentType,
|
||||||
} from "../components/dynamic/components";
|
} from "../components/dynamic/components";
|
||||||
|
import { MultivaluedScopesComponent } from "../components/dynamic/MultivaluedScopesComponent";
|
||||||
|
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
|
||||||
export type ItemType = { value: string };
|
export type ItemType = { value: string };
|
||||||
|
|
||||||
type ConfigProperty = ConfigPropertyRepresentation & {
|
type ConfigProperty = ConfigPropertyRepresentation & {
|
||||||
|
@ -54,6 +54,7 @@ export default function NewClientPolicyCondition() {
|
||||||
|
|
||||||
const [openConditionType, setOpenConditionType] = useState(false);
|
const [openConditionType, setOpenConditionType] = useState(false);
|
||||||
const [policies, setPolicies] = useState<ClientPolicyRepresentation[]>([]);
|
const [policies, setPolicies] = useState<ClientPolicyRepresentation[]>([]);
|
||||||
|
|
||||||
const [condition, setCondition] = useState<
|
const [condition, setCondition] = useState<
|
||||||
ClientPolicyConditionRepresentation[]
|
ClientPolicyConditionRepresentation[]
|
||||||
>([]);
|
>([]);
|
||||||
|
@ -87,9 +88,15 @@ export default function NewClientPolicyCondition() {
|
||||||
|
|
||||||
Object.entries(condition.configuration!).map(([key, value]) => {
|
Object.entries(condition.configuration!).map(([key, value]) => {
|
||||||
const formKey = `config.${key}`;
|
const formKey = `config.${key}`;
|
||||||
|
|
||||||
const property = properties.find((p) => p.name === 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));
|
form.setValue(formKey, convertToMultiline(value));
|
||||||
|
} else if (property?.name === "client-scopes") {
|
||||||
|
form.setValue("config.scopes", value);
|
||||||
} else {
|
} else {
|
||||||
form.setValue(formKey, value);
|
form.setValue(formKey, value);
|
||||||
}
|
}
|
||||||
|
@ -98,8 +105,10 @@ export default function NewClientPolicyCondition() {
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() => adminClient.clientPolicies.listPolicies(),
|
() => adminClient.clientPolicies.listPolicies(),
|
||||||
|
|
||||||
(policies) => {
|
(policies) => {
|
||||||
setPolicies(policies.policies ?? []);
|
setPolicies(policies.policies ?? []);
|
||||||
|
|
||||||
if (conditionName) {
|
if (conditionName) {
|
||||||
const currentPolicy = policies.policies?.find(
|
const currentPolicy = policies.policies?.find(
|
||||||
(item) => item.name === policyName
|
(item) => item.name === policyName
|
||||||
|
@ -124,13 +133,14 @@ export default function NewClientPolicyCondition() {
|
||||||
const save = async (configPolicy: ConfigProperty) => {
|
const save = async (configPolicy: ConfigProperty) => {
|
||||||
const configValues = configPolicy.config;
|
const configValues = configPolicy.config;
|
||||||
|
|
||||||
const writeConfig = () =>
|
const writeConfig = () => {
|
||||||
conditionProperties.reduce((r: any, p) => {
|
return conditionProperties.reduce((r: any, p) => {
|
||||||
p.type === "MultivaluedString"
|
p.type === "MultivaluedString" && p.name !== "scopes"
|
||||||
? (r[p.name!] = toValue(configValues[p.name!]))
|
? (r[p.name!] = toValue(configValues[p.name!]))
|
||||||
: (r[p.name!] = configValues[p.name!]);
|
: (r[p.name!] = configValues[p.name!]);
|
||||||
return r;
|
return r;
|
||||||
}, {});
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
const updatedPolicies = policies.map((policy) => {
|
const updatedPolicies = policies.map((policy) => {
|
||||||
if (policy.name !== policyName) {
|
if (policy.name !== policyName) {
|
||||||
|
@ -284,6 +294,17 @@ export default function NewClientPolicyCondition() {
|
||||||
conditionName === "client-roles")
|
conditionName === "client-roles")
|
||||||
) {
|
) {
|
||||||
return <MultivaluedRoleComponent {...property} />;
|
return <MultivaluedRoleComponent {...property} />;
|
||||||
|
} else if (
|
||||||
|
property.name === "scopes" &&
|
||||||
|
(conditionType === "client-scopes" ||
|
||||||
|
conditionName === "client-scopes")
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MultivaluedScopesComponent
|
||||||
|
defaultValue="offline_access"
|
||||||
|
{...property}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (isValidComponentType(componentType)) {
|
} else if (isValidComponentType(componentType)) {
|
||||||
const Component = COMPONENTS[componentType];
|
const Component = COMPONENTS[componentType];
|
||||||
return <Component key={property.name} {...property} />;
|
return <Component key={property.name} {...property} />;
|
||||||
|
|
|
@ -551,7 +551,7 @@ export default function NewClientPolicyForm() {
|
||||||
0 ? (
|
0 ? (
|
||||||
<Link
|
<Link
|
||||||
key={condition.condition}
|
key={condition.condition}
|
||||||
data-testid="condition-type-link"
|
data-testid={`${condition.condition}-condition-link`}
|
||||||
to={toEditClientPolicyCondition({
|
to={toEditClientPolicyCondition({
|
||||||
realm,
|
realm,
|
||||||
conditionName: condition.condition!,
|
conditionName: condition.condition!,
|
||||||
|
@ -581,7 +581,7 @@ export default function NewClientPolicyForm() {
|
||||||
icon={
|
icon={
|
||||||
<TrashIcon
|
<TrashIcon
|
||||||
className="kc-conditionType-trash-icon"
|
className="kc-conditionType-trash-icon"
|
||||||
data-testid="deleteClientProfileDropdown"
|
data-testid={`delete-${condition.condition}-condition`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleDeleteConditionDialog();
|
toggleDeleteConditionDialog();
|
||||||
setConditionToDelete({
|
setConditionToDelete({
|
||||||
|
|
|
@ -252,3 +252,16 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
|
||||||
.kc_eventListeners_select {
|
.kc_eventListeners_select {
|
||||||
width: 35rem;
|
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