Merge pull request #490 from mfrances17/ldap-mappers-layout

User fed mappers layout and partial logic
This commit is contained in:
mfrances17 2021-04-05 17:04:10 -04:00 committed by GitHub
commit f3dde7d2ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1802 additions and 179 deletions

View file

@ -157,8 +157,8 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => {
)}
{isBuiltIn && rows.length === 0 && (
<ListEmptyState
message={t("emptyMappers")}
instructions={t("emptyBuiltInMappersInstructions")}
message={t("common:emptyMappers")}
instructions={t("common:emptyBuiltInMappersInstructions")}
/>
)}
</Modal>

View file

@ -146,7 +146,7 @@ export const RoleMappingForm = () => {
return (
<>
<ViewHeader
titleKey="client-scopes:addMapper"
titleKey="common:addMapper"
subKey="client-scopes:addMapperExplain"
/>
<PageSection variant="light">
@ -156,11 +156,11 @@ export const RoleMappingForm = () => {
role="manage-clients"
>
<FormGroup
label={t("protocolMapper")}
label={t("common:mapperType")}
labelIcon={
<HelpItem
helpText="client-scopes-help:protocolMapper"
forLabel={t("protocolMapper")}
forLabel={t("common:mapperType")}
forID="protocolMapper"
/>
}

View file

@ -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={
<Dropdown
onSelect={() => setMapperAction(false)}
@ -130,7 +133,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
onToggle={() => setMapperAction(!mapperAction)}
toggleIndicator={CaretDownIcon}
>
{t("addMapper")}
{t("common:addMapper")}
</DropdownToggle>
}
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={
<ListEmptyState
message={t("emptyMappers")}
instructions={t("emptyMappersInstructions")}
primaryActionText={t("emptyPrimaryAction")}
message={t("common:emptyMappers")}
instructions={t("common:emptyMappersInstructions")}
primaryActionText={t("common:emptyPrimaryAction")}
onPrimaryAction={() => toggleAddMapperDialog(true)}
secondaryActions={[
{

View file

@ -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 = () => {
<>
<DeleteConfirm />
<ViewHeader
titleKey={mapping ? mapping.name! : t("addMapper")}
titleKey={mapping ? mapping.name! : t("common:addMapper")}
subKey={mapperId.match(isGuid) ? mapperId : ""}
badge={mapping?.protocol}
dropdownItems={

View file

@ -322,7 +322,7 @@ export const ClientScopeForm = () => {
<Tab
isHidden={!id}
eventKey="mappers"
title={<TabTitleText>{t("mappers")}</TabTitleText>}
title={<TabTitleText>{t("common:mappers")}</TabTitleText>}
>
{clientScope && (
<MapperList clientScope={clientScope} refresh={refresh} />

View file

@ -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",

View file

@ -22,11 +22,11 @@ export const ClientDescription = () => {
labelIcon={
<HelpItem
helpText="clients-help:clientID"
forLabel={t("clientID")}
forLabel={t("common:clientID")}
forID="kc-client-id"
/>
}
label={t("clientID")}
label={t("common:clientID")}
fieldId="kc-client-id"
helperTextInvalid={t("common:required")}
validated={

View file

@ -144,7 +144,7 @@ export const ClientsSection = () => {
columns={[
{
name: "clientId",
displayKey: "clients:clientID",
displayKey: "common:clientID",
cellRenderer: ClientDetailLink,
},
{ name: "protocol", displayKey: "common:type" },

View file

@ -74,7 +74,7 @@ export const InitialAccessTokenList = () => {
columns={[
{
name: "id",
displayKey: "clients:id",
displayKey: "common:id",
},
{
name: "timestamp",

View file

@ -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",

View file

@ -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"
}
}

View file

@ -107,7 +107,7 @@ export type DataListProps<T> = {
* <KeycloakDataTable columns={[
* {
* name: "clientId", //name of the field from the array of object the loader returns to display in this column
* displayKey: "clients:clientID", //i18n key to use to lookup the name of the column header
* displayKey: "common:clientID", //i18n key to use to lookup the name of the column header
* cellRenderer: ClientDetailLink, //optionally you can use a component to render the column when you don't want just the content of the field, the whole row / entire object is passed in.
* }
* ]}

View file

@ -174,7 +174,7 @@ export const GroupsSection = () => {
<Tab
data-testid="attributes"
eventKey={2}
title={<TabTitleText>{t("attributes")}</TabTitleText>}
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
>
<GroupAttributes />
</Tab>

View file

@ -327,7 +327,7 @@ export const RealmRoleTabs = () => {
) : null}
<Tab
eventKey="attributes"
title={<TabTitleText>{t("attributes")}</TabTitleText>}
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
>
<AttributesForm
form={form}

View file

@ -1,6 +1,5 @@
{
"roles": {
"attributes": "Attributes",
"addAttributeText": "Add an attribute",
"deleteAttributeText": "Delete an attribute",
"associatedRolesText": "Associated roles",
@ -59,7 +58,6 @@
"noDirectUsers": "No direct users",
"noUsersEmptyStateDescription": "Only the users with this role directly assigned will appear under this tab. If you need to find users assigned to this role, go to",
"noUsersEmptyStateDescriptionContinued": "to find them. Users that already have this role as an effective role cannot be added here.",
"id": "ID",
"groups": "Groups",
"or": "or",
"users": "Users",

View file

@ -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;
@ -95,13 +96,13 @@ export const routes: RoutesFn = (t: TFunction) => [
{
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,

View file

@ -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",

View file

@ -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,6 +281,12 @@ export const UserFederationLdapSettings = () => {
)}
/>
<PageSection variant="light" isFilled>
<KeycloakTabs isBox>
<Tab
id="settings"
eventKey="settings"
title={<TabTitleText>{t("common:settings")}</TabTitleText>}
>
<ScrollForm
sections={[
t("generalOptions"),
@ -314,6 +325,15 @@ export const UserFederationLdapSettings = () => {
</Button>
</ActionGroup>
</Form>
</Tab>
<Tab
id="mappers"
eventKey="mappers"
title={<TabTitleText>{t("common:mappers")}</TabTitleText>}
>
<LdapMapperList />
</Tab>
</KeycloakTabs>
</PageSection>
</>
);

View file

@ -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",

View file

@ -23,7 +23,7 @@ export const LdapSettingsKerberosIntegration = ({
const allowKerberosAuth: [string] = useWatch({
control: form.control,
name: "config.allowKerberosAuthentication",
defaultValue: ["true"],
defaultValue: ["false"],
});
return (
@ -169,8 +169,6 @@ export const LdapSettingsKerberosIntegration = ({
</div>
)}
</FormGroup>
</>
)}
<FormGroup
label={t("debug")}
@ -201,7 +199,8 @@ export const LdapSettingsKerberosIntegration = ({
)}
></Controller>
</FormGroup>
</>
)}
<FormGroup
label={t("useKerberosForPasswordAuthentication")}
labelIcon={

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("ldapFullNameAttribute")}
labelIcon={
<HelpItem
helpText={helpText("ldapFullNameAttributeHelp")}
forLabel={t("ldapFullNameAttribute")}
forID="kc-full-name-attribute"
/>
}
fieldId="kc-full-name-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-full-name-attribute"
data-testid="full-name-attribute"
name="config.ldap-full-name-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("readOnly")}
labelIcon={
<HelpItem
helpText={helpText("readOnlyHelp")}
forLabel={t("readOnly")}
forID="kc-read-only"
/>
}
fieldId="kc-read-only"
hasNoPaddingTop
>
<Controller
name="config.read-only"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-read-only"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("writeOnly")}
labelIcon={
<HelpItem
helpText={helpText("writeOnlyHelp")}
forLabel={t("writeOnly")}
forID="kc-write-only"
/>
}
fieldId="kc-read-only"
hasNoPaddingTop
>
<Controller
name="config.write-only"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-write-only"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("userModelAttributeName")}
labelIcon={
<HelpItem
helpText={helpText("userModelAttributeNameHelp")}
forLabel={t("userModelAttributeName")}
forID="kc-user-model-attribute"
/>
}
fieldId="kc-user-model-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-user-model-attribute"
data-testid="user-model-attribute"
name="config.user-model-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("attributeValue")}
labelIcon={
<HelpItem
helpText={helpText("attributeValueHelp")}
forLabel={t("attributeValue")}
forID="kc-attribute-value"
/>
}
fieldId="kc-attribute-value"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-attribute-value"
data-testid="attribute-value"
name="config.attribute-value"
ref={form.register}
/>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("ldapAttributeName")}
labelIcon={
<HelpItem
helpText={helpText("ldapAttributeNameHelp")}
forLabel={t("ldapAttributeName")}
forID="kc-ldap-attribute-name"
/>
}
fieldId="kc-ldap-attribute-name"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-attribute-name"
data-testid="ldap-attribute-name"
name="config.ldap-attribute-name"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("ldapAttributeValue")}
labelIcon={
<HelpItem
helpText={helpText("ldapAttributeValueHelp")}
forLabel={t("ldapAttributeValue")}
forID="kc-ldap-attribute-value"
/>
}
fieldId="kc-ldap-attribute-value"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-attribute-value"
data-testid="ldap-attribute-value"
name="config.ldap-attribute-value"
ref={form.register}
/>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("group")}
labelIcon={
<HelpItem
helpText={helpText("groupHelp")}
forLabel={t("group")}
forID="kc-group"
/>
}
fieldId="kc-group"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-group"
data-testid="group"
name="config.group"
ref={form.register}
/>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("common:role")}
labelIcon={
<HelpItem
helpText={helpText("roleHelp")}
forLabel={t("common:role")}
forID="kc-role"
/>
}
fieldId="kc-role"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-role"
data-testid="role"
name="config.role"
ref={form.register}
/>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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<ComponentRepresentation[]>();
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 (
<>
<ListEmptyState
message={t("common:emptyMappers")}
instructions={t("common:emptyMappersInstructions")}
primaryActionText={t("common:emptyPrimaryAction")}
/>
</>
);
}
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) => (
<>
<Link to={`${getUrl(url)}/${mapper.id}`}>{mapper.name}</Link>
</>
);
return (
<>
<KeycloakDataTable
key={key}
loader={loader}
ariaLabelKey="ldapMappersList"
searchPlaceholderKey="common:searchForMapper"
toolbarItem={
<ToolbarItem>
<Button
data-testid="createMapperBtn"
variant="primary"
// onClick={handleModalToggle}
onClick={() =>
addAlert(
t("Add functionality not implemented yet!"),
AlertVariant.success
)
}
>
{t("common:addMapper")}
</Button>
</ToolbarItem>
}
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={
<ListEmptyState
message={t("common:emptyMappers")}
instructions={t("common:emptyMappersInstructions")}
primaryActionText={t("common:emptyPrimaryAction")}
/>
}
/>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("passwordPolicyHintsEnabled")}
labelIcon={
<HelpItem
helpText={helpText("passwordPolicyHintsEnabledHelp")}
forLabel={t("passwordPolicyHintsEnabled")}
forID="kc-pw-policy-hints-enabled"
/>
}
fieldId="kc-der-formatted"
hasNoPaddingTop
>
<Controller
name="config.ldap-password-policy-hints-enabled"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-pw-policy-hints-enabled"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={isRole ? t("ldapRolesDn") : t("ldapGroupsDn")}
labelIcon={
<HelpItem
helpText={
isRole
? helpText("ldapRolesDnHelp")
: helpText("ldapGroupsDnHelp")
}
forLabel={isRole ? t("ldapRolesDN") : t("ldapGroupsDN")}
forID="kc-ldap-dn"
/>
}
fieldId="kc-ldap-dn"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-dn"
data-testid="ldap-dn"
name={isRole ? "config.roles-dn" : "config.groups-dn"}
ref={form.register}
/>
</FormGroup>
<FormGroup
label={
isRole ? t("roleNameLdapAttribute") : t("groupNameLdapAttribute")
}
labelIcon={
<HelpItem
helpText={
isRole
? helpText("roleNameLdapAttributeHelp")
: helpText("groupNameLdapAttributeHelp")
}
forLabel={
isRole ? t("roleNameLdapAttribute") : t("roleNameLdapAttribute")
}
forID="kc-name-attribute"
/>
}
fieldId="kc-name-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-name-attribute"
data-testid="name-attribute"
name={
isRole
? "config.role-name-ldap-attribute"
: "config.group-name-ldap-attribute"
}
ref={form.register}
/>
</FormGroup>
<FormGroup
label={isRole ? t("roleObjectClasses") : t("groupObjectClasses")}
labelIcon={
<HelpItem
helpText={
isRole
? helpText("roleObjectClassesHelp")
: helpText("groupObjectClassesHelp")
}
forLabel={
isRole ? t("roleObjectClasses") : t("groupObjectClasses")
}
forID="kc-object-classes"
/>
}
fieldId="kc-object-classes"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-object-classes"
data-testid="object-classes"
name={
isRole
? "config.role-object-classes"
: "config.group-object-classes"
}
ref={form.register}
/>
</FormGroup>
{!isRole && (
<>
<FormGroup
label={t("preserveGroupInheritance")}
labelIcon={
<HelpItem
helpText={helpText("preserveGroupInheritanceHelp")}
forLabel={t("preserveGroupInheritance")}
forID="kc-preserve-inheritance"
/>
}
fieldId="kc-preserve-inheritance"
hasNoPaddingTop
>
<Controller
name="config.preserve-group-inheritance"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-preserve-inheritance"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("ignoreMissingGroups")}
labelIcon={
<HelpItem
helpText={helpText("ignoreMissingGroupsHelp")}
forLabel={t("ignoreMissingGroups")}
forID="kc-ignore-missing"
/>
}
fieldId="kc-ignore-missing"
hasNoPaddingTop
>
<Controller
name="config.ignore-missing-groups"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-ignore-missing"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
</>
)}
<FormGroup
label={t("membershipLdapAttribute")}
labelIcon={
<HelpItem
helpText={helpText("membershipLdapAttributeHelp")}
forLabel={t("membershipLdapAttribute")}
forID="kc-membership-ldap-attribute"
/>
}
fieldId="kc-membership-ldap-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-membership-ldap-attribute"
data-testid="membership-ldap-attribute"
name="config.membership-ldap-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("membershipAttributeType")}
labelIcon={
<HelpItem
helpText={helpText("membershipAttributeTypeHelp")}
forLabel={t("membershipAttributeType")}
forID="kc-membership-attribute-type"
/>
}
fieldId="kc-membership-attribute-type"
>
<Controller
name="config.membership-attribute-type[0]"
defaultValue=""
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-membership-attribute-type"
onToggle={() =>
setIsMbAttTypeDropdownOpen(!isMbAttTypeDropdownOpen)
}
isOpen={isMbAttTypeDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsMbAttTypeDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value="DN">
DN
</SelectOption>
<SelectOption key={1} value="UID">
UID
</SelectOption>
</Select>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("membershipUserLdapAttribute")}
labelIcon={
<HelpItem
helpText={helpText("membershipUserLdapAttributeHelp")}
forLabel={t("membershipUserLdapAttribute")}
forID="kc-membership-user-ldap-attribute"
/>
}
fieldId="kc-membership-user-ldap-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-membership-user-ldap-attribute"
data-testid="membership-user-ldap-attribute"
name="config.membership-user-ldap-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("ldapFilter")}
labelIcon={
<HelpItem
helpText={helpText("ldapFilterHelp")}
forLabel={t("ldapFilter")}
forID="kc-ldap-filter"
/>
}
fieldId="kc-ldap-filter"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-filter"
data-testid="ldap-filter"
name="config.ldap-filter"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("mode")}
labelIcon={
<HelpItem
helpText={helpText("modeHelp")}
forLabel={t("mode")}
forID="kc-mode"
/>
}
fieldId="kc-mode"
>
<Controller
name="config.mode[0]"
defaultValue=""
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-mode"
onToggle={() => setIsModeDropdownOpen(!isModeDropdownOpen)}
isOpen={isModeDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsModeDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value="READ_ONLY">
READ_ONLY
</SelectOption>
<SelectOption key={1} value="LDAP_ONLY">
LDAP_ONLY
</SelectOption>
<SelectOption key={2} value="IMPORT">
IMPORT
</SelectOption>
</Select>
)}
></Controller>
</FormGroup>
<FormGroup
label={
isRole
? t("userRolesRetrieveStrategy")
: t("userGroupsRetrieveStrategy")
}
labelIcon={
<HelpItem
helpText={
isRole
? helpText("userRolesRetrieveStrategyHelp")
: helpText("userGroupsRetrieveStrategyHelp")
}
forLabel={
isRole
? t("userRolesRetrieveStrategy")
: t("userGroupsRetrieveStrategy")
}
forID="kc-user-retrieve-strategy"
/>
}
fieldId="kc-user-retrieve-strategy"
>
<Controller
name={
isRole
? "config.user-roles-retrieve-strategy[0]"
: "config.user-groups-retrieve-strategy[0]"
}
defaultValue=""
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-user-retrieve-strategy"
onToggle={() =>
setIsRetrieveStratDropdownOpen(!isRetrieveStratDropdownOpen)
}
isOpen={isRetrieveStratDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsRetrieveStratDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value="LOAD_ROLES_BY_MEMBER_ATTRIBUTE">
LOAD_ROLES_BY_MEMBER_ATTRIBUTE
</SelectOption>
<SelectOption
key={1}
value="GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE"
>
GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE
</SelectOption>
<SelectOption
key={2}
value="LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"
>
LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY
</SelectOption>
</Select>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("memberofLdapAttribute")}
labelIcon={
<HelpItem
helpText={helpText("memberofLdapAttributeHelp")}
forLabel={t("memberofLdapAttribute")}
forID="kc-member-of-attribute"
/>
}
fieldId="kc-member-of-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-member-of-attribute"
data-testid="member-of-attribute"
name="config.memberof-ldap-attribute"
ref={form.register}
/>
</FormGroup>
{isRole && (
<>
<FormGroup
label={t("useRealmRolesMapping")}
labelIcon={
<HelpItem
helpText={helpText("useRealmRolesMappingHelp")}
forLabel={t("useRealmRolesMapping")}
forID="kc-use-realm-roles"
/>
}
fieldId="kc-use-realm-roles"
hasNoPaddingTop
>
<Controller
name="config.use-realm-roles-mapping"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-use-realm-roles"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("common:clientId")}
labelIcon={
<HelpItem
helpText={helpText("clientIdHelp")}
forLabel={t("common:clientId")}
forID="kc-client-id"
/>
}
fieldId="kc-client-id"
>
<Controller
name="config.client-id[0]"
defaultValue=""
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-client-id"
onToggle={() =>
setIsClientIdDropdownOpen(!isClientIdDropdownOpen)
}
isOpen={isClientIdDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsClientIdDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value="account">
Need to fetch clients here
</SelectOption>
<SelectOption key={1} value="admin-cli">
These are placeholders
</SelectOption>
</Select>
)}
></Controller>
</FormGroup>
</>
)}
{!isRole && (
<>
<FormGroup
label={t("mappedGroupAttributes")}
labelIcon={
<HelpItem
helpText={helpText("mappedGroupAttributesHelp")}
forLabel={t("mappedGroupAttributes")}
forID="kc-mapped-attributes"
/>
}
fieldId="kc-mapped-attributes"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-mapped-attributes"
data-testid="mapped-attributes"
name="config.mapped-group-attributes"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("dropNonexistingGroupsDuringSync")}
labelIcon={
<HelpItem
helpText={helpText("dropNonexistingGroupsDuringSyncHelp")}
forLabel={t("dropNonexistingGroupsDuringSync")}
forID="kc-drop-nonexisting"
/>
}
fieldId="kc-drop-nonexisting"
hasNoPaddingTop
>
<Controller
name="config.drop-non-existing-groups-during-sync"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-drop-nonexisting"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("groupsPath")}
labelIcon={
<HelpItem
helpText={helpText("groupsPathHelp")}
forLabel={t("groupsPath")}
forID="kc-path"
/>
}
fieldId="kc-path"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-path"
data-testid="path"
name="config.groups-path"
ref={form.register}
/>
</FormGroup>
</>
)}
</FormAccess>
</>
);
};

View file

@ -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 (
<>
<FormAccess role="manage-realm" isHorizontal>
<LdapMapperGeneral form={form} />
<FormGroup
label={t("userModelAttribute")}
labelIcon={
<HelpItem
helpText={helpText("userModelAttributeHelp")}
forLabel={t("userModelAttribute")}
forID="kc-user-model-attribute"
/>
}
fieldId="kc-user-model-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-user-model-attribute"
data-testid="user-model-attribute"
name="config.user-model-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("ldapAttribute")}
labelIcon={
<HelpItem
helpText={helpText("ldapAttributeHelp")}
forLabel={t("ldapAttribute")}
forID="kc-ldap-attribute"
/>
}
fieldId="kc-ldap-attribute"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-attribute"
data-testid="ldap-attribute"
name="config.ldap-attribute"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("readOnly")}
labelIcon={
<HelpItem
helpText={helpText("readOnlyHelp")}
forLabel={t("readOnly")}
forID="kc-read-only"
/>
}
fieldId="kc-read-only"
hasNoPaddingTop
>
<Controller
name="config.read-only"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-read-only"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("alwaysReadValueFromLdap")}
labelIcon={
<HelpItem
helpText={helpText("alwaysReadValueFromLdapHelp")}
forLabel={t("alwaysReadValueFromLdap")}
forID="kc-always-read-value"
/>
}
fieldId="kc-always-read-value"
hasNoPaddingTop
>
<Controller
name="config.always-read-value-from-ldap"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-always-read-value"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("isMandatoryInLdap")}
labelIcon={
<HelpItem
helpText={helpText("isMandatoryInLdapHelp")}
forLabel={t("isMandatoryInLdap")}
forID="kc-is-mandatory"
/>
}
fieldId="kc-is-mandatory"
hasNoPaddingTop
>
<Controller
name="config.is-mandatory-in-ldap"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-is-mandatory"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("isBinaryAttribute")}
labelIcon={
<HelpItem
helpText={helpText("isBinaryAttributeHelp")}
forLabel={t("isBinaryAttribute")}
forID="kc-is-binary"
/>
}
fieldId="kc-is-binary"
hasNoPaddingTop
>
<Controller
name="config.is-binary-attribute"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-is-binary"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
{mapperType === "certificate-ldap-mapper" ? (
<>
<FormGroup
label={t("derFormatted")}
labelIcon={
<HelpItem
helpText={helpText("derFormattedHelp")}
forLabel={t("derFormatted")}
forID="kc-der-formatted"
/>
}
fieldId="kc-der-formatted"
hasNoPaddingTop
>
<Controller
name="config.is-der-formatted"
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-der-formatted"}
isDisabled={false}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
)}
></Controller>
</FormGroup>
</>
) : (
<></>
)}
</FormAccess>
</>
);
};

View file

@ -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<ComponentRepresentation>();
const [mapper, setMapper] = useState<ComponentRepresentation>();
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 (
<>
<ViewHeader titleKey={mapper ? mapper.name! : ""} subKey="" />
<PageSection variant="light" isFilled>
{mapper
? (mapper.providerId! === "certificate-ldap-mapper" ||
mapper.providerId! === "user-attribute-ldap-mapper") && (
<LdapMapperUserAttribute
form={form}
mapperType={mapper?.providerId}
/>
)
: ""}
{mapper
? mapper.providerId! === "msad-user-account-control-mapper" && (
<LdapMapperMsadUserAccount form={form} />
)
: ""}
{mapper
? mapper.providerId! === "msad-lds-user-account-control-mapper" && (
<LdapMapperMsadLdsUserAccount form={form} />
)
: ""}
{mapper
? mapper.providerId! === "full-name-ldap-mapper" && (
<LdapMapperFullNameAttribute form={form} />
)
: ""}
{mapper
? mapper.providerId! === "hardcoded-ldap-role-mapper" && (
<LdapMapperHardcodedLdapRole form={form} />
)
: ""}
{mapper
? mapper.providerId! === "hardcoded-ldap-group-mapper" && (
<LdapMapperHardcodedLdapGroup form={form} />
)
: ""}
{mapper
? mapper.providerId! === "hardcoded-ldap-attribute-mapper" && (
<LdapMapperHardcodedLdapAttribute form={form} />
)
: ""}
{mapper
? mapper.providerId! === "hardcoded-attribute-mapper" && (
<LdapMapperHardcodedAttribute form={form} />
)
: ""}
{mapper
? (mapper.providerId! === "role-ldap-mapper" ||
mapper.providerId! === "group-ldap-mapper") && (
<LdapMapperRoleGroup form={form} type={mapper.providerId} />
)
: ""}
<Form onSubmit={form.handleSubmit(save)}>
<ActionGroup>
<Button
isDisabled={!form.formState.isDirty}
variant="primary"
type="submit"
data-testid="ldap-save"
>
{t("common:save")}
</Button>
<Button
variant="link"
onClick={() =>
history.push(
`/${realm}/user-federation/ldap/${mapper!.parentId}/mappers`
)
}
data-testid="ldap-cancel"
>
{t("common:cancel")}
</Button>
</ActionGroup>
</Form>
</PageSection>
</>
);
};

View file

@ -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 (
<>
<FormGroup label={t("common:id")} fieldId="kc-ldap-mapper-id">
<TextInput
isRequired
type="text"
id="kc-ldap-mapper-id"
data-testid="ldap-mapper-id"
name="id"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("common:name")}
labelIcon={
<HelpItem
helpText={helpText("nameHelp")}
forLabel={t("common:name")}
forID="kc-ldap-mapper-name"
/>
}
fieldId="kc-ldap-mapper-name"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-mapper-name"
data-testid="ldap-mapper-name"
name="name"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("common:mapperType")}
labelIcon={
<HelpItem
helpText={helpText("mapperTypeHelp")}
forLabel={t("common:mapperType")}
forID="kc-ldap-mapper-type"
/>
}
fieldId="kc-ldap-mapper-type"
isRequired
>
<TextInput
isRequired
type="text"
id="kc-ldap-mapper-type"
data-testid="ldap-mapper-type"
name="providerId"
ref={form.register}
/>
</FormGroup>
</>
);
};

View file

@ -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",
"ldapGroupsDn": "LDAP Groups DN",
"groupNameLDAPAttribute": "Group name LDAP attribute",
"ldapFullNameAttribute": "LDAP full name attribute",
"writeOnly": "Write only",
"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"
}
}

View file

@ -100,7 +100,7 @@ export const UserForm = ({
{editMode ? (
<>
<FormGroup
label={t("id")}
label={t("common:id")}
fieldId="kc-id"
isRequired
validated={errors.id ? "error" : "default"}

View file

@ -19,7 +19,6 @@
"removedGroupMembershipError": "Error removing group membership",
"path": "Path",
"emptyInstructions": "Change your search criteria or add a user",
"id": "ID",
"createdAt": "Created at",
"username": "Username",
"email": "Email",