From bb21001d745da86e9c80384ee859c9eeca13d720 Mon Sep 17 00:00:00 2001 From: mfrances Date: Tue, 16 Mar 2021 10:24:32 -0400 Subject: [PATCH 01/17] add mappers tab --- src/client-scopes/form/ClientScopeForm.tsx | 2 +- src/client-scopes/messages.json | 1 - src/common-messages.json | 1 + src/route-config.ts | 2 +- .../UserFederationLdapSettings.tsx | 95 +++++++++++-------- 5 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/client-scopes/form/ClientScopeForm.tsx b/src/client-scopes/form/ClientScopeForm.tsx index d1a8a723e1..6a5db0f93f 100644 --- a/src/client-scopes/form/ClientScopeForm.tsx +++ b/src/client-scopes/form/ClientScopeForm.tsx @@ -322,7 +322,7 @@ export const ClientScopeForm = () => { {t("mappers")}} + title={{t("common:mappers")}} > {clientScope && ( diff --git a/src/client-scopes/messages.json b/src/client-scopes/messages.json index 2d143b887e..b8b95403e5 100644 --- a/src/client-scopes/messages.json +++ b/src/client-scopes/messages.json @@ -28,7 +28,6 @@ "createError": "Could not create client scope: '{{error}}'", "updateSuccess": "Client scope updated", "updateError": "Could not update client scope: '{{error}}'", - "mappers": "Mappers", "mappersSearchFor": "Search for mapper", "addMapper": "Add mapper", "addMapperExplain": "If you want more fine-grain control, you can create protocol mapper on this client", diff --git a/src/common-messages.json b/src/common-messages.json index 9b28ff89a2..85134402bb 100644 --- a/src/common-messages.json +++ b/src/common-messages.json @@ -64,6 +64,7 @@ "groups": "Groups", "sessions": "Sessions", "events": "Events", + "mappers": "Mappers", "configure": "Configure", "realmSettings": "Realm settings", diff --git a/src/route-config.ts b/src/route-config.ts index 056a3603fb..39075781cb 100644 --- a/src/route-config.ts +++ b/src/route-config.ts @@ -219,7 +219,7 @@ export const routes: RoutesFn = (t: TFunction) => [ access: "view-realm", }, { - path: "/:realm/user-federation/ldap/:id", + path: "/:realm/user-federation/ldap/:id/:tab?", component: UserFederationLdapSettings, breadcrumb: t("common:settings"), access: "view-realm", diff --git a/src/user-federation/UserFederationLdapSettings.tsx b/src/user-federation/UserFederationLdapSettings.tsx index b15ba8ffb5..d6b78b7c7a 100644 --- a/src/user-federation/UserFederationLdapSettings.tsx +++ b/src/user-federation/UserFederationLdapSettings.tsx @@ -8,6 +8,9 @@ import { DropdownSeparator, Form, PageSection, + Tab, + TabTitleText, + Text, } from "@patternfly/react-core"; import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced"; @@ -31,6 +34,8 @@ import { ViewHeader } from "../components/view-header/ViewHeader"; import { useHistory, useParams } from "react-router-dom"; import { ScrollForm } from "../components/scroll-form/ScrollForm"; +import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; + type LdapSettingsHeaderProps = { onChange: (value: string) => void; value: string; @@ -276,44 +281,60 @@ export const UserFederationLdapSettings = () => { )} /> - - - - - - - - - -
- - - - -
+ + + + + + + + +
+ + + + +
+
+ {t("common:mappers")}} + > + {/* */} + Coming soon! + + ); From 4593a3fa2abc429b540ce81aeb2498decf92374c Mon Sep 17 00:00:00 2001 From: mfrances Date: Tue, 16 Mar 2021 15:09:02 -0400 Subject: [PATCH 02/17] control debug visibliity with other kerberos --- .../ldap/LdapSettingsKerberosIntegration.tsx | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx b/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx index 547d0467ac..42cc57983b 100644 --- a/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx +++ b/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx @@ -169,39 +169,38 @@ export const LdapSettingsKerberosIntegration = ({ )} + + + } + fieldId="kc-debug" + hasNoPaddingTop + > + {" "} + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + )} - - - } - fieldId="kc-debug" - hasNoPaddingTop - > - {" "} - ( - onChange([`${value}`])} - isChecked={value[0] === "true"} - label={t("common:on")} - labelOff={t("common:off")} - /> - )} - > - - Date: Tue, 16 Mar 2021 16:10:31 -0400 Subject: [PATCH 03/17] fix isDirty issue --- src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx b/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx index 42cc57983b..ebc348f949 100644 --- a/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx +++ b/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx @@ -23,7 +23,7 @@ export const LdapSettingsKerberosIntegration = ({ const allowKerberosAuth: [string] = useWatch({ control: form.control, name: "config.allowKerberosAuthentication", - defaultValue: ["true"], + defaultValue: ["false"], }); return ( From 95c22fd86224f459df30fe3636ffdc134db740c4 Mon Sep 17 00:00:00 2001 From: mfrances Date: Mon, 22 Mar 2021 17:04:40 -0400 Subject: [PATCH 04/17] preliminary work - list working --- .../UserFederationLdapSettings.tsx | 5 +- .../ldap/mappers/LdapMapperDialog.tsx | 201 ++++++++++ .../ldap/mappers/LdapMapperList.tsx | 142 +++++++ .../ldap/mappers/LdapMappingDetails.tsx | 376 ++++++++++++++++++ .../ldap/mappers/LdapRoleMappingForm.tsx | 314 +++++++++++++++ 5 files changed, 1035 insertions(+), 3 deletions(-) create mode 100644 src/user-federation/ldap/mappers/LdapMapperDialog.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperList.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMappingDetails.tsx create mode 100644 src/user-federation/ldap/mappers/LdapRoleMappingForm.tsx diff --git a/src/user-federation/UserFederationLdapSettings.tsx b/src/user-federation/UserFederationLdapSettings.tsx index d6b78b7c7a..686a13a668 100644 --- a/src/user-federation/UserFederationLdapSettings.tsx +++ b/src/user-federation/UserFederationLdapSettings.tsx @@ -10,7 +10,6 @@ import { PageSection, Tab, TabTitleText, - Text, } from "@patternfly/react-core"; import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced"; @@ -35,6 +34,7 @@ import { useHistory, useParams } from "react-router-dom"; import { ScrollForm } from "../components/scroll-form/ScrollForm"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; +import {LdapMapperList} from "./ldap/mappers/LdapMapperList"; type LdapSettingsHeaderProps = { onChange: (value: string) => void; @@ -331,8 +331,7 @@ export const UserFederationLdapSettings = () => { eventKey="mappers" title={{t("common:mappers")}} > - {/* */} - Coming soon! + diff --git a/src/user-federation/ldap/mappers/LdapMapperDialog.tsx b/src/user-federation/ldap/mappers/LdapMapperDialog.tsx new file mode 100644 index 0000000000..e94b741e00 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperDialog.tsx @@ -0,0 +1,201 @@ +import React, { useState } from "react"; +import { + Button, + ButtonVariant, + DataList, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + Modal, + ModalVariant, + Text, + TextContent, +} from "@patternfly/react-core"; +import { + Table, + TableBody, + TableHeader, + TableVariant, +} from "@patternfly/react-table"; +import { useTranslation } from "react-i18next"; +import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; +import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation"; + +import { useServerInfo } from "../../../context/server-info/ServerInfoProvider"; +import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState"; + +// export type AddLdapMapperDialogModalProps = { +// protocol: string; +// filter?: ProtocolMapperRepresentation[]; +// onConfirm: ( +// value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] +// ) => void; +// }; + +export type AddLdapMapperDialogProps = { + open: boolean; + toggleDialog: () => void; +}; + +export const AddLdapMapperDialog = (props: AddLdapMapperDialogProps) => { + const { t } = useTranslation("client-scopes"); + + const serverInfo = useServerInfo(); + // const protocol = props.protocol; + // const protocolMappers = serverInfo.protocolMapperTypes![protocol]; + // const builtInMappers = serverInfo.builtinProtocolMappers![protocol]; + // const [filter, setFilter] = useState([]); + + // const allRows = builtInMappers.map((mapper) => { + // const mapperType = protocolMappers.filter( + // (type) => type.id === mapper.protocolMapper + // )[0]; + // return { + // item: mapper, + // selected: false, + // cells: [mapper.name, mapperType.helpText], + // }; + // }); + const [rows, setRows] = useState(allRows); + + // if (props.filter && props.filter.length !== filter.length) { + // setFilter(props.filter); + // const nameFilter = props.filter.map((f) => f.name); + // setRows([...allRows.filter((row) => !nameFilter.includes(row.item.name))]); + // } + + // const selectedRows = rows + // .filter((row) => row.selected) + // .map((row) => row.item); + + //const isBuiltIn = !!props.filter; + const isBuiltIn = true; + + const mapperList = [ + { + "id":"699b72e5-b936-41b9-98fc-5d5b3ec5ea6f", + "name":"username", + "providerId":"user-attribute-ldap-mapper", + "providerType":"org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "parentId":"f1da61f9-08f7-4dc5-83dd-d774fba518d4", + "config": + { + "ldap.attribute":["cn"], + "is.mandatory.in.ldap":["true"], + "always.read.value.from.ldap":["false"], + "read.only":["true"], + "user.model.attribute":["username"] + } + }, + { + "id":"c11788d2-62be-442f-813f-4b00382a1e10", + "name":"last name", + "providerId":"user-attribute-ldap-mapper", + "providerType":"org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "parentId":"f1da61f9-08f7-4dc5-83dd-d774fba518d4", + "config": + { + "ldap.attribute":["sn"], + "is.mandatory.in.ldap":["true"], + "always.read.value.from.ldap":["true"], + "read.only":["true"], + "user.model.attribute":["lastName"] + } + } + ] + + return ( + { + // props.onConfirm(selectedRows); + props.toggleDialog(); + }} + > + {t("common:add")} + , + , + ] + : [] + } + > + + {t("predefinedMappingDescription")} + + {!isBuiltIn && ( + { + const mapper = mapperList.find((mapper) => mapper.id === id); + // props.onConfirm(mapper!); + props.toggleDialog(); + }} + aria-label={t("chooseAMapperType")} + isCompact + > + {( + + + + <>{mapperList[0].name} + , + + <>{mapperList[0].helpText} + , + ]} + /> + + + ))} + + )} + {isBuiltIn && rows.length > 0 && ( + { + rows[rowIndex].selected = isSelected; + setRows([...rows]); + }} + canSelectAll={false} + rows={rows} + aria-label={t("chooseAMapperType")} + > + + +
+ )} + {isBuiltIn && rows.length === 0 && ( + + )} +
+ ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx new file mode 100644 index 0000000000..5caf577318 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -0,0 +1,142 @@ +import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { AlertVariant } from "@patternfly/react-core"; +import { + Table, + TableBody, + TableHeader, + TableVariant, +} from "@patternfly/react-table"; +import { useErrorHandler } from "react-error-boundary"; + +import { TableToolbar } from "../../../components/table-toolbar/TableToolbar"; +import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState"; +import { useAlerts } from "../../../components/alert/Alerts"; +import { + useAdminClient, + asyncStateFetch, +} from "../../../context/auth/AdminClient"; + +import { useParams } from "react-router-dom"; + +interface ComponentMapperRepresentation { + config?: Record; + id?: string; + name?: string; + providerId?: string; + providerType?: string; + parentID?: string; +} + +type Row = { + name: JSX.Element; + type: string; +}; + +export const LdapMapperList = () => { + const [mappers, setMappers] = useState(); + + const { t } = useTranslation("client-scopes"); + const adminClient = useAdminClient(); + const { addAlert } = useAlerts(); + const handleError = useErrorHandler(); + const [key, setKey] = useState(0); + + const { id } = useParams<{ id: string }>(); + + useEffect(() => { + return asyncStateFetch( + () => { + const testParams: { [name: string]: string | number } = { + parent: id, + type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + }; + return adminClient.components.find(testParams); + }, + (mappers) => { + setMappers(mappers); + console.log(mappers); + }, + handleError + ); + }, [key]); + + if (!mappers) { + return ( + <> + + + ); + } + + return ( + + { + return { + cells: Object.values([cell.name, cell.providerId]), + }; + })} + aria-label={t("clientScopeList")} + actions={[ + { + title: t("common:delete"), + onClick: () => { + addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + }, + }, + ]} + > + + +
+
+ ); +}; + +/* +Sample responses: + +const mapperList = [ + { + id: "699b72e5-b936-41b9-98fc-5d5b3ec5ea6f", + name: "username", + providerId: "user-attribute-ldap-mapper", + providerType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + parentId: "f1da61f9-08f7-4dc5-83dd-d774fba518d4", + config: { + "ldap.attribute": ["cn"], + "is.mandatory.in.ldap": ["true"], + "always.read.value.from.ldap": ["false"], + "read.only": ["true"], + "user.model.attribute": ["username"], + }, + }, + { + id: "c11788d2-62be-442f-813f-4b00382a1e10", + name: "last name", + providerId: "user-attribute-ldap-mapper", + providerType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + parentId: "f1da61f9-08f7-4dc5-83dd-d774fba518d4", + config: { + "ldap.attribute": ["sn"], + "is.mandatory.in.ldap": ["true"], + "always.read.value.from.ldap": ["true"], + "read.only": ["true"], + "user.model.attribute": ["lastName"], + }, + }, +]; +*/ diff --git a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx new file mode 100644 index 0000000000..0beb18ed86 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx @@ -0,0 +1,376 @@ +import React, { useEffect, useState } from "react"; +import { useHistory, useParams, useRouteMatch } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useErrorHandler } from "react-error-boundary"; +import { + ActionGroup, + AlertVariant, + Button, + ButtonVariant, + Checkbox, + DropdownItem, + Flex, + FlexItem, + FormGroup, + PageSection, + Select, + SelectOption, + SelectVariant, + Switch, + TextInput, + ValidatedOptions, +} from "@patternfly/react-core"; +import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation"; +import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; + +import { ViewHeader } from "../../../components/view-header/ViewHeader"; +import { + useAdminClient, + asyncStateFetch, +} from "../../../context/auth/AdminClient"; +import { Controller, useForm } from "react-hook-form"; +import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog"; +import { useAlerts } from "../../../components/alert/Alerts"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { useServerInfo } from "../../../context/server-info/ServerInfoProvider"; +import { convertFormValuesToObject, convertToFormValues } from "../../../util"; +import { FormAccess } from "../../../components/form-access/FormAccess"; + +type Params = { + id: string; + mapperId: string; +}; + +export const LdapMappingDetails = () => { + const { t } = useTranslation("client-scopes"); + const adminClient = useAdminClient(); + const handleError = useErrorHandler(); + const { addAlert } = useAlerts(); + + const { id, mapperId } = useParams(); + const { register, errors, setValue, control, handleSubmit } = useForm(); + const [mapping, setMapping] = useState(); + const [typeOpen, setTypeOpen] = useState(false); + const [configProperties, setConfigProperties] = useState< + ConfigPropertyRepresentation[] + >(); + + const history = useHistory(); + const serverInfo = useServerInfo(); + const { url } = useRouteMatch(); + const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/; + + useEffect(() => { + return asyncStateFetch( + async () => { + if (mapperId.match(isGuid)) { + const data = await adminClient.clientScopes.findProtocolMapper({ + id, + mapperId, + }); + if (data) { + Object.entries(data).map((entry) => { + convertToFormValues(entry[1], "config", setValue); + }); + } + const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; + const properties = mapperTypes.find( + (type) => type.id === data!.protocolMapper + )?.properties!; + + return { + configProperties: properties, + mapping: data, + }; + } else { + const scope = await adminClient.clientScopes.findOne({ id }); + const protocolMappers = serverInfo.protocolMapperTypes![ + scope.protocol! + ]; + const mapping = protocolMappers.find( + (mapper) => mapper.id === mapperId + )!; + return { + mapping: { + name: mapping.name, + protocol: scope.protocol, + protocolMapper: mapperId, + }, + }; + } + }, + (result) => { + setConfigProperties(result.configProperties); + setMapping(result.mapping); + }, + handleError + ); + }, []); + + const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + titleKey: "client-scopes:deleteMappingTitle", + messageKey: "client-scopes:deleteMappingConfirm", + continueButtonLabel: "common:delete", + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + try { + await adminClient.clientScopes.delClientScopeMappings( + { client: id, id: mapperId }, + [] + ); + addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + history.push(`${url}/${id}`); + } catch (error) { + addAlert(t("mappingDeletedError", { error }), AlertVariant.danger); + } + }, + }); + + const save = async (formMapping: ProtocolMapperRepresentation) => { + const config = convertFormValuesToObject(formMapping.config); + const map = { ...mapping, ...formMapping, config }; + const key = mapperId.match(isGuid) ? "Updated" : "Created"; + try { + if (mapperId.match(isGuid)) { + await adminClient.clientScopes.updateProtocolMapper( + { id, mapperId }, + map + ); + } else { + await adminClient.clientScopes.addProtocolMapper({ id }, map); + } + addAlert(t(`mapping${key}Success`), AlertVariant.success); + } catch (error) { + addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger); + } + }; + + return ( + <> + + + {t("common:delete")} + , + ] + : undefined + } + /> + + + <> + {!mapperId.match(isGuid) && ( + + } + fieldId="name" + isRequired + validated={ + errors.name + ? ValidatedOptions.error + : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + )} + + + } + fieldId="prefix" + > + + + + } + fieldId="multiValued" + > + ( + onChange("" + value)} + /> + )} + /> + + + } + fieldId="claimName" + > + + + + } + fieldId="claimJsonType" + > + ( + + )} + /> + + + + + ( + onChange("" + value)} + /> + )} + /> + + + ( + onChange("" + value)} + /> + )} + /> + + + ( + onChange("" + value)} + /> + )} + /> + + + + + + + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapRoleMappingForm.tsx b/src/user-federation/ldap/mappers/LdapRoleMappingForm.tsx new file mode 100644 index 0000000000..1e8b2e5e02 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapRoleMappingForm.tsx @@ -0,0 +1,314 @@ +import React, { useContext, useEffect, useState } from "react"; +import { useHistory, useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Controller, useForm } from "react-hook-form"; +import { useErrorHandler } from "react-error-boundary"; +import { + FormGroup, + PageSection, + Select, + SelectVariant, + TextInput, + SelectOption, + ActionGroup, + Button, + SelectGroup, + Split, + SplitItem, + Divider, + ValidatedOptions, +} from "@patternfly/react-core"; + +import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; +import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; +import { useAlerts } from "../../../components/alert/Alerts"; +import { RealmContext } from "../../../context/realm-context/RealmContext"; +import { + useAdminClient, + asyncStateFetch, +} from "../../../context/auth/AdminClient"; + +import { ViewHeader } from "../../../components/view-header/ViewHeader"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { FormAccess } from "../../../components/form-access/FormAccess"; + +export const RoleMappingForm = () => { + const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); + const handleError = useErrorHandler(); + const history = useHistory(); + const { addAlert } = useAlerts(); + + const { t } = useTranslation("client-scopes"); + const { register, handleSubmit, control, errors } = useForm(); + const { id } = useParams<{ id: string }>(); + + const [roleOpen, setRoleOpen] = useState(false); + + const [clientsOpen, setClientsOpen] = useState(false); + const [clients, setClients] = useState([]); + const [selectedClient, setSelectedClient] = useState(); + const [clientRoles, setClientRoles] = useState([]); + + useEffect(() => { + return asyncStateFetch( + async () => { + const clients = await adminClient.clients.find(); + + const asyncFilter = async ( + predicate: (client: ClientRepresentation) => Promise + ) => { + const results = await Promise.all(clients.map(predicate)); + return clients.filter((_, index) => results[index]); + }; + + const filteredClients = await asyncFilter( + async (client) => + (await adminClient.clients.listRoles({ id: client.id! })).length > 0 + ); + + filteredClients.map( + (client) => + (client.toString = function () { + return this.clientId!; + }) + ); + return filteredClients; + }, + (filteredClients) => setClients(filteredClients), + handleError + ); + }, []); + + useEffect(() => { + return asyncStateFetch( + async () => { + const client = selectedClient as ClientRepresentation; + if (client && client.name !== "realmRoles") { + const clientRoles = await adminClient.clients.listRoles({ + id: client.id!, + }); + return clientRoles; + } else { + return await adminClient.roles.find(); + } + }, + (clientRoles) => setClientRoles(clientRoles), + handleError + ); + }, [selectedClient]); + + const save = async (mapping: ProtocolMapperRepresentation) => { + try { + await adminClient.clientScopes.addProtocolMapper({ id }, mapping); + addAlert(t("mapperCreateSuccess")); + } catch (error) { + addAlert(t("mapperCreateError", error)); + } + }; + + const createSelectGroup = (clients: ClientRepresentation[]) => { + return [ + + t("realmRoles"), + } as ClientRepresentation + } + > + {realm} + + , + , + + {clients.map((client) => ( + + {client.clientId} + + ))} + , + ]; + }; + + const roleSelectOptions = () => { + const createItem = (role: RoleRepresentation) => ( + + {role.name} + + ); + return clientRoles.map((role) => createItem(role)); + }; + + return ( + <> + + + + + } + fieldId="protocolMapper" + > + + + + } + fieldId="name" + isRequired + validated={ + errors.name ? ValidatedOptions.error : ValidatedOptions.default + } + helperTextInvalid={t("common:required")} + > + + + + } + validated={errors["config.role"] ? "error" : "default"} + helperTextInvalid={t("common:required")} + fieldId="role" + > + + + + + + ( + + )} + /> + + + + + } + fieldId="newRoleName" + > + + + + + + + + + + ); +}; From f863f850d158aa1af497950b5fc30ea4294220e7 Mon Sep 17 00:00:00 2001 From: mfrances Date: Tue, 23 Mar 2021 17:15:46 -0400 Subject: [PATCH 05/17] form layout no logic --- src/route-config.ts | 7 + .../ldap/mappers/LdapMapperList.tsx | 256 +++++++++++++++--- .../ldap/mappers/LdapMappingDetails.tsx | 201 +++++++------- 3 files changed, 329 insertions(+), 135 deletions(-) diff --git a/src/route-config.ts b/src/route-config.ts index 39075781cb..643e59e7b8 100644 --- a/src/route-config.ts +++ b/src/route-config.ts @@ -29,6 +29,7 @@ import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm"; import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs"; import { SearchGroups } from "./groups/SearchGroups"; import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken"; +import { LdapMappingDetails } from "./user-federation/ldap/mappers/LdapMappingDetails"; export type RouteDef = BreadcrumbsRoute & { access: AccessType; @@ -224,6 +225,12 @@ export const routes: RoutesFn = (t: TFunction) => [ breadcrumb: t("common:settings"), access: "view-realm", }, + { + path: "/:realm/user-federation/ldap/:id/:tab/:mapperId", + component: LdapMappingDetails, + breadcrumb: t("client-scopes:mappingDetails"), + access: "view-clients", + }, { path: "/:realm/user-federation/ldap/new", component: UserFederationLdapSettings, diff --git a/src/user-federation/ldap/mappers/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx index 5caf577318..33de6509cd 100644 --- a/src/user-federation/ldap/mappers/LdapMapperList.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -1,15 +1,25 @@ import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { AlertVariant } from "@patternfly/react-core"; import { - Table, - TableBody, - TableHeader, - TableVariant, -} from "@patternfly/react-table"; -import { useErrorHandler } from "react-error-boundary"; + AlertVariant, + Button, + ToolbarItem, + // Dropdown, + // DropdownItem, + // DropdownToggle, +} from "@patternfly/react-core"; +import { CaretDownIcon } from "@patternfly/react-icons"; -import { TableToolbar } from "../../../components/table-toolbar/TableToolbar"; +// import { +// Table, +// TableBody, +// TableHeader, +// TableVariant, +// } from "@patternfly/react-table"; +import { useErrorHandler } from "react-error-boundary"; +import { KeycloakDataTable } from "../../../components/table-toolbar/KeycloakDataTable"; + +// import { TableToolbar } from "../../../components/table-toolbar/TableToolbar"; import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState"; import { useAlerts } from "../../../components/alert/Alerts"; import { @@ -17,7 +27,7 @@ import { asyncStateFetch, } from "../../../context/auth/AdminClient"; -import { useParams } from "react-router-dom"; +import { useParams, Link } from "react-router-dom"; interface ComponentMapperRepresentation { config?: Record; @@ -28,10 +38,10 @@ interface ComponentMapperRepresentation { parentID?: string; } -type Row = { - name: JSX.Element; - type: string; -}; +// type Row = ComponentMapperRepresentation { +// name: JSX.Element; +// type: string; +// }; export const LdapMapperList = () => { const [mappers, setMappers] = useState(); @@ -39,11 +49,14 @@ export const LdapMapperList = () => { const { t } = useTranslation("client-scopes"); const adminClient = useAdminClient(); const { addAlert } = useAlerts(); + +// const [mapperAction, setMapperAction] = useState(false); + const handleError = useErrorHandler(); const [key, setKey] = useState(0); const { id } = useParams<{ id: string }>(); - + useEffect(() => { return asyncStateFetch( () => { @@ -73,36 +86,205 @@ export const LdapMapperList = () => { ); } + const loader = async () => + Promise.resolve( + (mappers || []).map((mapper) => { + // const mapperType = mappers.filter( + // (type) => type.id === mapper.protocolMapper + // )[0]; + return { + ...mapper, + name: mapper.name, + type: mapper.providerId, + } as ComponentMapperRepresentation; + }) + // .sort((a, b) => a.priority - b.priority) + ); + + const url = "mappers"; + const MapperLink = (mapper: ComponentMapperRepresentation) => ( + <> + {mapper.name} + + ); + return ( - - { - return { - cells: Object.values([cell.name, cell.providerId]), - }; - })} - aria-label={t("clientScopeList")} + <> + + + + } + // setMapperAction(false)} + // toggle={ + // setMapperAction(!mapperAction)} + // toggleIndicator={CaretDownIcon} + // > + // {t("addMapper")} + // + // } + // isOpen={mapperAction} + // dropdownItems={[ + // + // addAlert(t("mappingCreatedSuccess"), AlertVariant.success) + // } + // > + // {t("fromPredefinedMapper")} + // , + // ]} + // /> + + // toolbarItem={ + // setMapperAction(false)} + // toggle={ + // setMapperAction(!mapperAction)} + // toggleIndicator={CaretDownIcon} + // > + // {t("addMapper")} + // + // } + // isOpen={mapperAction} + // dropdownItems={[ + // toggleAddMapperDialog(true)} + // > + // {t("fromPredefinedMapper")} + // , + // toggleAddMapperDialog(false)} + // > + // {t("byConfiguration")} + // , + // ]} + // /> + // } + // actions={[ + // { + // title: t("common:delete"), + // onRowClick: async (mapper) => { + // try { + // await adminClient.clientScopes.delProtocolMapper({ + // id: clientScope.id!, + // mapperId: mapper.id!, + // }); + // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + // refresh(); + // } catch (error) { + // addAlert( + // t("mappingDeletedError", { error }), + // AlertVariant.danger + // ); + // } + // return true; + // }, + // }, + // ]} actions={[ { title: t("common:delete"), - onClick: () => { - addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + onRowClick: async (mapper) => { + try { + // await adminClient.clientScopes.delProtocolMapper({ + // id: mapper.id!, + // mapperId: mapper.id!, + // }); + // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + addAlert( + "Delete functionality not implemented yet!", + AlertVariant.success + ); + // refresh(); + } catch (error) { + addAlert( + t("mappingDeletedError", { error }), + AlertVariant.danger + ); + } + return true; }, }, ]} - > - - -
-
+ columns={[ + { + name: "name", + cellRenderer: MapperLink, + }, + { + name: "type", + }, + ]} + emptyState={ + toggleAddMapperDialog(true)} + // secondaryActions={[ + // { + // text: t("emptySecondaryAction"), + // onClick: () => toggleAddMapperDialog(false), + // type: ButtonVariant.secondary, + // }, + // ]} + /> + } + /> + + + // + // { + // return { + // cells: Object.values([cell.name, cell.providerId]), + // }; + // })} + // aria-label={t("clientScopeList")} + // actions={[ + // { + // title: t("common:delete"), + // onClick: () => { + // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + // }, + // }, + // ]} + // > + // + // + //
+ //
); }; diff --git a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx index 0beb18ed86..43839a05fb 100644 --- a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx +++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx @@ -19,6 +19,7 @@ import { Switch, TextInput, ValidatedOptions, + Text } from "@patternfly/react-core"; import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation"; import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; @@ -42,112 +43,115 @@ type Params = { }; export const LdapMappingDetails = () => { - const { t } = useTranslation("client-scopes"); - const adminClient = useAdminClient(); - const handleError = useErrorHandler(); - const { addAlert } = useAlerts(); +// const { t } = useTranslation("client-scopes"); +// const adminClient = useAdminClient(); +// const handleError = useErrorHandler(); +// const { addAlert } = useAlerts(); - const { id, mapperId } = useParams(); - const { register, errors, setValue, control, handleSubmit } = useForm(); - const [mapping, setMapping] = useState(); - const [typeOpen, setTypeOpen] = useState(false); - const [configProperties, setConfigProperties] = useState< - ConfigPropertyRepresentation[] - >(); +// const { id, mapperId } = useParams(); +// const { register, errors, setValue, control, handleSubmit } = useForm(); +// const [mapping, setMapping] = useState(); +// const [typeOpen, setTypeOpen] = useState(false); +// const [configProperties, setConfigProperties] = useState< +// ConfigPropertyRepresentation[] +// >(); - const history = useHistory(); - const serverInfo = useServerInfo(); - const { url } = useRouteMatch(); - const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/; +// const history = useHistory(); +// const serverInfo = useServerInfo(); +// const { url } = useRouteMatch(); +// const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/; - useEffect(() => { - return asyncStateFetch( - async () => { - if (mapperId.match(isGuid)) { - const data = await adminClient.clientScopes.findProtocolMapper({ - id, - mapperId, - }); - if (data) { - Object.entries(data).map((entry) => { - convertToFormValues(entry[1], "config", setValue); - }); - } - const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; - const properties = mapperTypes.find( - (type) => type.id === data!.protocolMapper - )?.properties!; + // useEffect(() => { + // return asyncStateFetch( + // async () => { + // if (mapperId.match(isGuid)) { + // const data = await adminClient.clientScopes.findProtocolMapper({ + // id, + // mapperId, + // }); + // if (data) { + // Object.entries(data).map((entry) => { + // convertToFormValues(entry[1], "config", setValue); + // }); + // } + // const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; + // const properties = mapperTypes.find( + // (type) => type.id === data!.protocolMapper + // )?.properties!; - return { - configProperties: properties, - mapping: data, - }; - } else { - const scope = await adminClient.clientScopes.findOne({ id }); - const protocolMappers = serverInfo.protocolMapperTypes![ - scope.protocol! - ]; - const mapping = protocolMappers.find( - (mapper) => mapper.id === mapperId - )!; - return { - mapping: { - name: mapping.name, - protocol: scope.protocol, - protocolMapper: mapperId, - }, - }; - } - }, - (result) => { - setConfigProperties(result.configProperties); - setMapping(result.mapping); - }, - handleError - ); - }, []); + // return { + // configProperties: properties, + // mapping: data, + // }; + // } else { + // const scope = await adminClient.clientScopes.findOne({ id }); + // const protocolMappers = serverInfo.protocolMapperTypes![ + // scope.protocol! + // ]; + // const mapping = protocolMappers.find( + // (mapper) => mapper.id === mapperId + // )!; + // return { + // mapping: { + // name: mapping.name, + // protocol: scope.protocol, + // protocolMapper: mapperId, + // }, + // }; + // } + // }, + // (result) => { + // setConfigProperties(result.configProperties); + // setMapping(result.mapping); + // }, + // handleError + // ); + // }, []); - const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ - titleKey: "client-scopes:deleteMappingTitle", - messageKey: "client-scopes:deleteMappingConfirm", - continueButtonLabel: "common:delete", - continueButtonVariant: ButtonVariant.danger, - onConfirm: async () => { - try { - await adminClient.clientScopes.delClientScopeMappings( - { client: id, id: mapperId }, - [] - ); - addAlert(t("mappingDeletedSuccess"), AlertVariant.success); - history.push(`${url}/${id}`); - } catch (error) { - addAlert(t("mappingDeletedError", { error }), AlertVariant.danger); - } - }, - }); + // const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + // titleKey: "client-scopes:deleteMappingTitle", + // messageKey: "client-scopes:deleteMappingConfirm", + // continueButtonLabel: "common:delete", + // continueButtonVariant: ButtonVariant.danger, + // onConfirm: async () => { + // try { + // await adminClient.clientScopes.delClientScopeMappings( + // { client: id, id: mapperId }, + // [] + // ); + // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); + // history.push(`${url}/${id}`); + // } catch (error) { + // addAlert(t("mappingDeletedError", { error }), AlertVariant.danger); + // } + // }, + // }); - const save = async (formMapping: ProtocolMapperRepresentation) => { - const config = convertFormValuesToObject(formMapping.config); - const map = { ...mapping, ...formMapping, config }; - const key = mapperId.match(isGuid) ? "Updated" : "Created"; - try { - if (mapperId.match(isGuid)) { - await adminClient.clientScopes.updateProtocolMapper( - { id, mapperId }, - map - ); - } else { - await adminClient.clientScopes.addProtocolMapper({ id }, map); - } - addAlert(t(`mapping${key}Success`), AlertVariant.success); - } catch (error) { - addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger); - } - }; + // const save = async (formMapping: ProtocolMapperRepresentation) => { + // const config = convertFormValuesToObject(formMapping.config); + // const map = { ...mapping, ...formMapping, config }; + // const key = mapperId.match(isGuid) ? "Updated" : "Created"; + // try { + // if (mapperId.match(isGuid)) { + // await adminClient.clientScopes.updateProtocolMapper( + // { id, mapperId }, + // map + // ); + // } else { + // await adminClient.clientScopes.addProtocolMapper({ id }, map); + // } + // addAlert(t(`mapping${key}Success`), AlertVariant.success); + // } catch (error) { + // addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger); + // } + // }; return ( <> - + + Coming soon! + + {/* { - + */} + ); }; From 2a7782fbae1e14253f094903476d9ba375a5e701 Mon Sep 17 00:00:00 2001 From: mfrances Date: Tue, 23 Mar 2021 17:24:57 -0400 Subject: [PATCH 06/17] fix url and rm hack --- src/user-federation/ldap/mappers/LdapMapperList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/user-federation/ldap/mappers/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx index 33de6509cd..87702e6498 100644 --- a/src/user-federation/ldap/mappers/LdapMapperList.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -27,7 +27,7 @@ import { asyncStateFetch, } from "../../../context/auth/AdminClient"; -import { useParams, Link } from "react-router-dom"; +import { Link, useParams, useRouteMatch } from "react-router-dom"; interface ComponentMapperRepresentation { config?: Record; @@ -50,6 +50,8 @@ export const LdapMapperList = () => { const adminClient = useAdminClient(); const { addAlert } = useAlerts(); + const { url } = useRouteMatch(); + // const [mapperAction, setMapperAction] = useState(false); const handleError = useErrorHandler(); @@ -101,7 +103,6 @@ export const LdapMapperList = () => { // .sort((a, b) => a.priority - b.priority) ); - const url = "mappers"; const MapperLink = (mapper: ComponentMapperRepresentation) => ( <> {mapper.name} From 2e7f38d9a54bf1ebfdd896a74af9bb497ba8d54c Mon Sep 17 00:00:00 2001 From: mfrances Date: Mon, 29 Mar 2021 11:52:56 -0400 Subject: [PATCH 07/17] mapper list and edit mapper working --- src/route-config.ts | 2 +- .../UserFederationLdapSettings.tsx | 2 +- .../ldap/mappers/LdapMapperDialog.tsx | 201 -------- .../ldap/mappers/LdapMapperList.tsx | 241 ++-------- .../ldap/mappers/LdapMapperUsername.tsx | 123 +++++ .../ldap/mappers/LdapMappingDetails.tsx | 433 +++--------------- 6 files changed, 229 insertions(+), 773 deletions(-) delete mode 100644 src/user-federation/ldap/mappers/LdapMapperDialog.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperUsername.tsx diff --git a/src/route-config.ts b/src/route-config.ts index 643e59e7b8..11ec5668e4 100644 --- a/src/route-config.ts +++ b/src/route-config.ts @@ -229,7 +229,7 @@ export const routes: RoutesFn = (t: TFunction) => [ path: "/:realm/user-federation/ldap/:id/:tab/:mapperId", component: LdapMappingDetails, breadcrumb: t("client-scopes:mappingDetails"), - access: "view-clients", + access: "view-realm", }, { path: "/:realm/user-federation/ldap/new", diff --git a/src/user-federation/UserFederationLdapSettings.tsx b/src/user-federation/UserFederationLdapSettings.tsx index 686a13a668..a87ebe6e2c 100644 --- a/src/user-federation/UserFederationLdapSettings.tsx +++ b/src/user-federation/UserFederationLdapSettings.tsx @@ -34,7 +34,7 @@ import { useHistory, useParams } from "react-router-dom"; import { ScrollForm } from "../components/scroll-form/ScrollForm"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; -import {LdapMapperList} from "./ldap/mappers/LdapMapperList"; +import { LdapMapperList } from "./ldap/mappers/LdapMapperList"; type LdapSettingsHeaderProps = { onChange: (value: string) => void; diff --git a/src/user-federation/ldap/mappers/LdapMapperDialog.tsx b/src/user-federation/ldap/mappers/LdapMapperDialog.tsx deleted file mode 100644 index e94b741e00..0000000000 --- a/src/user-federation/ldap/mappers/LdapMapperDialog.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React, { useState } from "react"; -import { - Button, - ButtonVariant, - DataList, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, - Modal, - ModalVariant, - Text, - TextContent, -} from "@patternfly/react-core"; -import { - Table, - TableBody, - TableHeader, - TableVariant, -} from "@patternfly/react-table"; -import { useTranslation } from "react-i18next"; -import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; -import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation"; - -import { useServerInfo } from "../../../context/server-info/ServerInfoProvider"; -import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState"; - -// export type AddLdapMapperDialogModalProps = { -// protocol: string; -// filter?: ProtocolMapperRepresentation[]; -// onConfirm: ( -// value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] -// ) => void; -// }; - -export type AddLdapMapperDialogProps = { - open: boolean; - toggleDialog: () => void; -}; - -export const AddLdapMapperDialog = (props: AddLdapMapperDialogProps) => { - const { t } = useTranslation("client-scopes"); - - const serverInfo = useServerInfo(); - // const protocol = props.protocol; - // const protocolMappers = serverInfo.protocolMapperTypes![protocol]; - // const builtInMappers = serverInfo.builtinProtocolMappers![protocol]; - // const [filter, setFilter] = useState([]); - - // const allRows = builtInMappers.map((mapper) => { - // const mapperType = protocolMappers.filter( - // (type) => type.id === mapper.protocolMapper - // )[0]; - // return { - // item: mapper, - // selected: false, - // cells: [mapper.name, mapperType.helpText], - // }; - // }); - const [rows, setRows] = useState(allRows); - - // if (props.filter && props.filter.length !== filter.length) { - // setFilter(props.filter); - // const nameFilter = props.filter.map((f) => f.name); - // setRows([...allRows.filter((row) => !nameFilter.includes(row.item.name))]); - // } - - // const selectedRows = rows - // .filter((row) => row.selected) - // .map((row) => row.item); - - //const isBuiltIn = !!props.filter; - const isBuiltIn = true; - - const mapperList = [ - { - "id":"699b72e5-b936-41b9-98fc-5d5b3ec5ea6f", - "name":"username", - "providerId":"user-attribute-ldap-mapper", - "providerType":"org.keycloak.storage.ldap.mappers.LDAPStorageMapper", - "parentId":"f1da61f9-08f7-4dc5-83dd-d774fba518d4", - "config": - { - "ldap.attribute":["cn"], - "is.mandatory.in.ldap":["true"], - "always.read.value.from.ldap":["false"], - "read.only":["true"], - "user.model.attribute":["username"] - } - }, - { - "id":"c11788d2-62be-442f-813f-4b00382a1e10", - "name":"last name", - "providerId":"user-attribute-ldap-mapper", - "providerType":"org.keycloak.storage.ldap.mappers.LDAPStorageMapper", - "parentId":"f1da61f9-08f7-4dc5-83dd-d774fba518d4", - "config": - { - "ldap.attribute":["sn"], - "is.mandatory.in.ldap":["true"], - "always.read.value.from.ldap":["true"], - "read.only":["true"], - "user.model.attribute":["lastName"] - } - } - ] - - return ( - { - // props.onConfirm(selectedRows); - props.toggleDialog(); - }} - > - {t("common:add")} - , - , - ] - : [] - } - > - - {t("predefinedMappingDescription")} - - {!isBuiltIn && ( - { - const mapper = mapperList.find((mapper) => mapper.id === id); - // props.onConfirm(mapper!); - props.toggleDialog(); - }} - aria-label={t("chooseAMapperType")} - isCompact - > - {( - - - - <>{mapperList[0].name} - , - - <>{mapperList[0].helpText} - , - ]} - /> - - - ))} - - )} - {isBuiltIn && rows.length > 0 && ( - { - rows[rowIndex].selected = isSelected; - setRows([...rows]); - }} - canSelectAll={false} - rows={rows} - aria-label={t("chooseAMapperType")} - > - - -
- )} - {isBuiltIn && rows.length === 0 && ( - - )} -
- ); -}; diff --git a/src/user-federation/ldap/mappers/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx index 87702e6498..c32a6ad114 100644 --- a/src/user-federation/ldap/mappers/LdapMapperList.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -1,50 +1,19 @@ import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { - AlertVariant, - Button, - ToolbarItem, - // Dropdown, - // DropdownItem, - // DropdownToggle, -} from "@patternfly/react-core"; -import { CaretDownIcon } from "@patternfly/react-icons"; - -// import { -// Table, -// TableBody, -// TableHeader, -// TableVariant, -// } from "@patternfly/react-table"; +import { AlertVariant, Button, ToolbarItem } from "@patternfly/react-core"; +import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import { useErrorHandler } from "react-error-boundary"; import { KeycloakDataTable } from "../../../components/table-toolbar/KeycloakDataTable"; - -// import { TableToolbar } from "../../../components/table-toolbar/TableToolbar"; import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState"; import { useAlerts } from "../../../components/alert/Alerts"; import { useAdminClient, asyncStateFetch, } from "../../../context/auth/AdminClient"; - import { Link, useParams, useRouteMatch } from "react-router-dom"; -interface ComponentMapperRepresentation { - config?: Record; - id?: string; - name?: string; - providerId?: string; - providerType?: string; - parentID?: string; -} - -// type Row = ComponentMapperRepresentation { -// name: JSX.Element; -// type: string; -// }; - export const LdapMapperList = () => { - const [mappers, setMappers] = useState(); + const [mappers, setMappers] = useState(); const { t } = useTranslation("client-scopes"); const adminClient = useAdminClient(); @@ -52,8 +21,6 @@ export const LdapMapperList = () => { const { url } = useRouteMatch(); -// const [mapperAction, setMapperAction] = useState(false); - const handleError = useErrorHandler(); const [key, setKey] = useState(0); @@ -62,7 +29,9 @@ export const LdapMapperList = () => { useEffect(() => { return asyncStateFetch( () => { - const testParams: { [name: string]: string | number } = { + const testParams: { + [name: string]: string | number; + } = { parent: id, type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", }; @@ -70,6 +39,8 @@ export const LdapMapperList = () => { }, (mappers) => { setMappers(mappers); + // TODO: remove after debugging + console.log("LdapMapperList - setMappers being set with:"); console.log(mappers); }, handleError @@ -91,21 +62,17 @@ export const LdapMapperList = () => { const loader = async () => Promise.resolve( (mappers || []).map((mapper) => { - // const mapperType = mappers.filter( - // (type) => type.id === mapper.protocolMapper - // )[0]; return { ...mapper, name: mapper.name, type: mapper.providerId, - } as ComponentMapperRepresentation; + } as ComponentRepresentation; }) - // .sort((a, b) => a.priority - b.priority) ); - const MapperLink = (mapper: ComponentMapperRepresentation) => ( + const MapperLink = (mapper: ComponentRepresentation) => ( <> - {mapper.name} + {mapper.name} ); @@ -116,110 +83,33 @@ export const LdapMapperList = () => { loader={loader} ariaLabelKey="client-scopes:clientScopeList" searchPlaceholderKey="client-scopes:mappersSearchFor" - toolbarItem={ - - - - } - // setMapperAction(false)} - // toggle={ - // setMapperAction(!mapperAction)} - // toggleIndicator={CaretDownIcon} - // > - // {t("addMapper")} - // - // } - // isOpen={mapperAction} - // dropdownItems={[ - // - // addAlert(t("mappingCreatedSuccess"), AlertVariant.success) - // } - // > - // {t("fromPredefinedMapper")} - // , - // ]} - // /> - - // toolbarItem={ - // setMapperAction(false)} - // toggle={ - // setMapperAction(!mapperAction)} - // toggleIndicator={CaretDownIcon} - // > - // {t("addMapper")} - // - // } - // isOpen={mapperAction} - // dropdownItems={[ - // toggleAddMapperDialog(true)} - // > - // {t("fromPredefinedMapper")} - // , - // toggleAddMapperDialog(false)} - // > - // {t("byConfiguration")} - // , - // ]} - // /> - // } - // actions={[ - // { - // title: t("common:delete"), - // onRowClick: async (mapper) => { - // try { - // await adminClient.clientScopes.delProtocolMapper({ - // id: clientScope.id!, - // mapperId: mapper.id!, - // }); - // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); - // refresh(); - // } catch (error) { - // addAlert( - // t("mappingDeletedError", { error }), - // AlertVariant.danger - // ); - // } - // return true; - // }, - // }, - // ]} + toolbarItem={ + + + + } actions={[ { title: t("common:delete"), onRowClick: async (mapper) => { try { - // await adminClient.clientScopes.delProtocolMapper({ - // id: mapper.id!, - // mapperId: mapper.id!, - // }); - // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); addAlert( "Delete functionality not implemented yet!", AlertVariant.success ); - // refresh(); } catch (error) { addAlert( t("mappingDeletedError", { error }), @@ -244,82 +134,9 @@ export const LdapMapperList = () => { message={t("emptyMappers")} instructions={t("emptyMappersInstructions")} primaryActionText={t("emptyPrimaryAction")} - // onPrimaryAction={() => toggleAddMapperDialog(true)} - // secondaryActions={[ - // { - // text: t("emptySecondaryAction"), - // onClick: () => toggleAddMapperDialog(false), - // type: ButtonVariant.secondary, - // }, - // ]} /> } /> - - // - // { - // return { - // cells: Object.values([cell.name, cell.providerId]), - // }; - // })} - // aria-label={t("clientScopeList")} - // actions={[ - // { - // title: t("common:delete"), - // onClick: () => { - // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); - // }, - // }, - // ]} - // > - // - // - //
- //
); }; - -/* -Sample responses: - -const mapperList = [ - { - id: "699b72e5-b936-41b9-98fc-5d5b3ec5ea6f", - name: "username", - providerId: "user-attribute-ldap-mapper", - providerType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", - parentId: "f1da61f9-08f7-4dc5-83dd-d774fba518d4", - config: { - "ldap.attribute": ["cn"], - "is.mandatory.in.ldap": ["true"], - "always.read.value.from.ldap": ["false"], - "read.only": ["true"], - "user.model.attribute": ["username"], - }, - }, - { - id: "c11788d2-62be-442f-813f-4b00382a1e10", - name: "last name", - providerId: "user-attribute-ldap-mapper", - providerType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", - parentId: "f1da61f9-08f7-4dc5-83dd-d774fba518d4", - config: { - "ldap.attribute": ["sn"], - "is.mandatory.in.ldap": ["true"], - "always.read.value.from.ldap": ["true"], - "read.only": ["true"], - "user.model.attribute": ["lastName"], - }, - }, -]; -*/ diff --git a/src/user-federation/ldap/mappers/LdapMapperUsername.tsx b/src/user-federation/ldap/mappers/LdapMapperUsername.tsx new file mode 100644 index 0000000000..5464de1af6 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperUsername.tsx @@ -0,0 +1,123 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; + +export type LdapMapperUsernameProps = { + form: UseFormMethods; +}; + +export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { + return ( + <> + + + } + fieldId="kc-ldap-mapper-id" + isRequired + > + + + + } + fieldId="kc-ldap-mapper-name" + isRequired + > + + + + } + fieldId="kc-ldap-mapper-type" + isRequired + > + + + + } + fieldId="kc-user-model-attribute" + isRequired + > + + + + } + fieldId="kc-ldap-attribute" + isRequired + > + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx index 43839a05fb..b986b750b8 100644 --- a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx +++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx @@ -1,381 +1,98 @@ -import React, { useEffect, useState } from "react"; -import { useHistory, useParams, useRouteMatch } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import { useErrorHandler } from "react-error-boundary"; +import React, { useState, useEffect } from "react"; import { ActionGroup, AlertVariant, Button, - ButtonVariant, - Checkbox, - DropdownItem, - Flex, - FlexItem, - FormGroup, + Form, PageSection, - Select, - SelectOption, - SelectVariant, - Switch, - TextInput, - ValidatedOptions, - Text } from "@patternfly/react-core"; -import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation"; -import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; - +import { convertToFormValues } from "../../../util"; +import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; +import { useAdminClient } from "../../../context/auth/AdminClient"; import { ViewHeader } from "../../../components/view-header/ViewHeader"; -import { - useAdminClient, - asyncStateFetch, -} from "../../../context/auth/AdminClient"; -import { Controller, useForm } from "react-hook-form"; -import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog"; +import { useHistory, useParams } from "react-router-dom"; +import { useForm } from "react-hook-form"; import { useAlerts } from "../../../components/alert/Alerts"; -import { HelpItem } from "../../../components/help-enabler/HelpItem"; -import { useServerInfo } from "../../../context/server-info/ServerInfoProvider"; -import { convertFormValuesToObject, convertToFormValues } from "../../../util"; -import { FormAccess } from "../../../components/form-access/FormAccess"; - -type Params = { - id: string; - mapperId: string; -}; +import { useTranslation } from "react-i18next"; +import { LdapMapperUsername } from "./LdapMapperUsername"; +import { useRealm } from "../../../context/realm-context/RealmContext"; export const LdapMappingDetails = () => { -// const { t } = useTranslation("client-scopes"); -// const adminClient = useAdminClient(); -// const handleError = useErrorHandler(); -// const { addAlert } = useAlerts(); + const form = useForm(); + const [mapper, setMapper] = useState(); + const adminClient = useAdminClient(); + const { mapperId } = useParams<{ mapperId: string }>(); + const history = useHistory(); -// const { id, mapperId } = useParams(); -// const { register, errors, setValue, control, handleSubmit } = useForm(); -// const [mapping, setMapping] = useState(); -// const [typeOpen, setTypeOpen] = useState(false); -// const [configProperties, setConfigProperties] = useState< -// ConfigPropertyRepresentation[] -// >(); + const { realm } = useRealm(); + const id = mapperId; + const { t } = useTranslation("user-federation"); + const { addAlert } = useAlerts(); -// const history = useHistory(); -// const serverInfo = useServerInfo(); -// const { url } = useRouteMatch(); -// const isGuid = /^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/; + useEffect(() => { + (async () => { + if (mapperId) { + const fetchedMapper = await adminClient.components.findOne({ id }); + if (fetchedMapper) { + // TODO: remove after adding all mapper types + console.log("LdapMappingDetails: id used in findOne(id) call::"); + console.log(id); + console.log("LdapMappingDetails: data returned from findOne(id):"); + console.log(fetchedMapper); + setMapper(fetchedMapper); + setupForm(fetchedMapper); + } + } + })(); + }, []); - // useEffect(() => { - // return asyncStateFetch( - // async () => { - // if (mapperId.match(isGuid)) { - // const data = await adminClient.clientScopes.findProtocolMapper({ - // id, - // mapperId, - // }); - // if (data) { - // Object.entries(data).map((entry) => { - // convertToFormValues(entry[1], "config", setValue); - // }); - // } - // const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; - // const properties = mapperTypes.find( - // (type) => type.id === data!.protocolMapper - // )?.properties!; + const setupForm = (mapper: ComponentRepresentation) => { + Object.entries(mapper).map((entry) => { + if (entry[0] === "config") { + convertToFormValues(entry[1], "config", form.setValue); + } else { + form.setValue(entry[0], entry[1]); + } + }); + }; - // return { - // configProperties: properties, - // mapping: data, - // }; - // } else { - // const scope = await adminClient.clientScopes.findOne({ id }); - // const protocolMappers = serverInfo.protocolMapperTypes![ - // scope.protocol! - // ]; - // const mapping = protocolMappers.find( - // (mapper) => mapper.id === mapperId - // )!; - // return { - // mapping: { - // name: mapping.name, - // protocol: scope.protocol, - // protocolMapper: mapperId, - // }, - // }; - // } - // }, - // (result) => { - // setConfigProperties(result.configProperties); - // setMapping(result.mapping); - // }, - // handleError - // ); - // }, []); - - // const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ - // titleKey: "client-scopes:deleteMappingTitle", - // messageKey: "client-scopes:deleteMappingConfirm", - // continueButtonLabel: "common:delete", - // continueButtonVariant: ButtonVariant.danger, - // onConfirm: async () => { - // try { - // await adminClient.clientScopes.delClientScopeMappings( - // { client: id, id: mapperId }, - // [] - // ); - // addAlert(t("mappingDeletedSuccess"), AlertVariant.success); - // history.push(`${url}/${id}`); - // } catch (error) { - // addAlert(t("mappingDeletedError", { error }), AlertVariant.danger); - // } - // }, - // }); - - // const save = async (formMapping: ProtocolMapperRepresentation) => { - // const config = convertFormValuesToObject(formMapping.config); - // const map = { ...mapping, ...formMapping, config }; - // const key = mapperId.match(isGuid) ? "Updated" : "Created"; - // try { - // if (mapperId.match(isGuid)) { - // await adminClient.clientScopes.updateProtocolMapper( - // { id, mapperId }, - // map - // ); - // } else { - // await adminClient.clientScopes.addProtocolMapper({ id }, map); - // } - // addAlert(t(`mapping${key}Success`), AlertVariant.success); - // } catch (error) { - // addAlert(t(`mapping${key}Error`, { error }), AlertVariant.danger); - // } - // }; + const save = () => { + addAlert( + t( + id === "new" + ? "Create functionality not implemented yet!" + : "Save functionality not implemented yet!" + ), + AlertVariant.success + ); + history.push(`/${realm}/user-federation`); + }; return ( <> - - Coming soon! - - {/* - - {t("common:delete")} - , - ] - : undefined - } - /> - - - <> - {!mapperId.match(isGuid) && ( - - } - fieldId="name" - isRequired - validated={ - errors.name - ? ValidatedOptions.error - : ValidatedOptions.default - } - helperTextInvalid={t("common:required")} - > - - - )} - - - } - fieldId="prefix" - > - - - - } - fieldId="multiValued" - > - ( - onChange("" + value)} - /> - )} - /> - - - } - fieldId="claimName" - > - - - - } - fieldId="claimJsonType" - > - ( - - )} - /> - - - - - ( - onChange("" + value)} - /> - )} - /> - - - ( - onChange("" + value)} - /> - )} - /> - - - ( - onChange("" + value)} - /> - )} - /> - - - - - - + - + - */} - + ); }; From 9166212720f570839a7458528f11bbb508d3932b Mon Sep 17 00:00:00 2001 From: mfrances Date: Mon, 29 Mar 2021 19:47:13 -0400 Subject: [PATCH 08/17] two mappers complete and strings done --- src/common-messages.json | 12 +- src/user-federation/help.json | 62 +++--- .../ldap/mappers/LdapMapperList.tsx | 12 +- .../ldap/mappers/LdapMapperUsername.tsx | 202 +++++++++++++++--- .../ldap/mappers/LdapMappingDetails.tsx | 9 +- src/user-federation/messages.json | 45 ++-- 6 files changed, 255 insertions(+), 87 deletions(-) diff --git a/src/common-messages.json b/src/common-messages.json index 85134402bb..4d03319e1b 100644 --- a/src/common-messages.json +++ b/src/common-messages.json @@ -95,6 +95,16 @@ "minutes": "Minutes", "hours": "Hours", "days": "Days" - } + }, + + "attributes": "Attributes", + "clientId": "Client ID", + "id": "ID", + + "addMapper": "Add mapper", + "searchForMapper": "Search for mapper", + "mapperType": "Mapper type", + "mappingDeletedSuccess": "Mapping successfully deleted", + "mappingDeletedError": "Could not delete mapping: '{{error}}'" } } diff --git a/src/user-federation/help.json b/src/user-federation/help.json index 0ae96f96ea..0dd44198fe 100644 --- a/src/user-federation/help.json +++ b/src/user-federation/help.json @@ -66,9 +66,6 @@ "editModeKerberosHelp": "READ_ONLY means that password updates are not allowed and user always authenticates with Kerberos password. UNSYNCED means that the user can change the password in the Keycloak database and this one will be used instead of the Kerberos password.", "updateFirstLoginHelp": "Update profile on first login", - "nameHelp": "Name of the mapper", - "mapperTypeHelp": "", - "mapperTypeMsadUserAccountControlManagerHelp": "Mapper specific to MSAD. It's able to integrate the MSAD user account state into Keycloak account state (account enabled, password is expired etc). It's using userAccountControl and pwdLastSet MSAD attributes for that. For example if pwdLastSet is 0, the Keycloak user is required to update the password; if userAccountControl is 514 (disabled account) the Keycloak user is disabled as well etc. Mapper is also able to handle the exception code from LDAP user authentication.", "mapperTypeMsadLdsUserAccountControlMapperHelp": "Mapper specific to MSAD LDS. It's able to integrate the MSAD LDS user account state into Keycloak account state (account enabled, password is expired etc). It's using msDS-UserAccountDisabled and pwdLastSet is 0, the Keycloak user is required to update password, if msDS-UserAccountDisabled is 'TRUE' the Keycloak user is disabled as well etc. Mapper is also able to handle exception code from LDAP user authentication.", "mapperTypeGroupLdapMapperHelp": "Used to map group mappings of groups from some LDAP DN to Keycloak group mappings", @@ -83,46 +80,51 @@ "passwordPolicyHintsEnabledHelp": "Applicable just for writable MSAD. If on, then updating password of MSAD user will use LDAP_SERVER_POLICY_HINTS_OID extension, which means that advanced MSAD password policies like 'password history' or 'minimal password age' will be applied. This extension works just for MSAD 2008 R2 or newer.", - "ldapGroupsDnHelp": "LDAP DN where groups of this tree are saved. For example 'ou=groups,dc=example,dc=org'", - "groupNameLDAPAttributeHelp": "Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn'. In this case typical group/role object may have DN like 'cn=Group1,ouu=groups,dc=example,dc=org'.", - "groupObjectClassesHelp": "Object class (or classes) of the group object. It's divided by commas if more classes needed. In typical LDAP deployment it could be 'groupOfNames'. In Active Directory it's usually 'group'.", - "preserveGroupInheritanceHelp": "Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups.", - "ignoreMissingGroupsHelp": "Ignore missing groups in the group hierarchy.", - "membershipLdapAttributeHelp": "Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member'. However when 'Membership Attribute Type' is 'UID', then 'Membership LDAP Attribute' could be typically 'memberUid'.", - "membershipAttributeTypeHelp": "DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com'. UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john'.", - "membershipUserLdapAttributeHelp": "Used just if Membership Attribute Type is UID. It is the name of the LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid'. For example if the value of 'Membership User LDAP Attribute' is 'uid' and LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john'.", - "ldapFilterHelp": "LDAP Filter adds an additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'.", - "modeHelp": "LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are retrieved from both LDAP and DB and merged together. New group joins are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where group mappings are retrieved from LDAP just at the time when user is imported from LDAP and then they are saved to local keycloak DB.", - "userGroupsRetrieveStrategyHelp": "Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user or from the other attribute specified by 'Member-Of LDAP Attribute'.", - "memberofLdapAttributeHelp": "Used just when 'User Roles Retrieve Strategy' is GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE. It specifies the name of the LDAP attribute on the LDAP user, which contains the groups, which the user is member of. Usually it will be the default 'memberOf'.", - "mappedGroupAttributesHelp": "List of names of attributes divided by commas. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. Leave this empty if no additional group attributes are required to be mapped in Keycloak.", - "dropNonexistingGroupsDuringSync": "If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups that still exist in LDAP. The rest will be deleted.", - "groupsPath": "Keycloak group path the LDAP groups are added to. For example if value '/Applications/App1' is used, then LDAP groups will be available in Keycloak under group 'App1', which is child of top level group 'Applications'. The default value is '/' so LDAP groups will be mapped to the Keycloak groups at the top level. The configured group path must already exist in the Keycloak when creating this mapper.", + "nameHelp": "Name of the mapper", + "mapperTypeHelp": "Used to map single attribute from LDAP user to attribute of UserModel in Keycloak DB", "userModelAttributeHelp": "Name of the UserModel property or attribute you want to map the LDAP attribute into. For example 'firstName', 'lastName, 'email', 'street' etc.", - "ldapAttribute": "Name of mapped attribute on LDAP object. For example 'cn', 'sn', 'mail', 'street', etc.", + "ldapAttributeHelp": "Name of mapped attribute on LDAP object. For example 'cn', 'sn', 'mail', 'street', etc.", "readOnlyHelp": "Read-only attribute is imported from LDAP to UserModel, but it's not saved back to LDAP when user is updated in Keycloak.", "alwaysReadValueFromLdapHelp": "If on, then during reading of the LDAP attribute value will always used instead of the value from Keycloak DB.", "isMandatoryInLdapHelp": "If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP.", "isBinaryAttributeHelp": "Should be true for binary LDAP attributes.", - "ldapRolesDNHelp": "LDAP DN where roles of this tree are saved. For example, 'ou=finance,dc=example,dc=org'", - "roleNameLdapAttributeHelp": "Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn'. In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org'.", - "roleObjectClassesHelp": "Object class (or classes) of the role object. It's divided by commas if more classes are needed. In typical LDAP deployment it could be 'groupOfNames'. In Active Directory it's usually 'group'.", - "useRealmRolesMappingHelp": "If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings.", - "clientIdHelp": "Client ID of client to which LDAP role mappings will be mapped. Applicable only if 'Use Realm Roles Mapping' is false.", - - "userModelAttributeNameHelp": "Name of the model attribute to be added when importing user from LDAP", - "attributeValueHelp": "Value of the model attribute to be added when importing user from LDAP", - - "roleHelp": "Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.", - "derFormattedHelp": "Activate this if the certificate is DER formatted in LDAP and not PEM formatted.", "ldapFullNameAttributeHelp": "Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn',", "fullNameLdapMapperReadOnlyHelp": "For Read-only, data is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", "fullNameLdapMapperWriteOnlyHelp": "For Write-only, is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak", + "ldapGroupsDnHelp": "LDAP DN where groups of this tree are saved. For example 'ou=groups,dc=example,dc=org'", + "groupNameLDAPAttributeHelp": "Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn'. In this case typical group/role object may have DN like 'cn=Group1,ouu=groups,dc=example,dc=org'.", + "groupObjectClassesHelp": "Object class (or classes) of the group object. It's divided by commas if more classes needed. In typical LDAP deployment it could be 'groupOfNames'. In Active Directory it's usually 'group'.", + "preserveGroupInheritanceHelp": "Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups.", + "ignoreMissingGroupsHelp": "Ignore missing groups in the group hierarchy.", + "userGroupsRetrieveStrategyHelp": "Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user or from the other attribute specified by 'Member-Of LDAP Attribute'.", + "mappedGroupAttributesHelp": "List of names of attributes divided by commas. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. Leave this empty if no additional group attributes are required to be mapped in Keycloak.", + "dropNonexistingGroupsDuringSync": "If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups that still exist in LDAP. The rest will be deleted.", + "groupsPath": "Keycloak group path the LDAP groups are added to. For example if value '/Applications/App1' is used, then LDAP groups will be available in Keycloak under group 'App1', which is child of top level group 'Applications'. The default value is '/' so LDAP groups will be mapped to the Keycloak groups at the top level. The configured group path must already exist in the Keycloak when creating this mapper.", + + "ldapRolesDnHelp": "LDAP DN where roles of this tree are saved. For example, 'ou=finance,dc=example,dc=org'", + "roleNameLdapAttributeHelp": "Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn'. In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org'.", + "roleObjectClassesHelp": "Object class (or classes) of the role object. It's divided by commas if more classes are needed. In typical LDAP deployment it could be 'groupOfNames'. In Active Directory it's usually 'group'.", + "userRolesRetrieveStrategy": "Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles fo user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. GET_ROLES_FROM_USER_MEMBEROF means that roles of user will be retrieved from 'memberOf' attribute of our user. Or from the other attributes specified by 'Member-Of LDAP Attribute'. LOAD_ROLES_BY_MEMBER_ATTRIBUTE is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN LDAP extension.", + "useRealmRolesMappingHelp": "If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings.", + "clientIdHelp": "Client ID of client to which LDAP role mappings will be mapped. Applicable only if 'Use Realm Roles Mapping' is false.", + + "membershipLdapAttributeHelp": "Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member'. However when 'Membership Attribute Type' is 'UID', then 'Membership LDAP Attribute' could be typically 'memberUid'.", + "membershipAttributeTypeHelp": "DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com'. UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john'.", + "membershipUserLdapAttributeHelp": "Used just if Membership Attribute Type is UID. It is the name of the LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid'. For example if the value of 'Membership User LDAP Attribute' is 'uid' and LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john'.", + "ldapFilterHelp": "LDAP Filter adds an additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'.", + "modeHelp": "LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are retrieved from both LDAP and DB and merged together. New group joins are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where group mappings are retrieved from LDAP just at the time when user is imported from LDAP and then they are saved to local keycloak DB.", + "memberofLdapAttributeHelp": "Used just when 'User Roles Retrieve Strategy' is GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE. It specifies the name of the LDAP attribute on the LDAP user, which contains the groups, which the user is member of. Usually it will be the default 'memberOf'.", + + "userModelAttributeNameHelp": "Name of the model attribute to be added when importing user from LDAP", + "attributeValueHelp": "Value of the model attribute to be added when importing user from LDAP", + + "roleHelp": "Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.", + "groupHelp": "Users imported from LDAP will be automatically added into this configured group.", "ldapAttributeNameHelp": "Name of the LDAP attribute, which will be added to the new user during registration", diff --git a/src/user-federation/ldap/mappers/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx index c32a6ad114..d4473005cd 100644 --- a/src/user-federation/ldap/mappers/LdapMapperList.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -15,7 +15,7 @@ import { Link, useParams, useRouteMatch } from "react-router-dom"; export const LdapMapperList = () => { const [mappers, setMappers] = useState(); - const { t } = useTranslation("client-scopes"); + const { t } = useTranslation("user-federation"); const adminClient = useAdminClient(); const { addAlert } = useAlerts(); @@ -81,8 +81,8 @@ export const LdapMapperList = () => { } @@ -107,12 +106,13 @@ export const LdapMapperList = () => { onRowClick: async (mapper) => { try { addAlert( + // t("common:mappingDeletedError"), "Delete functionality not implemented yet!", AlertVariant.success ); } catch (error) { addAlert( - t("mappingDeletedError", { error }), + t("common:mappingDeletedError", { error }), AlertVariant.danger ); } diff --git a/src/user-federation/ldap/mappers/LdapMapperUsername.tsx b/src/user-federation/ldap/mappers/LdapMapperUsername.tsx index 5464de1af6..56e286d039 100644 --- a/src/user-federation/ldap/mappers/LdapMapperUsername.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperUsername.tsx @@ -1,29 +1,26 @@ -import { FormGroup, TextInput } from "@patternfly/react-core"; +import { FormGroup, Switch, TextInput } from "@patternfly/react-core"; import React from "react"; import { HelpItem } from "../../../components/help-enabler/HelpItem"; -import { UseFormMethods } from "react-hook-form"; +import { Controller, UseFormMethods } from "react-hook-form"; import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; export type LdapMapperUsernameProps = { form: UseFormMethods; + mapperType: string | undefined; }; -export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { +export const LdapMapperUsername = ({ + form, + mapperType, +}: LdapMapperUsernameProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + return ( <> - - } - fieldId="kc-ldap-mapper-id" - isRequired - > + { /> } @@ -55,12 +52,12 @@ export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { /> } fieldId="kc-ldap-mapper-type" @@ -76,11 +73,11 @@ export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { /> } @@ -97,11 +94,11 @@ export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { /> } @@ -117,6 +114,155 @@ export const LdapMapperUsername = ({ form }: LdapMapperUsernameProps) => { ref={form.register} /> + + } + fieldId="kc-read-only" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + } + fieldId="kc-always-read-value" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + + } + fieldId="kc-is-mandatory" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + + } + fieldId="kc-is-binary" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + {mapperType === "certificate-ldap-mapper" ? ( + <> + + } + fieldId="kc-der-formatted" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + ) : ( + <> + )} ); diff --git a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx index b986b750b8..e689b97bad 100644 --- a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx +++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx @@ -72,9 +72,14 @@ export const LdapMappingDetails = () => { <> - + {mapper + ? (mapper.providerId! === "certificate-ldap-mapper" || + mapper.providerId! === "user-attribute-ldap-mapper") && ( + + ) + : ""}
- + - - - - - - ); -}; diff --git a/src/user-federation/ldap/mappers/shared/LdapMapperGeneral.tsx b/src/user-federation/ldap/mappers/shared/LdapMapperGeneral.tsx new file mode 100644 index 0000000000..52925a3646 --- /dev/null +++ b/src/user-federation/ldap/mappers/shared/LdapMapperGeneral.tsx @@ -0,0 +1,73 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +export type LdapMapperGeneralProps = { + form: UseFormMethods; +}; + +export const LdapMapperGeneral = ({ + form, +}: LdapMapperGeneralProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + + } + fieldId="kc-ldap-mapper-name" + isRequired + > + + + + } + fieldId="kc-ldap-mapper-type" + isRequired + > + + + + ); +}; From f1f9f8736c707444af04e38878c2138319dc9ed5 Mon Sep 17 00:00:00 2001 From: mfrances Date: Tue, 30 Mar 2021 17:16:38 -0400 Subject: [PATCH 11/17] nine of eleven mappers complete --- .../mappers/LdapMapperFullNameAttribute.tsx | 104 ++++++++++++++++++ .../mappers/LdapMapperHardcodedAttribute.tsx | 68 ++++++++++++ .../LdapMapperHardcodedLdapAttribute.tsx | 69 ++++++++++++ .../mappers/LdapMapperHardcodedLdapGroup.tsx | 47 ++++++++ .../mappers/LdapMapperHardcodedLdapRole.tsx | 47 ++++++++ .../mappers/LdapMapperMsadLdsUserAccount.tsx | 20 ++++ .../mappers/LdapMapperMsadUserAccount.tsx | 54 +++++++++ .../ldap/mappers/LdapMapperUserAttribute.tsx | 7 +- .../ldap/mappers/LdapMappingDetails.tsx | 45 ++++++++ .../ldap/mappers/shared/LdapMapperGeneral.tsx | 4 +- src/user-federation/messages.json | 1 + 11 files changed, 458 insertions(+), 8 deletions(-) create mode 100644 src/user-federation/ldap/mappers/LdapMapperFullNameAttribute.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperHardcodedAttribute.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperHardcodedLdapAttribute.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperHardcodedLdapGroup.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperHardcodedLdapRole.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperMsadLdsUserAccount.tsx create mode 100644 src/user-federation/ldap/mappers/LdapMapperMsadUserAccount.tsx diff --git a/src/user-federation/ldap/mappers/LdapMapperFullNameAttribute.tsx b/src/user-federation/ldap/mappers/LdapMapperFullNameAttribute.tsx new file mode 100644 index 0000000000..458a0f2dee --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperFullNameAttribute.tsx @@ -0,0 +1,104 @@ +import { FormGroup, Switch, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { Controller, UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperFullNameAttributeProps = { + form: UseFormMethods; +}; + +export const LdapMapperFullNameAttribute = ({ + form, +}: LdapMapperFullNameAttributeProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + + } + fieldId="kc-full-name-attribute" + isRequired + > + + + + } + fieldId="kc-read-only" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + } + fieldId="kc-read-only" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperHardcodedAttribute.tsx b/src/user-federation/ldap/mappers/LdapMapperHardcodedAttribute.tsx new file mode 100644 index 0000000000..e71c13adfd --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperHardcodedAttribute.tsx @@ -0,0 +1,68 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperHardcodedAttributeProps = { + form: UseFormMethods; +}; + +export const LdapMapperHardcodedAttribute = ({ + form, +}: LdapMapperHardcodedAttributeProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + } + fieldId="kc-user-model-attribute" + isRequired + > + + + + } + fieldId="kc-attribute-value" + isRequired + > + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapAttribute.tsx b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapAttribute.tsx new file mode 100644 index 0000000000..e1b786592e --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapAttribute.tsx @@ -0,0 +1,69 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperHardcodedLdapAttributeProps = { + form: UseFormMethods; +}; + +export const LdapMapperHardcodedLdapAttribute = ({ + form, +}: LdapMapperHardcodedLdapAttributeProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + + } + fieldId="kc-ldap-attribute-name" + isRequired + > + + + + } + fieldId="kc-ldap-attribute-value" + isRequired + > + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapGroup.tsx b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapGroup.tsx new file mode 100644 index 0000000000..f6c3b3893e --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapGroup.tsx @@ -0,0 +1,47 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperHardcodedLdapGroupProps = { + form: UseFormMethods; +}; + +export const LdapMapperHardcodedLdapGroup = ({ + form, +}: LdapMapperHardcodedLdapGroupProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + } + fieldId="kc-group" + isRequired + > + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapRole.tsx b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapRole.tsx new file mode 100644 index 0000000000..9bcf71d85c --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperHardcodedLdapRole.tsx @@ -0,0 +1,47 @@ +import { FormGroup, TextInput } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperHardcodedLdapRoleProps = { + form: UseFormMethods; +}; + +export const LdapMapperHardcodedLdapRole = ({ + form, +}: LdapMapperHardcodedLdapRoleProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + } + fieldId="kc-role" + isRequired + > + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperMsadLdsUserAccount.tsx b/src/user-federation/ldap/mappers/LdapMapperMsadLdsUserAccount.tsx new file mode 100644 index 0000000000..b1ad0a2c82 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperMsadLdsUserAccount.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperMsadLdsUserAccountProps = { + form: UseFormMethods; +}; + +export const LdapMapperMsadLdsUserAccount = ({ + form, +}: LdapMapperMsadLdsUserAccountProps) => { + return ( + <> + + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperMsadUserAccount.tsx b/src/user-federation/ldap/mappers/LdapMapperMsadUserAccount.tsx new file mode 100644 index 0000000000..de4d9e3d26 --- /dev/null +++ b/src/user-federation/ldap/mappers/LdapMapperMsadUserAccount.tsx @@ -0,0 +1,54 @@ +import { FormGroup, Switch } from "@patternfly/react-core"; +import React from "react"; +import { HelpItem } from "../../../components/help-enabler/HelpItem"; +import { Controller, UseFormMethods } from "react-hook-form"; +import { FormAccess } from "../../../components/form-access/FormAccess"; +import { useTranslation } from "react-i18next"; +import { LdapMapperGeneral } from "./shared/LdapMapperGeneral"; + +export type LdapMapperMsadUserAccountProps = { + form: UseFormMethods; +}; + +export const LdapMapperMsadUserAccount = ({ + form, +}: LdapMapperMsadUserAccountProps) => { + const { t } = useTranslation("user-federation"); + const helpText = useTranslation("user-federation-help").t; + + return ( + <> + + + + } + fieldId="kc-der-formatted" + hasNoPaddingTop + > + ( + onChange([`${value}`])} + isChecked={value[0] === "true"} + label={t("common:on")} + labelOff={t("common:off")} + /> + )} + > + + + + ); +}; diff --git a/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx b/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx index 8446de2301..31f11198d1 100644 --- a/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx +++ b/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx @@ -120,7 +120,6 @@ export const LdapMapperUserAttribute = ({ )} > - - - {mapperType === "certificate-ldap-mapper" ? ( <> } fieldId="kc-der-formatted" diff --git a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx index d71a1f8afd..d485f12aa9 100644 --- a/src/user-federation/ldap/mappers/LdapMappingDetails.tsx +++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx @@ -14,7 +14,17 @@ import { useHistory, useParams } from "react-router-dom"; import { useForm } from "react-hook-form"; import { useAlerts } from "../../../components/alert/Alerts"; import { useTranslation } from "react-i18next"; + import { LdapMapperUserAttribute } from "./LdapMapperUserAttribute"; +import { LdapMapperMsadUserAccount } from "./LdapMapperMsadUserAccount"; +import { LdapMapperMsadLdsUserAccount } from "./LdapMapperMsadLdsUserAccount"; +import { LdapMapperFullNameAttribute } from "./LdapMapperFullNameAttribute"; + +import { LdapMapperHardcodedLdapRole } from "./LdapMapperHardcodedLdapRole"; +import { LdapMapperHardcodedLdapGroup } from "./LdapMapperHardcodedLdapGroup"; +import { LdapMapperHardcodedLdapAttribute } from "./LdapMapperHardcodedLdapAttribute"; +import { LdapMapperHardcodedAttribute } from "./LdapMapperHardcodedAttribute"; + import { useRealm } from "../../../context/realm-context/RealmContext"; export const LdapMappingDetails = () => { @@ -81,6 +91,41 @@ export const LdapMappingDetails = () => { /> ) : ""} + {mapper + ? mapper.providerId! === "msad-user-account-control-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "msad-lds-user-account-control-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "full-name-ldap-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "hardcoded-ldap-role-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "hardcoded-ldap-group-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "hardcoded-ldap-attribute-mapper" && ( + + ) + : ""} + {mapper + ? mapper.providerId! === "hardcoded-attribute-mapper" && ( + + ) + : ""}