diff --git a/src/client-scopes/add/MapperDialog.tsx b/src/client-scopes/add/MapperDialog.tsx
index 1ff5ffba87..401455b218 100644
--- a/src/client-scopes/add/MapperDialog.tsx
+++ b/src/client-scopes/add/MapperDialog.tsx
@@ -157,8 +157,8 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
)}
{isBuiltIn && rows.length === 0 && (
)}
diff --git a/src/client-scopes/add/RoleMappingForm.tsx b/src/client-scopes/add/RoleMappingForm.tsx
index c9fc142f74..e98be38c1c 100644
--- a/src/client-scopes/add/RoleMappingForm.tsx
+++ b/src/client-scopes/add/RoleMappingForm.tsx
@@ -146,7 +146,7 @@ export const RoleMappingForm = () => {
return (
<>
@@ -156,11 +156,11 @@ export const RoleMappingForm = () => {
role="manage-clients"
>
}
diff --git a/src/client-scopes/details/MapperList.tsx b/src/client-scopes/details/MapperList.tsx
index 8aeabd2f8a..73f57169ec 100644
--- a/src/client-scopes/details/MapperList.tsx
+++ b/src/client-scopes/details/MapperList.tsx
@@ -75,9 +75,12 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
mappers as ProtocolMapperRepresentation[]
);
refresh();
- addAlert(t("mappingCreatedSuccess"), AlertVariant.success);
+ addAlert(t("common:mappingCreatedSuccess"), AlertVariant.success);
} catch (error) {
- addAlert(t("mappingCreatedError", { error }), AlertVariant.danger);
+ addAlert(
+ t("common:mappingCreatedError", { error }),
+ AlertVariant.danger
+ );
}
}
};
@@ -119,7 +122,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
key={key}
loader={loader}
ariaLabelKey="client-scopes:clientScopeList"
- searchPlaceholderKey="client-scopes:mappersSearchFor"
+ searchPlaceholderKey="common:searchForMapper"
toolbarItem={
setMapperAction(false)}
@@ -130,7 +133,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
onToggle={() => setMapperAction(!mapperAction)}
toggleIndicator={CaretDownIcon}
>
- {t("addMapper")}
+ {t("common:addMapper")}
}
isOpen={mapperAction}
@@ -159,11 +162,14 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
id: clientScope.id!,
mapperId: mapper.id!,
});
- addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
+ addAlert(
+ t("common:mappingDeletedSuccess"),
+ AlertVariant.success
+ );
refresh();
} catch (error) {
addAlert(
- t("mappingDeletedError", { error }),
+ t("common:mappingDeletedError", { error }),
AlertVariant.danger
);
}
@@ -186,9 +192,9 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
]}
emptyState={
toggleAddMapperDialog(true)}
secondaryActions={[
{
diff --git a/src/client-scopes/details/MappingDetails.tsx b/src/client-scopes/details/MappingDetails.tsx
index c52528cb60..0ab64c48d9 100644
--- a/src/client-scopes/details/MappingDetails.tsx
+++ b/src/client-scopes/details/MappingDetails.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { useHistory, useParams, useRouteMatch } from "react-router-dom";
+import { useHistory, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useErrorHandler } from "react-error-boundary";
import {
@@ -109,8 +109,8 @@ export const MappingDetails = () => {
}, []);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
- titleKey: "client-scopes:deleteMappingTitle",
- messageKey: "client-scopes:deleteMappingConfirm",
+ titleKey: "common:deleteMappingTitle",
+ messageKey: "common:deleteMappingConfirm",
continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
@@ -119,10 +119,13 @@ export const MappingDetails = () => {
id,
mapperId: mapperId,
});
- addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
+ addAlert(t("common:mappingDeletedSuccess"), AlertVariant.success);
history.push(`/${realm}/client-scopes/${id}/mappers`);
} catch (error) {
- addAlert(t("mappingDeletedError", { error }), AlertVariant.danger);
+ addAlert(
+ t("common:mappingDeletedError", { error }),
+ AlertVariant.danger
+ );
}
},
});
@@ -150,7 +153,7 @@ export const MappingDetails = () => {
<>
{
{t("mappers")}}
+ title={{t("common:mappers")}}
>
{clientScope && (
diff --git a/src/client-scopes/messages.json b/src/client-scopes/messages.json
index 2d143b887e..fe2517dfcb 100644
--- a/src/client-scopes/messages.json
+++ b/src/client-scopes/messages.json
@@ -9,11 +9,6 @@
"deletedSuccess": "The client scope has been deleted",
"deleteError": "Could not delete client scope: {{error}}",
"includeInTokenScope": "Include in token scope",
- "mappingDetails": "Mapper details",
- "mappingCreatedSuccess": "Mapping successfully created",
- "mappingCreatedError": "Could not create mapping: '{{error}}'",
- "deleteMappingTitle": "Delete mapping?",
- "deleteMappingConfirm": "Are you sure you want to delete this mapping?",
"mappingUpdatedSuccess": "Mapping successfully updated",
"mappingUpdatedError": "Could not update mapping: '{{error}}'",
"realmRolePrefix": "Realm role prefix",
@@ -28,11 +23,7 @@
"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",
- "protocolMapper": "Mapper type",
"realmRoles": "Realm roles",
"selectARole": "Select a role",
"clientRoles": "Client roles",
@@ -44,13 +35,8 @@
"mapperCreateError": "Could not create mapping: {{error}}",
"fromPredefinedMapper": "From predefined mappers",
"byConfiguration": "By configuration",
- "emptyMappers": "No mappers",
- "emptyMappersInstructions": "If you want to add mappers, please click the button below to add some predefined mappers or to configure a new mapper.",
"emptyBuiltInMappersInstructions": "All built in mappers were added to this client",
- "emptyPrimaryAction": "Add predefined mapper",
"emptySecondaryAction": "Configure a new mapper",
- "mappingDeletedSuccess": "Mapping successfully deleted",
- "mappingDeletedError": "Could not delete mapping: '{{error}}'",
"displayOnConsentScreen": "Display on consent screen",
"consentScreenText": "Consent screen text",
"guiOrder": "Display Order",
diff --git a/src/clients/ClientDescription.tsx b/src/clients/ClientDescription.tsx
index 3f5afb30f5..ecca6d3c7f 100644
--- a/src/clients/ClientDescription.tsx
+++ b/src/clients/ClientDescription.tsx
@@ -22,11 +22,11 @@ export const ClientDescription = () => {
labelIcon={
}
- label={t("clientID")}
+ label={t("common:clientID")}
fieldId="kc-client-id"
helperTextInvalid={t("common:required")}
validated={
diff --git a/src/clients/ClientsSection.tsx b/src/clients/ClientsSection.tsx
index 76f5b17054..fcb04e00af 100644
--- a/src/clients/ClientsSection.tsx
+++ b/src/clients/ClientsSection.tsx
@@ -144,7 +144,7 @@ export const ClientsSection = () => {
columns={[
{
name: "clientId",
- displayKey: "clients:clientID",
+ displayKey: "common:clientID",
cellRenderer: ClientDetailLink,
},
{ name: "protocol", displayKey: "common:type" },
diff --git a/src/clients/initial-access/InitialAccessTokenList.tsx b/src/clients/initial-access/InitialAccessTokenList.tsx
index 1de09066df..7a7e4577c7 100644
--- a/src/clients/initial-access/InitialAccessTokenList.tsx
+++ b/src/clients/initial-access/InitialAccessTokenList.tsx
@@ -74,7 +74,7 @@ export const InitialAccessTokenList = () => {
columns={[
{
name: "id",
- displayKey: "clients:id",
+ displayKey: "common:id",
},
{
name: "timestamp",
diff --git a/src/clients/messages.json b/src/clients/messages.json
index 1748d40efb..33e38ee174 100644
--- a/src/clients/messages.json
+++ b/src/clients/messages.json
@@ -5,7 +5,6 @@
"implicitFlow": "Implicit flow",
"createClient": "Create client",
"importClient": "Import client",
- "clientID": "Client ID",
"homeURL": "Home URL",
"webOrigins": "Web origins",
"addWebOrigins": "Add web origins",
@@ -86,7 +85,6 @@
"tokenDeleteConfirmTitle": "Delete initial access token?",
"tokenDeleteSuccess": "initial access token created successfully",
"tokenDeleteError": "Could not delete initial access token: '{{error}}'",
- "id": "ID",
"timestamp": "Created date",
"expires": "Expires",
"count": "Count",
diff --git a/src/common-messages.json b/src/common-messages.json
index 9b28ff89a2..9d855887ae 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",
@@ -94,6 +95,25 @@
"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}}'",
+ "mappingDetails": "Mapper details",
+ "mappingCreatedSuccess": "Mapping successfully created",
+ "mappingCreatedError": "Could not create mapping: '{{error}}'",
+ "deleteMappingTitle": "Delete mapping?",
+ "deleteMappingConfirm": "Are you sure you want to delete this mapping?",
+
+ "emptyMappers": "No mappers",
+ "emptyMappersInstructions": "If you want to add mappers, please click the button below to add some predefined mappers or to configure a new mapper.",
+ "emptyPrimaryAction": "Add predefined mapper"
}
}
diff --git a/src/components/table-toolbar/KeycloakDataTable.tsx b/src/components/table-toolbar/KeycloakDataTable.tsx
index 58619a203e..04e5486a4a 100644
--- a/src/components/table-toolbar/KeycloakDataTable.tsx
+++ b/src/components/table-toolbar/KeycloakDataTable.tsx
@@ -107,7 +107,7 @@ export type DataListProps = {
* {
{t("attributes")}}
+ title={{t("common:attributes")}}
>
diff --git a/src/realm-roles/RealmRoleTabs.tsx b/src/realm-roles/RealmRoleTabs.tsx
index 5d1a7b8dd0..7e915eca09 100644
--- a/src/realm-roles/RealmRoleTabs.tsx
+++ b/src/realm-roles/RealmRoleTabs.tsx
@@ -327,7 +327,7 @@ export const RealmRoleTabs = () => {
) : null}
{t("attributes")}}
+ title={{t("common:attributes")}}
>
[
{
path: "/:realm/client-scopes/:id/mappers/oidc-role-name-mapper",
component: RoleMappingForm,
- breadcrumb: t("client-scopes:mappingDetails"),
+ breadcrumb: t("common:mappingDetails"),
access: "view-clients",
},
{
path: "/:realm/client-scopes/:id/mappers/:mapperId",
component: MappingDetails,
- breadcrumb: t("client-scopes:mappingDetails"),
+ breadcrumb: t("common:mappingDetails"),
access: "view-clients",
},
{
@@ -200,12 +201,6 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: null,
access: "view-realm",
},
- {
- path: "/:realm/user-federation/ldap",
- component: UserFederationSection,
- breadcrumb: null,
- access: "view-realm",
- },
{
path: "/:realm/user-federation/kerberos/:id",
component: UserFederationKerberosSettings,
@@ -219,11 +214,23 @@ export const routes: RoutesFn = (t: TFunction) => [
access: "view-realm",
},
{
- path: "/:realm/user-federation/ldap/:id",
+ path: "/:realm/user-federation/ldap",
+ component: UserFederationSection,
+ breadcrumb: null,
+ access: "view-realm",
+ },
+ {
+ path: "/:realm/user-federation/ldap/:id/:tab?",
component: UserFederationLdapSettings,
breadcrumb: t("common:settings"),
access: "view-realm",
},
+ {
+ path: "/:realm/user-federation/ldap/:id/:tab/:mapperId",
+ component: LdapMappingDetails,
+ breadcrumb: t("common:mappingDetails"),
+ access: "view-realm",
+ },
{
path: "/:realm/user-federation/ldap/new",
component: UserFederationLdapSettings,
diff --git a/src/stories/KeycloakDataTable.stories.tsx b/src/stories/KeycloakDataTable.stories.tsx
index 58fdfbe23e..95bf1e45ed 100644
--- a/src/stories/KeycloakDataTable.stories.tsx
+++ b/src/stories/KeycloakDataTable.stories.tsx
@@ -26,7 +26,7 @@ SimpleList.args = {
ariaLabelKey: "clients:clientList",
searchPlaceholderKey: "common:search",
columns: [
- { name: "clientId", displayKey: "clients:clientID" },
+ { name: "clientId", displayKey: "common:clientID" },
{ name: "protocol", displayKey: "common:type" },
{
name: "description",
diff --git a/src/user-federation/UserFederationLdapSettings.tsx b/src/user-federation/UserFederationLdapSettings.tsx
index b15ba8ffb5..a87ebe6e2c 100644
--- a/src/user-federation/UserFederationLdapSettings.tsx
+++ b/src/user-federation/UserFederationLdapSettings.tsx
@@ -8,6 +8,8 @@ import {
DropdownSeparator,
Form,
PageSection,
+ Tab,
+ TabTitleText,
} from "@patternfly/react-core";
import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced";
@@ -31,6 +33,9 @@ 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";
+import { LdapMapperList } from "./ldap/mappers/LdapMapperList";
+
type LdapSettingsHeaderProps = {
onChange: (value: string) => void;
value: string;
@@ -276,44 +281,59 @@ export const UserFederationLdapSettings = () => {
)}
/>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {t("common:mappers")}}
+ >
+
+
+
>
);
diff --git a/src/user-federation/help.json b/src/user-federation/help.json
index 0ae96f96ea..9b2e96affd 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.",
+ "dropNonexistingGroupsDuringSyncHelp": "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.",
+ "groupsPathHelp": "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'.",
+ "userRolesRetrieveStrategyHelp": "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/LdapSettingsKerberosIntegration.tsx b/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx
index 547d0467ac..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 (
@@ -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")}
- />
- )}
- >
-
-
{
+ 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/LdapMapperList.tsx b/src/user-federation/ldap/mappers/LdapMapperList.tsx
new file mode 100644
index 0000000000..c2b9a1a09d
--- /dev/null
+++ b/src/user-federation/ldap/mappers/LdapMapperList.tsx
@@ -0,0 +1,149 @@
+import React, { useState, useEffect } from "react";
+import { useTranslation } from "react-i18next";
+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 { 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";
+
+export const LdapMapperList = () => {
+ const [mappers, setMappers] = useState();
+
+ const { t } = useTranslation("user-federation");
+ const adminClient = useAdminClient();
+ const { addAlert } = useAlerts();
+
+ const { url } = useRouteMatch();
+
+ 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);
+ // TODO: remove after debugging
+ console.log("LdapMapperList - setMappers being set with:");
+ console.log(mappers);
+ },
+ handleError
+ );
+ }, [key]);
+
+ if (!mappers) {
+ return (
+ <>
+
+ >
+ );
+ }
+
+ const loader = async () =>
+ Promise.resolve(
+ (mappers || []).map((mapper) => {
+ return {
+ ...mapper,
+ name: mapper.name,
+ type: mapper.providerId,
+ } as ComponentRepresentation;
+ })
+ );
+
+ const getUrl = (url: string) => {
+ if (url.indexOf("/mappers") === -1) {
+ return `${url}/mappers`;
+ }
+ return `${url}`;
+ };
+
+ const MapperLink = (mapper: ComponentRepresentation) => (
+ <>
+ {mapper.name}
+ >
+ );
+
+ return (
+ <>
+
+
+
+ }
+ actions={[
+ {
+ title: t("common:delete"),
+ onRowClick: async (mapper) => {
+ try {
+ addAlert(
+ // t("common:mappingDeletedError"),
+ "Delete functionality not implemented yet!",
+ AlertVariant.success
+ );
+ } catch (error) {
+ addAlert(
+ t("common:mappingDeletedError", { error }),
+ AlertVariant.danger
+ );
+ }
+ return true;
+ },
+ },
+ ]}
+ columns={[
+ {
+ name: "name",
+ cellRenderer: MapperLink,
+ },
+ {
+ name: "type",
+ },
+ ]}
+ emptyState={
+
+ }
+ />
+ >
+ );
+};
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/LdapMapperRoleGroup.tsx b/src/user-federation/ldap/mappers/LdapMapperRoleGroup.tsx
new file mode 100644
index 0000000000..9899bfd9ae
--- /dev/null
+++ b/src/user-federation/ldap/mappers/LdapMapperRoleGroup.tsx
@@ -0,0 +1,571 @@
+import {
+ FormGroup,
+ Select,
+ SelectOption,
+ SelectVariant,
+ Switch,
+ TextInput,
+} from "@patternfly/react-core";
+import React, { useState } 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 LdapMapperRoleGroupProps = {
+ form: UseFormMethods;
+ type: string;
+};
+
+export const LdapMapperRoleGroup = ({
+ form,
+ type,
+}: LdapMapperRoleGroupProps) => {
+ const { t } = useTranslation("user-federation");
+ const helpText = useTranslation("user-federation-help").t;
+
+ const [isMbAttTypeDropdownOpen, setIsMbAttTypeDropdownOpen] = useState(false);
+ const [isModeDropdownOpen, setIsModeDropdownOpen] = useState(false);
+ const [
+ isRetrieveStratDropdownOpen,
+ setIsRetrieveStratDropdownOpen,
+ ] = useState(false);
+ const [isClientIdDropdownOpen, setIsClientIdDropdownOpen] = useState(false);
+
+ let isRole = true;
+ const groupMapper = "group-ldap-mapper";
+
+ if (type === groupMapper) {
+ isRole = false;
+ }
+
+ return (
+ <>
+
+
+
+ }
+ fieldId="kc-ldap-dn"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-name-attribute"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-object-classes"
+ isRequired
+ >
+
+
+ {!isRole && (
+ <>
+
+ }
+ fieldId="kc-preserve-inheritance"
+ hasNoPaddingTop
+ >
+ (
+ onChange([`${value}`])}
+ isChecked={value[0] === "true"}
+ label={t("common:on")}
+ labelOff={t("common:off")}
+ />
+ )}
+ >
+
+
+ }
+ fieldId="kc-ignore-missing"
+ hasNoPaddingTop
+ >
+ (
+ onChange([`${value}`])}
+ isChecked={value[0] === "true"}
+ label={t("common:on")}
+ labelOff={t("common:off")}
+ />
+ )}
+ >
+
+ >
+ )}
+
+ }
+ fieldId="kc-membership-ldap-attribute"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-membership-attribute-type"
+ >
+ (
+
+ )}
+ >
+
+
+ }
+ fieldId="kc-membership-user-ldap-attribute"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-ldap-filter"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-mode"
+ >
+ (
+
+ )}
+ >
+
+
+ }
+ fieldId="kc-user-retrieve-strategy"
+ >
+ (
+
+ )}
+ >
+
+
+ }
+ fieldId="kc-member-of-attribute"
+ isRequired
+ >
+
+
+ {isRole && (
+ <>
+
+ }
+ fieldId="kc-use-realm-roles"
+ hasNoPaddingTop
+ >
+ (
+ onChange([`${value}`])}
+ isChecked={value[0] === "true"}
+ label={t("common:on")}
+ labelOff={t("common:off")}
+ />
+ )}
+ >
+
+
+ }
+ fieldId="kc-client-id"
+ >
+ (
+
+ )}
+ >
+
+ >
+ )}
+ {!isRole && (
+ <>
+
+ }
+ fieldId="kc-mapped-attributes"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-drop-nonexisting"
+ hasNoPaddingTop
+ >
+ (
+ onChange([`${value}`])}
+ isChecked={value[0] === "true"}
+ label={t("common:on")}
+ labelOff={t("common:off")}
+ />
+ )}
+ >
+
+
+ }
+ fieldId="kc-path"
+ isRequired
+ >
+
+
+ >
+ )}
+
+ >
+ );
+};
diff --git a/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx b/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx
new file mode 100644
index 0000000000..31f11198d1
--- /dev/null
+++ b/src/user-federation/ldap/mappers/LdapMapperUserAttribute.tsx
@@ -0,0 +1,216 @@
+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 LdapMapperUserAttributeProps = {
+ form: UseFormMethods;
+ mapperType: string | undefined;
+};
+
+export const LdapMapperUserAttribute = ({
+ form,
+ mapperType,
+}: LdapMapperUserAttributeProps) => {
+ const { t } = useTranslation("user-federation");
+ const helpText = useTranslation("user-federation-help").t;
+
+ return (
+ <>
+
+
+
+ }
+ fieldId="kc-user-model-attribute"
+ isRequired
+ >
+
+
+
+ }
+ fieldId="kc-ldap-attribute"
+ isRequired
+ >
+
+
+
+ }
+ 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
new file mode 100644
index 0000000000..8f6e6d1d72
--- /dev/null
+++ b/src/user-federation/ldap/mappers/LdapMappingDetails.tsx
@@ -0,0 +1,164 @@
+import React, { useState, useEffect } from "react";
+import {
+ ActionGroup,
+ AlertVariant,
+ Button,
+ Form,
+ PageSection,
+} from "@patternfly/react-core";
+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 { 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 { LdapMapperRoleGroup } from "./LdapMapperRoleGroup";
+
+import { useRealm } from "../../../context/realm-context/RealmContext";
+
+export const LdapMappingDetails = () => {
+ const form = useForm();
+ const [mapper, setMapper] = useState();
+
+ const adminClient = useAdminClient();
+ const { mapperId } = useParams<{ mapperId: string }>();
+ const history = useHistory();
+
+ const { realm } = useRealm();
+ const id = mapperId;
+ const { t } = useTranslation("user-federation");
+ const { addAlert } = useAlerts();
+
+ 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);
+ }
+ }
+ })();
+ }, []);
+
+ 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]);
+ }
+ });
+ };
+
+ const save = () => {
+ addAlert(
+ t(
+ id === "new"
+ ? "Create functionality not implemented yet!"
+ : "Save functionality not implemented yet!"
+ ),
+ AlertVariant.success
+ );
+ history.push(`/${realm}/user-federation`);
+ };
+
+ return (
+ <>
+
+
+ {mapper
+ ? (mapper.providerId! === "certificate-ldap-mapper" ||
+ mapper.providerId! === "user-attribute-ldap-mapper") && (
+
+ )
+ : ""}
+ {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" && (
+
+ )
+ : ""}
+ {mapper
+ ? (mapper.providerId! === "role-ldap-mapper" ||
+ mapper.providerId! === "group-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..12002ff48e
--- /dev/null
+++ b/src/user-federation/ldap/mappers/shared/LdapMapperGeneral.tsx
@@ -0,0 +1,71 @@
+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
+ >
+
+
+ >
+ );
+};
diff --git a/src/user-federation/messages.json b/src/user-federation/messages.json
index 13e6580a79..2440958b80 100644
--- a/src/user-federation/messages.json
+++ b/src/user-federation/messages.json
@@ -119,8 +119,6 @@
"validateRdnLdapAttribute": "You must enter an RDN LDAP attribute",
"validateCustomUserSearchFilter": "Filter must be enclosed in parentheses, for example: (filter)",
- "id": "ID",
- "mapperType": "Mapper type",
"mapperTypeMsadUserAccountControlManager": "msad-user-account-control-mapper",
"mapperTypeMsadLdsUserAccountControlMapper": "msad-user-account-control-mapper",
"mapperTypeGroupLdapMapper": "group-ldap-mapper",
@@ -133,23 +131,36 @@
"mapperTypeHardcodedLdapGroupMapper": "hardcoded-ldap-group-mapper",
"mapperTypeLdapAttributeMapper": "hardcoded-ldap-attribute-mapper",
- "passwordPolicyHintsEnabled": "Password policy hints enabled",
+ "ldapMappersList": "LDAP Mappers",
+
+ "ldapFullNameAttribute": "LDAP full name attribute",
+ "writeOnly": "Write only",
- "ldapGroupsDn": "LDAP Groups DN",
- "groupNameLDAPAttribute": "Group name LDAP attribute",
+ "ldapGroupsDn": "LDAP groups DN",
+ "groupNameLdapAttribute": "Group name LDAP attribute",
"groupObjectClasses": "Group object classes",
"preserveGroupInheritance": "Preserve group inheritance",
"ignoreMissingGroups": "Ignore missing groups",
+ "userGroupsRetrieveStrategy": "User groups retrieve strategy",
+ "mappedGroupAttributes": "Mapped group attributes",
+ "dropNonexistingGroupsDuringSync": "Drop non-existing groups during sync",
+ "groupsPath": "Groups path",
+
"membershipLdapAttribute": "Membership LDAP attribute",
"membershipAttributeType": "Membership attribute type",
"membershipUserLdapAttribute": "Membership user LDAP attribute",
"ldapFilter": "LDAP filter",
"mode": "Mode",
- "userGroupsRetrieveStrategy": "User groups retrieve strategy",
"memberofLdapAttribute": "Member-of LDAP attribute",
- "mappedGroupAttributes": "Mapped group attributes",
- "dropNonexistingGroupsDuringSync": "Drop non-existing groups during sync",
- "groupsPath": "Groups path",
+
+ "ldapRolesDn": "LDAP roles DN",
+ "roleNameLdapAttribute": "Role name LDAP attribute",
+ "roleObjectClasses": "Role object classes",
+ "userRolesRetrieveStrategy": "User roles retrieve strategy",
+ "useRealmRolesMapping": "Use realm roles mapping",
+
+ "ldapAttributeName": "LDAP attribute name",
+ "ldapAttributeValue": "LDAP attribute value",
"userModelAttribute": "User model attribute",
"ldapAttribute": "LDAP attribute",
@@ -157,21 +168,15 @@
"alwaysReadValueFromLdap": "Always read value from LDAP",
"isMandatoryInLdap": "Is mandatory in LDAP",
"isBinaryAttribute": "Is binary attribute",
-
- "ldapRolesDN": "LDAP roles DN",
- "roleNameLdapAttribute": "Role Name LDAP attribute",
- "roleObjectClasses": "Role object classes",
- "useRealmRolesMapping": "Use realm roles mapping",
- "clientId": "Client ID",
-
"derFormatted": "DER formatted",
- "ldapFullNameAttribute": "LDAP full name attribute",
- "writeOnly": "Write only",
+ "passwordPolicyHintsEnabled": "Password policy hints enabled",
- "group": "Group",
+ "userModelAttributeName": "User model attribute name",
+ "attributeValue": "Attribute value",
- "ldapAttributeName": "LDAP attribute name",
- "ldapAttributeValue": "LDAP attribute value"
+ "selectRole": "Select role",
+
+ "group": "Group"
}
}
diff --git a/src/user/UserForm.tsx b/src/user/UserForm.tsx
index bb8e4e2f4a..3f7bfcf2ac 100644
--- a/src/user/UserForm.tsx
+++ b/src/user/UserForm.tsx
@@ -100,7 +100,7 @@ export const UserForm = ({
{editMode ? (
<>