2021-03-19 12:43:32 +00:00
|
|
|
import React, { useState } from "react";
|
2020-11-24 20:11:13 +00:00
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import {
|
2020-12-04 21:08:11 +00:00
|
|
|
AlertVariant,
|
2020-11-24 20:11:13 +00:00
|
|
|
Button,
|
|
|
|
Dropdown,
|
|
|
|
DropdownItem,
|
|
|
|
DropdownToggle,
|
2020-12-07 13:42:08 +00:00
|
|
|
KebabToggle,
|
2020-11-24 20:11:13 +00:00
|
|
|
Select,
|
2020-12-07 13:42:08 +00:00
|
|
|
ToolbarItem,
|
2020-11-24 20:11:13 +00:00
|
|
|
} from "@patternfly/react-core";
|
|
|
|
import { FilterIcon } from "@patternfly/react-icons";
|
|
|
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
|
|
|
import KeycloakAdminClient from "keycloak-admin";
|
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
2021-01-15 01:44:16 +00:00
|
|
|
import { toUpperCase } from "../../util";
|
2020-11-24 20:11:13 +00:00
|
|
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
|
|
|
import { AddScopeDialog } from "./AddScopeDialog";
|
|
|
|
import {
|
|
|
|
clientScopeTypesSelectOptions,
|
|
|
|
ClientScopeType,
|
|
|
|
ClientScope,
|
2021-04-06 07:29:11 +00:00
|
|
|
CellDropdown,
|
|
|
|
} from "../../components/client-scope/ClientScopeTypes";
|
2020-12-04 21:08:11 +00:00
|
|
|
import { useAlerts } from "../../components/alert/Alerts";
|
2021-03-19 12:43:32 +00:00
|
|
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-04-07 05:42:30 +00:00
|
|
|
import "./client-scopes.css";
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
export type ClientScopesProps = {
|
|
|
|
clientId: string;
|
|
|
|
protocol: string;
|
|
|
|
};
|
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
type Row = ClientScopeRepresentation & {
|
|
|
|
type: ClientScopeType;
|
|
|
|
description: string;
|
|
|
|
};
|
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
|
|
|
(adminClient.clients as unknown) as {
|
|
|
|
[index: string]: Function;
|
|
|
|
};
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
const changeScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType,
|
|
|
|
changeTo: ClientScopeType
|
|
|
|
) => {
|
2020-12-04 21:08:11 +00:00
|
|
|
await removeScope(adminClient, clientId, clientScope, type);
|
|
|
|
await addScope(adminClient, clientId, clientScope, changeTo);
|
|
|
|
};
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2020-12-04 21:08:11 +00:00
|
|
|
const removeScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType
|
|
|
|
) => {
|
2021-01-15 01:44:16 +00:00
|
|
|
const typeToName = toUpperCase(type);
|
2020-12-04 21:08:11 +00:00
|
|
|
await castAdminClient(adminClient)[`del${typeToName}ClientScope`]({
|
2020-11-24 20:11:13 +00:00
|
|
|
id: clientId,
|
|
|
|
clientScopeId: clientScope.id!,
|
|
|
|
});
|
2020-12-04 21:08:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const addScope = async (
|
|
|
|
adminClient: KeycloakAdminClient,
|
|
|
|
clientId: string,
|
|
|
|
clientScope: ClientScopeRepresentation,
|
|
|
|
type: ClientScopeType
|
|
|
|
) => {
|
2021-01-15 01:44:16 +00:00
|
|
|
const typeToName = toUpperCase(type);
|
2020-12-04 21:08:11 +00:00
|
|
|
await castAdminClient(adminClient)[`add${typeToName}ClientScope`]({
|
2020-11-24 20:11:13 +00:00
|
|
|
id: clientId,
|
|
|
|
clientScopeId: clientScope.id!,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
type SearchType = "client" | "assigned";
|
|
|
|
|
|
|
|
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
|
|
|
const { t } = useTranslation("clients");
|
|
|
|
const adminClient = useAdminClient();
|
2020-12-04 21:08:11 +00:00
|
|
|
const { addAlert } = useAlerts();
|
|
|
|
|
2020-11-24 20:11:13 +00:00
|
|
|
const [searchToggle, setSearchToggle] = useState(false);
|
|
|
|
const [searchType, setSearchType] = useState<SearchType>("client");
|
|
|
|
const [addToggle, setAddToggle] = useState(false);
|
|
|
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
2020-12-07 13:42:08 +00:00
|
|
|
const [kebabOpen, setKebabOpen] = useState(false);
|
2020-11-24 20:11:13 +00:00
|
|
|
|
|
|
|
const [rest, setRest] = useState<ClientScopeRepresentation[]>();
|
2021-03-19 12:43:32 +00:00
|
|
|
const [selectedRows, setSelectedRows] = useState<Row[]>([]);
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-01-05 13:39:27 +00:00
|
|
|
const [key, setKey] = useState(0);
|
|
|
|
const refresh = () => setKey(new Date().getTime());
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
const loader = async () => {
|
|
|
|
const defaultClientScopes = await adminClient.clients.listDefaultClientScopes(
|
|
|
|
{ id: clientId }
|
|
|
|
);
|
|
|
|
const optionalClientScopes = await adminClient.clients.listOptionalClientScopes(
|
|
|
|
{ id: clientId }
|
|
|
|
);
|
|
|
|
const clientScopes = await adminClient.clientScopes.find();
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
const find = (id: string) =>
|
|
|
|
clientScopes.find((clientScope) => id === clientScope.id)!;
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
const optional = optionalClientScopes.map((c) => {
|
|
|
|
const scope = find(c.id!);
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
type: ClientScope.optional,
|
|
|
|
description: scope.description,
|
|
|
|
} as Row;
|
|
|
|
});
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
const defaultScopes = defaultClientScopes.map((c) => {
|
|
|
|
const scope = find(c.id!);
|
|
|
|
return {
|
|
|
|
...c,
|
|
|
|
type: ClientScope.default,
|
|
|
|
description: scope.description,
|
|
|
|
} as Row;
|
|
|
|
});
|
2020-11-24 20:11:13 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
const rows = [...optional, ...defaultScopes];
|
|
|
|
const names = rows.map((row) => row.name);
|
|
|
|
setRest(
|
|
|
|
clientScopes
|
|
|
|
.filter((scope) => !names.includes(scope.name))
|
|
|
|
.filter((scope) => scope.protocol === protocol)
|
2021-01-05 13:39:27 +00:00
|
|
|
);
|
2020-12-04 21:08:11 +00:00
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
return rows;
|
|
|
|
};
|
|
|
|
|
|
|
|
const TypeSelector = (scope: Row) => (
|
|
|
|
<>
|
2020-11-24 20:11:13 +00:00
|
|
|
<CellDropdown
|
2021-03-19 12:43:32 +00:00
|
|
|
clientScope={scope}
|
|
|
|
type={scope.type}
|
2020-12-04 21:08:11 +00:00
|
|
|
onSelect={async (value) => {
|
|
|
|
try {
|
2021-04-06 07:29:11 +00:00
|
|
|
await changeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
scope,
|
|
|
|
scope.type,
|
|
|
|
value as ClientScope
|
|
|
|
);
|
2020-12-04 21:08:11 +00:00
|
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
2021-03-19 12:43:32 +00:00
|
|
|
refresh();
|
2020-12-04 21:08:11 +00:00
|
|
|
} catch (error) {
|
|
|
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
|
|
|
}
|
|
|
|
}}
|
2020-11-24 20:11:13 +00:00
|
|
|
/>
|
2021-03-19 12:43:32 +00:00
|
|
|
</>
|
|
|
|
);
|
2020-11-24 20:11:13 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2020-12-04 21:08:11 +00:00
|
|
|
{rest && (
|
|
|
|
<AddScopeDialog
|
|
|
|
clientScopes={rest}
|
|
|
|
open={addDialogOpen}
|
|
|
|
toggleDialog={() => setAddDialogOpen(!addDialogOpen)}
|
|
|
|
onAdd={async (scopes) => {
|
|
|
|
try {
|
|
|
|
await Promise.all(
|
|
|
|
scopes.map(
|
|
|
|
async (scope) =>
|
|
|
|
await addScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
scope.scope,
|
|
|
|
scope.type
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
2021-01-05 13:39:27 +00:00
|
|
|
refresh();
|
2020-12-04 21:08:11 +00:00
|
|
|
} catch (error) {
|
|
|
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
2021-03-19 12:43:32 +00:00
|
|
|
<KeycloakDataTable
|
|
|
|
key={key}
|
|
|
|
loader={loader}
|
|
|
|
ariaLabelKey="clients:clientScopeList"
|
|
|
|
searchPlaceholderKey="clients:searchByName"
|
2021-04-07 05:42:30 +00:00
|
|
|
canSelectAll
|
2021-03-19 12:43:32 +00:00
|
|
|
onSelect={(rows) => setSelectedRows([...rows])}
|
|
|
|
searchTypeComponent={
|
|
|
|
<Dropdown
|
2021-04-07 05:42:30 +00:00
|
|
|
className="keycloak__client-scopes__searchtype"
|
2021-03-19 12:43:32 +00:00
|
|
|
toggle={
|
|
|
|
<DropdownToggle
|
|
|
|
id="toggle-id"
|
|
|
|
onToggle={() => setSearchToggle(!searchToggle)}
|
|
|
|
>
|
|
|
|
<FilterIcon /> {t(`clientScopeSearch.${searchType}`)}
|
|
|
|
</DropdownToggle>
|
|
|
|
}
|
|
|
|
aria-label="Select Input"
|
|
|
|
isOpen={searchToggle}
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
key="client"
|
|
|
|
onClick={() => {
|
|
|
|
setSearchType("client");
|
|
|
|
setSearchToggle(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("clientScopeSearch.client")}
|
|
|
|
</DropdownItem>,
|
|
|
|
<DropdownItem
|
|
|
|
key="assigned"
|
|
|
|
onClick={() => {
|
|
|
|
setSearchType("assigned");
|
|
|
|
setSearchToggle(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("clientScopeSearch.assigned")}
|
|
|
|
</DropdownItem>,
|
2020-12-04 21:08:11 +00:00
|
|
|
]}
|
2021-03-19 12:43:32 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
toolbarItem={
|
|
|
|
<>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Button onClick={() => setAddDialogOpen(true)}>
|
|
|
|
{t("addClientScope")}
|
|
|
|
</Button>
|
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Select
|
|
|
|
id="add-dropdown"
|
|
|
|
key="add-dropdown"
|
|
|
|
isOpen={addToggle}
|
|
|
|
selections={[]}
|
2021-04-07 05:42:30 +00:00
|
|
|
isDisabled={selectedRows.length === 0}
|
2021-03-19 12:43:32 +00:00
|
|
|
placeholderText={t("changeTypeTo")}
|
|
|
|
onToggle={() => setAddToggle(!addToggle)}
|
|
|
|
onSelect={async (_, value) => {
|
2020-12-04 21:08:11 +00:00
|
|
|
try {
|
2021-03-19 12:43:32 +00:00
|
|
|
await Promise.all(
|
|
|
|
selectedRows.map((row) => {
|
|
|
|
return changeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
{ ...row },
|
|
|
|
row.type,
|
|
|
|
value as ClientScope
|
|
|
|
);
|
|
|
|
})
|
2020-12-04 21:08:11 +00:00
|
|
|
);
|
2021-03-19 12:43:32 +00:00
|
|
|
setAddToggle(false);
|
2021-01-05 13:39:27 +00:00
|
|
|
refresh();
|
2021-03-19 12:43:32 +00:00
|
|
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
2020-12-04 21:08:11 +00:00
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
2021-03-19 12:43:32 +00:00
|
|
|
t("clientScopeError", { error }),
|
2020-12-04 21:08:11 +00:00
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
2021-03-19 12:43:32 +00:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{clientScopeTypesSelectOptions(t)}
|
|
|
|
</Select>
|
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Dropdown
|
|
|
|
onSelect={() => {}}
|
|
|
|
toggle={
|
|
|
|
<KebabToggle onToggle={() => setKebabOpen(!kebabOpen)} />
|
|
|
|
}
|
|
|
|
isOpen={kebabOpen}
|
|
|
|
isPlain
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
key="deleteAll"
|
|
|
|
isDisabled={selectedRows.length === 0}
|
|
|
|
onClick={async () => {
|
|
|
|
try {
|
|
|
|
await Promise.all(
|
|
|
|
selectedRows.map(async (row) => {
|
|
|
|
await removeScope(
|
|
|
|
adminClient,
|
|
|
|
clientId,
|
|
|
|
{ ...row },
|
|
|
|
row.type
|
|
|
|
);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
setKebabOpen(false);
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveSuccess"),
|
|
|
|
AlertVariant.success
|
|
|
|
);
|
|
|
|
refresh();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveError", { error }),
|
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("common:remove")}
|
|
|
|
</DropdownItem>,
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</ToolbarItem>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
columns={[
|
|
|
|
{
|
|
|
|
name: "name",
|
2021-04-07 05:42:30 +00:00
|
|
|
displayKey: "clients:assignedClientScope",
|
2021-03-19 12:43:32 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "type",
|
|
|
|
displayKey: "clients:assignedType",
|
|
|
|
cellRenderer: TypeSelector,
|
|
|
|
},
|
|
|
|
{ name: "description" },
|
|
|
|
]}
|
2021-04-07 05:42:30 +00:00
|
|
|
actions={[
|
|
|
|
{
|
|
|
|
title: t("common:remove"),
|
|
|
|
onRowClick: async (row) => {
|
|
|
|
try {
|
|
|
|
await removeScope(adminClient, clientId, row, row.type);
|
|
|
|
addAlert(t("clientScopeRemoveSuccess"), AlertVariant.success);
|
|
|
|
refresh();
|
|
|
|
} catch (error) {
|
|
|
|
addAlert(
|
|
|
|
t("clientScopeRemoveError", { error }),
|
|
|
|
AlertVariant.danger
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]}
|
2021-03-19 12:43:32 +00:00
|
|
|
emptyState={
|
|
|
|
<ListEmptyState
|
|
|
|
message={t("clients:emptyClientScopes")}
|
|
|
|
instructions={t("clients:emptyClientScopesInstructions")}
|
|
|
|
primaryActionText={t("clients:emptyClientScopesPrimaryAction")}
|
|
|
|
onPrimaryAction={() => setAddDialogOpen(true)}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
2020-11-24 20:11:13 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|