diff --git a/features/admin.core.v1/configs/app.ts b/features/admin.core.v1/configs/app.ts index 592452d6d53..55254178e91 100644 --- a/features/admin.core.v1/configs/app.ts +++ b/features/admin.core.v1/configs/app.ts @@ -210,7 +210,8 @@ export class Config { I18nConstants.TEMPLATE_CORE_NAMESPACE, I18nConstants.APPLICATION_TEMPLATES_NAMESPACE, I18nConstants.ACTIONS_NAMESPACE, - I18nConstants.TENANTS_NAMESPACE + I18nConstants.TENANTS_NAMESPACE, + I18nConstants.REMOTE_USER_STORES_NAMESPACE ], preload: [] }; diff --git a/features/admin.core.v1/constants/i18n-constants.ts b/features/admin.core.v1/constants/i18n-constants.ts index 7154111eacc..3d9f1c6229d 100644 --- a/features/admin.core.v1/constants/i18n-constants.ts +++ b/features/admin.core.v1/constants/i18n-constants.ts @@ -271,6 +271,11 @@ export class I18nConstants { */ public static readonly TENANTS_NAMESPACE: string = I18nModuleConstants.TENANTS_NAMESPACE; + /** + * Remote User Stores namespace. + */ + public static readonly REMOTE_USER_STORES_NAMESPACE: string = I18nModuleConstants.REMOTE_USER_STORES_NAMESPACE; + /** * Locations of the I18n namespaces. */ @@ -321,7 +326,8 @@ export class I18nConstants { [ I18nConstants.TEMPLATE_CORE_NAMESPACE, "portals" ], [ I18nConstants.IMPERSONATION_CONFIGURATION_NAMESPACE, "portals" ], [ I18nConstants.ACTIONS_NAMESPACE, "portals" ], - [ I18nConstants.TENANTS_NAMESPACE, "portals" ] + [ I18nConstants.TENANTS_NAMESPACE, "portals" ], + [ I18nConstants.REMOTE_USER_STORES_NAMESPACE, "portals" ] ]); /** diff --git a/features/admin.extensions.v1/configs/common.tsx b/features/admin.extensions.v1/configs/common.tsx index a51e1ae9c3b..2def4f286b9 100644 --- a/features/admin.extensions.v1/configs/common.tsx +++ b/features/admin.extensions.v1/configs/common.tsx @@ -66,7 +66,7 @@ export const commonConfig: CommonConfig = { }, { component: lazy(() => - import("@wso2is/admin.remote-userstores.v1/pages/remote-customer-user-store-create-page") + import("@wso2is/admin.remote-userstores.v1/pages/remote-user-store-create-page") ), icon: { icon: getSidePanelIcons().childIcon diff --git a/features/admin.remote-userstores.v1/components/create/attribute-mappings.tsx b/features/admin.remote-userstores.v1/components/create/attribute-mappings.tsx deleted file mode 100644 index 1ace82d4484..00000000000 --- a/features/admin.remote-userstores.v1/components/create/attribute-mappings.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright (c) 2022, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { ClaimManagementConstants } from "@wso2is/admin.claims.v1/constants/claim-management-constants"; -import { getUsernameConfiguration } from "@wso2is/admin.users.v1/utils"; -import { useValidationConfigData } from "@wso2is/admin.validation.v1/api"; -import { Claim, TestableComponentInterface } from "@wso2is/core/models"; -import { Field, FormValue, Forms } from "@wso2is/forms"; -import { ContentLoader, Hint, Text } from "@wso2is/react-components"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; -import { Grid, Header, Segment } from "semantic-ui-react"; - -/** - * Prop types of the attribute mappings component - */ -interface AttributeMappingsComponentPropsInterface extends TestableComponentInterface { - /** - * Flag to hold the submit state. - */ - triggerSubmit: boolean; - /** - * List of mandatory attributes. - */ - mandatoryAttributes?: Claim[]; - /** - * Callback function to handle attribute mapping submit. - */ - handleAttributeMappingsSubmit?: (values: Map) => void; - /** - * Flag to hold the status of the attribute listing request. - */ - isAttributesListRequestLoading: boolean; -} - -/** - * This component renders the attribute mappings component. - * - * @param props - Props injected to the component. - * - * @returns AttributeMappingsComponent. - */ -export const AttributeMappingsComponent: FunctionComponent = ( - props: AttributeMappingsComponentPropsInterface -): ReactElement => { - - const { - triggerSubmit, - handleAttributeMappingsSubmit, - mandatoryAttributes, - isAttributesListRequestLoading, - [ "data-testid" ]: testId - } = props; - - const [ usernameAttr, setUsernameAttr ] = useState([]); - const [ userIDAttr, setUserIDAttr ] = useState([]); - const [ usernameType, setUsernameType ] = useState(""); - const { t } = useTranslation(); - - const { data: validationData } = useValidationConfigData(); - - useEffect(() => { - if (validationData) { - setUsernameType( - getUsernameConfiguration(validationData)?.enableValidator === "false" - ? t("extensions:manage.features.userStores.create.pageLayout." + - "steps.attributeMappings.emailUsername") - : t("extensions:manage.features.userStores.create.pageLayout." + - "steps.attributeMappings.alphanumericUsername") - ); - } - }, [ validationData ]); - - useEffect(() => { - - if (mandatoryAttributes) { - - const username: Claim[] = mandatoryAttributes.filter((attribute: Claim) => - attribute.claimURI === ClaimManagementConstants.USER_NAME_CLAIM_URI); - - const userID: Claim[] = mandatoryAttributes.filter((attribute: Claim) => - attribute.claimURI !== ClaimManagementConstants.USER_NAME_CLAIM_URI); - - setUsernameAttr(username); - setUserIDAttr(userID); - - } - - }, [ mandatoryAttributes ]); - - return ( - ) => { - handleAttributeMappingsSubmit(values); - } } - > - - { - !isAttributesListRequestLoading - ? ( - - { - usernameAttr?.map((attribute: Claim, index: number) => ( - - - - { attribute?.displayName } - * - - - { attribute?.claimURI } - - - - - - - - - The mapped attribute for username should - be unique and be of - type { usernameType }. - This field cannot be empty as - username is the primary identifier of the user. - - - - - - )) - } - { - userIDAttr?.map((attribute: Claim, index: number) => ( - - - - { attribute?.displayName } - * - - - { attribute?.claimURI } - - - - - - - - The mapped attribute for user ID should be unique. - - - - )) - } - - ) - : - } - - - ); -}; diff --git a/features/admin.remote-userstores.v1/components/create/configurations-form.scss b/features/admin.remote-userstores.v1/components/create/configurations-form.scss new file mode 100644 index 00000000000..299aea064fd --- /dev/null +++ b/features/admin.remote-userstores.v1/components/create/configurations-form.scss @@ -0,0 +1,8 @@ +.configurations-form { + padding-top: 16px; + + .form-grid-container { + margin-top: 8px; + margin-bottom: 16px; + } +} diff --git a/features/admin.remote-userstores.v1/components/create/configurations-form.tsx b/features/admin.remote-userstores.v1/components/create/configurations-form.tsx new file mode 100644 index 00000000000..fd44931632d --- /dev/null +++ b/features/admin.remote-userstores.v1/components/create/configurations-form.tsx @@ -0,0 +1,338 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import OxygenCode from "@oxygen-ui/react/Code"; +import Divider from "@oxygen-ui/react/Divider"; +import FormLabel from "@oxygen-ui/react/FormLabel"; +import Grid from "@oxygen-ui/react/Grid"; +import Typography from "@oxygen-ui/react/Typography"; +import { ClaimManagementConstants } from "@wso2is/admin.claims.v1/constants"; +import { RemoteUserStoreManagerType } from "@wso2is/admin.userstores.v1/constants/user-store-constants"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { + CheckboxFieldAdapter, + FinalForm, + FinalFormField, + FormRenderProps, + FormSpy, + TextFieldAdapter +} from "@wso2is/form/src"; +import { Hint } from "@wso2is/react-components"; +import React, { + ForwardRefExoticComponent, + ForwardedRef, + MutableRefObject, + ReactElement, + RefAttributes, + forwardRef, + useImperativeHandle, + useRef +} from "react"; +import { useTranslation } from "react-i18next"; +import { ConfigurationsFormValuesInterface } from "../../models/ui"; + +import "./configurations-form.scss"; + +interface ConfigurationsFormPropsInterface extends IdentifiableComponentInterface { + /** + * User store manager. + */ + userStoreManager: + | RemoteUserStoreManagerType.WSOutboundUserStoreManager + | RemoteUserStoreManagerType.RemoteUserStoreManager; + /** + * Whether the form is read only or not. + */ + isReadOnly: boolean; + /** + * Callback to be called on form submit. + * @param values - Form values. + * @returns void + */ + onSubmit: (values: ConfigurationsFormValuesInterface) => void; + /** + * Initial values for the form. + */ + initialValues?: Partial; +} + +export interface ConfigurationsFormRef { + triggerSubmit: () => void; +} + +const ConfigurationsForm: ForwardRefExoticComponent & + ConfigurationsFormPropsInterface> = forwardRef(( + { + userStoreManager, + isReadOnly, + onSubmit, + initialValues, + ["data-componentid"]: componentId = "user-store-configurations-form" + }: ConfigurationsFormPropsInterface, + ref: ForwardedRef + ): ReactElement => { + const usernameClaimUri: string = ClaimManagementConstants.USER_NAME_CLAIM_URI; + const userIDClaimUri: string = ClaimManagementConstants.USER_ID_CLAIM_URI; + + const triggerFormSubmit: MutableRefObject<() => void> = useRef<(() => void) | null>(null); + + const { t } = useTranslation(); + + // Expose triggerFormSubmit to the parent via the ref. + useImperativeHandle( + ref, + () => ({ + triggerSubmit: () => { + if (triggerFormSubmit.current) { + triggerFormSubmit.current(); + } + } + }), + [] + ); + + /** + * Validates the configuration form values and returns any validation errors. + * + * @param values - The form values to validate. + * @returns An object containing validation errors, if any. + */ + const validateForm = ( + values: ConfigurationsFormValuesInterface + ): Partial => { + const errors: Partial = {}; + + if (!values.usernameMapping) { + errors.usernameMapping = t("remoteUserStores:form.fields.usernameMapping.validation.required"); + } + + if (!values.userIdMapping) { + errors.userIdMapping = t("remoteUserStores:form.fields.userIdMapping.validation.required"); + } + + if (values.readGroups && userStoreManager === RemoteUserStoreManagerType.RemoteUserStoreManager) { + if (!values.groupnameMapping) { + errors.groupnameMapping = t( + "remoteUserStores:form.fields.groupnameMapping.validation.required"); + } + if (!values.groupIdMapping) { + errors.groupIdMapping = t("remoteUserStores:form.fields.groupIdMapping.validation.required");; + } + } + + return errors; + }; + + return ( + { + // Group mappings are only required for remote user stores. + if (userStoreManager !== RemoteUserStoreManagerType.RemoteUserStoreManager) { + delete values.groupnameMapping; + delete values.groupIdMapping; + } + onSubmit(values); + } } + validate={ validateForm } + initialValues={ initialValues } + render={ ({ handleSubmit }: FormRenderProps) => { + triggerFormSubmit.current = handleSubmit; + + return ( +
+ + { t("remoteUserStores:form.sections.userAttributes") } + + + + + + { t("remoteUserStores:form.fields.usernameMapping.label") } + +
+ { usernameClaimUri } +
+ + + { + t("remoteUserStores:form.fields.usernameMapping.helperText") + } + ) + } + /> + + + + + { t("remoteUserStores:form.fields.userIdMapping.label") } + +
+ { userIDClaimUri } +
+ + + { + t("remoteUserStores:form.fields.userIdMapping.helperText") + } + ) + } + /> + +
+ + + { t("remoteUserStores:form.sections.groupAttributes") } + + + + + { ({ values }: { values: ConfigurationsFormValuesInterface }) => { + const isReadGroupsEnabled: boolean = values?.readGroups; + + return ( + + + + { t("remoteUserStores:form.fields.readGroups.helperText") } + ) + } + /> + + + { userStoreManager === RemoteUserStoreManagerType + .RemoteUserStoreManager && ( + <> + + + { t("remoteUserStores:form.fields.groupnameMapping.label") } + + + + + { + t("remoteUserStores:form.fields." + + "groupnameMapping.helperText") + } + ) + } + /> + + + + + { t("remoteUserStores:form.fields.groupIdMapping.label") } + + + + + { + t("remoteUserStores:form.fields." + + "groupIdMapping.helperText") + } + ) + } + /> + + + ) } + + ); + } } + + + ); + } } + /> + ); + }); + +export default ConfigurationsForm; diff --git a/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.scss b/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.scss new file mode 100644 index 00000000000..5f55f460bc9 --- /dev/null +++ b/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.scss @@ -0,0 +1,6 @@ +.general-user-store-details-form { + + .text-field-container { + margin-top: 8px; + } +} diff --git a/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.tsx b/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.tsx new file mode 100644 index 00000000000..07e4b95195e --- /dev/null +++ b/features/admin.remote-userstores.v1/components/create/general-user-store-details-form.tsx @@ -0,0 +1,354 @@ +/** + * Copyright (c) 2022-2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RemoteUserStoreManagerType } from "@wso2is/admin.userstores.v1/constants"; +import { TestableComponentInterface } from "@wso2is/core/models"; +import { FinalForm, FinalFormField, FormRenderProps, RadioGroupFieldAdapter, TextFieldAdapter } from "@wso2is/form"; +import { RadioChild } from "@wso2is/forms"; +import React, { + ForwardRefExoticComponent, + ForwardedRef, + MutableRefObject, + ReactElement, + RefAttributes, + forwardRef, + useImperativeHandle, + useRef +} from "react"; +import { useTranslation } from "react-i18next"; +import { + ConnectedUserStoreTypes, + RemoteUserStoreAccessTypes, + RemoteUserStoreConstants, + USERSTORE_VALIDATION_REGEX_PATTERNS +} from "../../constants/remote-user-stores-constants"; +import { GeneralDetailsFormValuesInterface } from "../../models/ui"; +import { validateInputWithRegex } from "../../utils/userstore-utils"; + +import "./general-user-store-details-form.scss"; + +/** + * Prop types of the general user store details component + */ +interface GeneralUserStoreDetailsPropsInterface extends TestableComponentInterface { + /** + * User store manager. + */ + userStoreManager: + | RemoteUserStoreManagerType.WSOutboundUserStoreManager + | RemoteUserStoreManagerType.RemoteUserStoreManager; + /** + * Whether the form is read only or not. + */ + isReadOnly: boolean; + /** + * Whether the read-write user stores feature is enabled or not. + */ + isReadWriteUserStoresEnabled: boolean; + /** + * Callback to be called on form submit. + * @param values - Form values. + * @returns void + */ + onSubmit: (values: GeneralDetailsFormValuesInterface) => void; + /** + * Initial values for the form. + */ + initialValues?: Partial; +} + +export interface GeneralUserStoreDetailsFormRef { + triggerSubmit: () => void; +} + +/** + * This component renders the general user store details form component. + * + * @param props - Props injected to the component. + * + * @returns The general user store details form component. + */ +const GeneralUserStoreDetailsForm: ForwardRefExoticComponent & + GeneralUserStoreDetailsPropsInterface> = forwardRef( + ( + { + userStoreManager, + isReadOnly, + isReadWriteUserStoresEnabled, + onSubmit, + initialValues, + ["data-testid"]: testId = "asgardeo-customer-userstore-general-details" + }: GeneralUserStoreDetailsPropsInterface, + ref: ForwardedRef + ): ReactElement => { + const { t } = useTranslation(); + + const triggerFormSubmit: MutableRefObject<() => void> = useRef<(() => void) | null>(null); + + // Expose triggerSubmit to the parent via the ref + useImperativeHandle( + ref, + () => ({ + triggerSubmit: () => { + if (triggerFormSubmit.current) { + triggerFormSubmit.current(); + } + } + }), + [] + ); + + const userStoreOptions: RadioChild[] = [ + { + "data-testid": `${testId}-create-user-store-form-user-store-ldap-option-radio-button`, + label: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + + "userStoreType.types.ldap.label" + ), + value: ConnectedUserStoreTypes.LDAP + }, + { + "data-testid": `${testId}-create-user-store-form-user-store-ad-option-radio-button`, + label: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + + "userStoreType.types.ad.label" + ), + value: ConnectedUserStoreTypes.ActiveDirectory + } + ]; + + const accessTypeOptions: RadioChild[] = [ + { + "data-componentid": `${testId}-create-user-store-form-access-type-read-only-option-radio-button`, + hint: { + content: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form." + + "fields.accessType.types.readOnly.hint" + ) + }, + label: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + + "accessType.types.readOnly.label" + ), + value: RemoteUserStoreAccessTypes.ReadOnly + }, + { + "data-componentid": `${testId}-create-user-store-form-access-type-read-write-option-radio-button`, + hint: { + content: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form." + + "fields.accessType.types.readWrite.hint" + ) + }, + label: t( + "extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + + "accessType.types.readWrite.label" + ), + value: RemoteUserStoreAccessTypes.ReadWrite + } + ]; + + const validateForm = ( + values: GeneralDetailsFormValuesInterface + ): Partial => { + const error: Partial = {}; + + if (!values?.name) { + error.name = t("remoteUserStores:form.fields.name.validation.required"); + } else { + const nameValue: string = values?.name.toUpperCase(); + // Regular expression to validate having no symbols in the user store name. + const regExpInvalidSymbols: RegExp = new RegExp("^[^_/]+$"); + // Regular expression to validate having all symbols in the user store name. + const regExpAllSymbols: RegExp = new RegExp("^([^a-zA-Z0-9]+$)"); + + // Already created/restricted user store names. + const restrictedUserStores: string[] = [ + RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME, + RemoteUserStoreConstants.FEDERATION_USER_STORE_NAME, + RemoteUserStoreConstants.DEFAULT_USER_STORE_NAME + ]; + + // Reserved user store names. + const reservedUserStores: string[] = [ + RemoteUserStoreConstants.INTERNAL_USER_STORE_NAME, + RemoteUserStoreConstants.APPLICATION_USER_STORE_NAME + ]; + + const validityResult: Map = validateInputWithRegex( + nameValue, + USERSTORE_VALIDATION_REGEX_PATTERNS.EscapeRegEx + ); + const validationMatch: boolean = validityResult.get("isMatch").toString() === "true"; + + if (!regExpInvalidSymbols.test(nameValue)) { + error.name = t( + "extensions:manage.features.userStores.edit." + + "general.form.validations.invalidSymbolsErrorMessage" + ); + } else if (restrictedUserStores.includes(nameValue.trim().toUpperCase())) { + error.name = t( + "extensions:manage.features.userStores.edit." + + "general.form.validations.restrictedNamesErrorMessage", + { name: nameValue } + ); + } else if (reservedUserStores.includes(nameValue.trim().toUpperCase())) { + error.name = t( + "extensions:manage.features.userStores.edit." + + "general.form.validations.reservedNamesErrorMessage", + { name: nameValue } + ); + } else if (regExpAllSymbols.test(nameValue)) { + error.name = t( + "extensions:manage.features.userStores.edit." + + "general.form.validations.allSymbolsErrorMessage" + ); + } else if (validationMatch) { + const invalidString: string = validityResult.get("invalidStringValue").toString(); + + error.name = t( + "userstores:forms.general.name.validationErrorMessages.invalidInputErrorMessage", + { invalidString } + ); + } + } + + if (values?.description) { + const validityResult: Map = validateInputWithRegex( + values?.description, + USERSTORE_VALIDATION_REGEX_PATTERNS.EscapeRegEx + ); + const validationMatch: boolean = validityResult.get("isMatch").toString() === "true"; + + if (validationMatch) { + const invalidString: string = validityResult.get("invalidStringValue").toString(); + + error.description = t( + "userstores:forms.general.description.validationErrorMessages.invalidInputErrorMessage", + { invalidString } + ); + } + } + + if (!values?.accessType) { + error.accessType = t("remoteUserStores:form.fields.accessType.validation.required"); + } + + if ( + isReadWriteUserStoresEnabled && + userStoreManager === RemoteUserStoreManagerType.WSOutboundUserStoreManager && + !values?.connectedUserStoreType + ) { + error.connectedUserStoreType = t( + "remoteUserStores:form.fields.connectedUserStoreType.validation.required"); + } + + return error; + }; + + return ( + { + onSubmit({ ...values, name: values?.name?.toUpperCase() }); + } } + validate={ validateForm } + initialValues={ initialValues } + render={ ({ handleSubmit }: FormRenderProps) => { + triggerFormSubmit.current = handleSubmit; + + return ( +
+ + + + + ({ + label: option.label, + value: option.value + })) } + required + /> + + { isReadWriteUserStoresEnabled && + userStoreManager === RemoteUserStoreManagerType.WSOutboundUserStoreManager && ( + ({ + label: option.label, + value: option.value + })) } + required + /> + ) } + + ); + } } + /> + ); + } + ); + +export default GeneralUserStoreDetailsForm; diff --git a/features/admin.remote-userstores.v1/components/create/general-user-store-details.tsx b/features/admin.remote-userstores.v1/components/create/general-user-store-details.tsx deleted file mode 100644 index 7ca2f59eb8a..00000000000 --- a/features/admin.remote-userstores.v1/components/create/general-user-store-details.tsx +++ /dev/null @@ -1,343 +0,0 @@ -/** - * Copyright (c) 2022, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FeatureConfigInterface } from "@wso2is/admin.core.v1/models/config"; -import { isFeatureEnabled } from "@wso2is/core/helpers"; -import { SBACInterface, TestableComponentInterface } from "@wso2is/core/models"; -import { Field, FormValue, Forms, RadioChild, Validation } from "@wso2is/forms"; -import isEmpty from "lodash-es/isEmpty"; -import React, { FunctionComponent, ReactElement } from "react"; -import { useTranslation } from "react-i18next"; -import { Grid } from "semantic-ui-react"; -import { - RemoteUserStoreAccessTypes, - RemoteUserStoreConstants, - RemoteUserStoreTypes, - USERSTORE_VALIDATION_REGEX_PATTERNS -} from "../../constants/remote-user-stores"; -import { validateInputWithRegex } from "../../utils/userstore-utils"; - -/** - * Prop types of the general user store details component - */ -interface GeneralUserStoreDetailsPropsInterface extends SBACInterface, - TestableComponentInterface { - /** - * Flag to hold the submit state. - */ - triggerSubmit: boolean; - /** - * Callback function to handle basic details submit. - */ - handleBasicDetailsSubmit: (values: Map) => void; - /** - * Callback to handle user store type change. - */ - handleUserStoreTypeChange: (userStoreAccessType: RemoteUserStoreTypes) => void; - /** - * Callback to handle user store access type change. - */ - handleUserStoreAccessTypeChange: (userStoreType: RemoteUserStoreAccessTypes) => void; - /** - * User store type. - */ - userStoreType?: string; - /** - * Checks whether userstore name entered is valid. - */ - setUserStoreNameValid: (isValid: boolean) => void; - /** - * Checks whether userstore description entered is valid. - */ - setUserStoreDescriptionValid: (isValid: boolean) => void; - /** - * Set the listened description. - */ - setListenedDescription: (description: string) => void; -} - -/** - * This component renders the general user store details component. - * - * @param props - Props injected to the component. - * - * @returns General User Store Details. - */ -export const GeneralUserStoreDetails: FunctionComponent = ( - props: GeneralUserStoreDetailsPropsInterface -): ReactElement => { - - const { - triggerSubmit, - featureConfig, - handleBasicDetailsSubmit, - userStoreType, - handleUserStoreAccessTypeChange, - handleUserStoreTypeChange, - setUserStoreNameValid, - setUserStoreDescriptionValid, - setListenedDescription, - ["data-testid"]: testId - } = props; - - const { t } = useTranslation(); - - const userStoreOptions: RadioChild[] = [ - { - "data-testid": `${testId}-create-user-store-form-user-store-ldap-option-radio-button`, - label: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + - "userStoreType.types.ldap.label"), - value: RemoteUserStoreTypes.LDAP - }, - { - "data-testid": `${testId}-create-user-store-form-user-store-ad-option-radio-button`, - label: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + - "userStoreType.types.ad.label"), - value: RemoteUserStoreTypes.ActiveDirectory - } - ]; - - const accessTypeOptions: RadioChild[] = [ - { - "data-componentid": `${testId}-create-user-store-form-access-type-read-only-option-radio-button`, - hint: { - content: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form." + - "fields.accessType.types.readOnly.hint") - }, - label: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + - "accessType.types.readOnly.label"), - value: RemoteUserStoreAccessTypes.ReadOnly - }, - { - "data-componentid": `${testId}-create-user-store-form-access-type-read-write-option-radio-button`, - hint: { - content: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form." + - "fields.accessType.types.readWrite.hint") - }, - label: t("extensions:manage.features.userStores.create.pageLayout.steps.generalSettings.form.fields." + - "accessType.types.readWrite.label"), - value: RemoteUserStoreAccessTypes.ReadWrite - } - ]; - - return ( - - - - ) => { - handleBasicDetailsSubmit(values); - } } - > - ) => { - setUserStoreNameValid(values.get("name").length > 0); - values.set("name", (values.get("name").toString().toUpperCase())); - } } - validation={ (value: string, validation: Validation) => { - if (!isEmpty(value)) { - // Regular expression to validate having / and _ in the user store name - const regExpInvalidSymbols: RegExp = new RegExp("^[^_/]+$"); - - // Regular expression to validate having all symbols in the user store name - const regExpAllSymbols: RegExp = new RegExp("^([^a-zA-Z0-9]+$)"); - - // Already created/restricted user store names - const restrictedUserstores: string[] = [ - RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME, - RemoteUserStoreConstants.FEDERATION_USER_STORE_NAME, - RemoteUserStoreConstants.DEFAULT_USER_STORE_NAME - ]; - - // Reserved user store names. - const reservedUserstores: string[] = [ - RemoteUserStoreConstants.INTERNAL_USER_STORE_NAME, - RemoteUserStoreConstants.APPLICATION_USER_STORE_NAME - ]; - - let isMatch: boolean = true; - let validationErrorMessage: string; - const validityResult: Map = validateInputWithRegex(value, - USERSTORE_VALIDATION_REGEX_PATTERNS.EscapeRegEx); - const validationMatch: boolean = ( - validityResult.get("isMatch").toString() === "true"); - - if (!regExpInvalidSymbols.test(value)) { - isMatch = false; - validationErrorMessage = t("extensions:manage.features.userStores.edit." - + "general.form.validations.invalidSymbolsErrorMessage"); - } else if ((restrictedUserstores.includes(value.trim().toUpperCase()))) { - isMatch = false; - validationErrorMessage = t("extensions:manage.features.userStores.edit." - + "general.form.validations.restrictedNamesErrorMessage", { name: value }); - } else if ((reservedUserstores.includes(value.trim().toUpperCase()))) { - isMatch = false; - validationErrorMessage = t("extensions:manage.features.userStores.edit." - + "general.form.validations.reservedNamesErrorMessage", { name: value }); - } else if (regExpAllSymbols.test(value)) { - isMatch = false; - validationErrorMessage = t("extensions:manage.features.userStores.edit." - + "general.form.validations.allSymbolsErrorMessage"); - } else if (validationMatch) { - isMatch = false; - const invalidString: string = - validityResult.get("invalidStringValue").toString(); - - validationErrorMessage = t("console:manage.features.userstores." - + "forms.general.name.validationErrorMessages." - + "invalidInputErrorMessage", { invalidString: invalidString }); - } else { - isMatch = true; - } - - if (!isMatch) { - setUserStoreNameValid(false); - validation.isValid = false; - validation.errorMessages.push( - validationErrorMessage - ); - } - } - } } - /> - ) => { - setUserStoreDescriptionValid( - validateInputWithRegex(values.get("description").toString(), - USERSTORE_VALIDATION_REGEX_PATTERNS.EscapeRegEx) - .get("isMatch").toString() === "false"); - setListenedDescription(values.get("description").toString()); - } } - validation={ (value: string, validation: Validation) => { - - let isMatch: boolean = true; - let validationErrorMessage: string; - - const validityResult: Map = validateInputWithRegex(value, - USERSTORE_VALIDATION_REGEX_PATTERNS.EscapeRegEx); - const validationMatch: boolean = (validityResult.get("isMatch").toString() === "true"); - - if (validationMatch) { - isMatch = false; - const invalidString: string = validityResult.get("invalidStringValue").toString(); - - validationErrorMessage = - t("console:manage.features.userstores.forms.general.description" - + ".validationErrorMessages.invalidInputErrorMessage", { - invalidString: invalidString - }); - } else { - isMatch = true; - } - - if (!isMatch) { - setUserStoreDescriptionValid(false); - validation.isValid = false; - validation.errorMessages.push( - validationErrorMessage - ); - } - } } - /> - ) => { - handleUserStoreTypeChange(values.get("userStoreType")); - } } - children={ userStoreOptions } - value={ userStoreType ?? "LDAP" } - /> - ) => { - handleUserStoreAccessTypeChange(values.get("accessType")); - } } - children={ accessTypeOptions } - value={ userStoreType ?? "true" } - tabIndex={ 3 } - hidden={ - !isFeatureEnabled(featureConfig?.userStores, - RemoteUserStoreConstants.FEATURE_DICTIONARY.get("USERSTORES_READ_WRITE_USERSTORES")) - } - /> - - - - - ); -}; - -/** - * Default props for the component. - */ -GeneralUserStoreDetails.defaultProps = { - "data-testid": "asgardeo-customer-userstore-general-details" -}; diff --git a/features/admin.remote-userstores.v1/components/create/index.ts b/features/admin.remote-userstores.v1/components/create/index.ts index 122c2d33d52..e2887336a79 100644 --- a/features/admin.remote-userstores.v1/components/create/index.ts +++ b/features/admin.remote-userstores.v1/components/create/index.ts @@ -16,5 +16,4 @@ * under the License. */ -export * from "./general-user-store-details"; -export * from "./attribute-mappings"; +export * from "./general-user-store-details-form"; diff --git a/features/admin.remote-userstores.v1/components/edit/userstore-general-settings.tsx b/features/admin.remote-userstores.v1/components/edit/userstore-general-settings.tsx index e5554290691..a741c8d26a7 100644 --- a/features/admin.remote-userstores.v1/components/edit/userstore-general-settings.tsx +++ b/features/admin.remote-userstores.v1/components/edit/userstore-general-settings.tsx @@ -53,7 +53,7 @@ import { useDispatch, useSelector } from "react-redux"; import { Dispatch } from "redux"; import { Button, CheckboxProps, Divider, Grid, Icon, List, Segment } from "semantic-ui-react"; import { disconnectAgentConnection, generateToken, getAgentConnections, regenerateToken } from "../../api"; -import { USERSTORE_VALIDATION_REGEX_PATTERNS } from "../../constants/remote-user-stores"; +import { USERSTORE_VALIDATION_REGEX_PATTERNS } from "../../constants/remote-user-stores-constants"; import { AgentConnectionInterface, RegenerateTokenInterface } from "../../models/remote-user-stores"; import { validateInputWithRegex } from "../../utils/userstore-utils"; diff --git a/features/admin.remote-userstores.v1/constants/index.ts b/features/admin.remote-userstores.v1/constants/index.ts index 4e3ae70bf5f..7c623a6b039 100644 --- a/features/admin.remote-userstores.v1/constants/index.ts +++ b/features/admin.remote-userstores.v1/constants/index.ts @@ -16,5 +16,5 @@ * under the License. */ -export * from "./remote-user-stores"; +export * from "./remote-user-stores-constants"; export * from "./remote-user-store-meta"; diff --git a/features/admin.remote-userstores.v1/constants/remote-user-stores.ts b/features/admin.remote-userstores.v1/constants/remote-user-stores-constants.ts similarity index 65% rename from features/admin.remote-userstores.v1/constants/remote-user-stores.ts rename to features/admin.remote-userstores.v1/constants/remote-user-stores-constants.ts index f10eb94abb4..41e99d74eba 100644 --- a/features/admin.remote-userstores.v1/constants/remote-user-stores.ts +++ b/features/admin.remote-userstores.v1/constants/remote-user-stores-constants.ts @@ -16,6 +16,14 @@ * under the License. */ +/** + * Keys used in feature dictionry. + */ +export enum UserStoresFeatureDictionaryKeys { + ReadWriteUserStores = "USERSTORES_READ_WRITE_USERSTORES", + OptimizedUserStore = "OPTIMIZED_USERSTORE" +} + /** * Class containing user store constants. */ @@ -30,11 +38,11 @@ export class RemoteUserStoreConstants { // Type ID of the on-prem customer user store. public static readonly OUTBOUND_USER_STORE_TYPE_ID: string = "V1NPdXRib3VuZFVzZXJTdG9yZU1hbmFnZXI"; - public static readonly READONLY_LDAP_USER_STORE_TYPE_ID: string - = "VW5pcXVlSURSZWFkT25seUxEQVBVc2VyU3RvcmVNYW5hZ2Vy"; + public static readonly READONLY_LDAP_USER_STORE_TYPE_ID: string = + "VW5pcXVlSURSZWFkT25seUxEQVBVc2VyU3RvcmVNYW5hZ2Vy"; - public static readonly READONLY_AD_USER_STORE_TYPE_ID: string - = "VW5pcXVlSURBY3RpdmVEaXJlY3RvcnlVc2VyU3RvcmVNYW5hZ2Vy"; + public static readonly READONLY_AD_USER_STORE_TYPE_ID: string = + "VW5pcXVlSURBY3RpdmVEaXJlY3RvcnlVc2VyU3RvcmVNYW5hZ2Vy"; public static readonly CUSTOMER_USERSTORE_ID: string = "REVGQVVMVA"; @@ -51,21 +59,29 @@ export class RemoteUserStoreConstants { public static readonly DISPLAY_NAME_VALUE: string = "displayName"; // Userstore propery names - public static readonly PROPERTY_NAME_CONNECTED_USERSTORE_TYPE: string = "ConnectedUserstoreType"; + public static readonly PROPERTY_NAME_CONNECTED_USER_STORE_TYPE: string = "ConnectedUserstoreType"; public static readonly PROPERTY_NAME_READ_ONLY: string = "ReadOnly"; + public static readonly PROPERTY_NAME_DISABLED: string = "Disabled"; + public static readonly PROPERTY_NAME_READ_GROUPS: string = "ReadGroups"; + public static readonly PROPERTY_NAME_USERID: string = "UserIDAttribute"; + public static readonly PROPERTY_NAME_USERNAME: string = "UserNameAttribute"; + public static readonly PROPERTY_NAME_GROUPID: string = "GroupIdAttribute"; + public static readonly PROPERTY_NAME_GROUPNAME: string = "GroupNameAttribute"; /** - * Set of keys used to enable/disable features. + * Set of keys used to enable/disable sub-features. */ - public static readonly FEATURE_DICTIONARY: Map = new Map() - .set("USERSTORES_READ_WRITE_USERSTORES", "userStores.readWriteUserstores"); + public static readonly FEATURE_DICTIONARY: Map = new Map([ + [ UserStoresFeatureDictionaryKeys.ReadWriteUserStores, "userStores.readWriteUserstores" ], + [ UserStoresFeatureDictionaryKeys.OptimizedUserStore, "userStores.optimizedUserstore" ] + ]); public static getPaths(): Map { - return new Map() - .set("REMOTE_USER_STORE_CREATE", - `${ window["AppUtils"].getConfig().adminApp.basePath }/remote-user-store-create`); + return new Map().set( + "REMOTE_USER_STORE_CREATE", + `${window["AppUtils"].getConfig().adminApp.basePath}/remote-user-store-create` + ); } - } /** @@ -73,7 +89,7 @@ export class RemoteUserStoreConstants { * * @readonly */ -export enum RemoteUserStoreTypes { +export enum ConnectedUserStoreTypes { ActiveDirectory = "AD", LDAP = "LDAP" } diff --git a/features/admin.remote-userstores.v1/constants/ui-constants.ts b/features/admin.remote-userstores.v1/constants/ui-constants.ts new file mode 100644 index 00000000000..1cbbab718e3 --- /dev/null +++ b/features/admin.remote-userstores.v1/constants/ui-constants.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2022-2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Class containing user store constants. + */ +export class RemoteUserStoreUIConstants { + /** + * Private constructor to avoid object instantiation from outside + * the class. + * + */ + // eslint-disable-next-line @typescript-eslint/no-empty-function + private constructor() {} + + public static readonly REMOTE_USER_STORE_TYPE_QUERY_PARAM: string = "type"; + +} + +/** + * Remote user store implementation types, passed in query param. + */ +export enum RemoteUserStoreImplType { + /** + * Latest implementation. + */ + OPTIMIZED = "optimized", + /** + * Old implementation. + */ + CLASSIC = "classic" +} diff --git a/features/admin.remote-userstores.v1/models/ui.ts b/features/admin.remote-userstores.v1/models/ui.ts new file mode 100644 index 00000000000..90e6d86a5ff --- /dev/null +++ b/features/admin.remote-userstores.v1/models/ui.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface GeneralDetailsFormValuesInterface { + name: string; + description: string; + connectedUserStoreType: string; + accessType?: string; +} + +export interface ConfigurationsFormValuesInterface { + usernameMapping: string; + userIdMapping: string; + readGroups: boolean, + groupnameMapping?: string; + groupIdMapping?: string; +} + +export interface UserStoreFormDataInterface { + generalDetails: Partial; + configurations: Partial; +} diff --git a/features/admin.remote-userstores.v1/pages/remote-customer-user-store-create-page.tsx b/features/admin.remote-userstores.v1/pages/remote-customer-user-store-create-page.tsx deleted file mode 100644 index 75a5bb91288..00000000000 --- a/features/admin.remote-userstores.v1/pages/remote-customer-user-store-create-page.tsx +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Copyright (c) 2022-2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getAllLocalClaims } from "@wso2is/admin.claims.v1/api"; -import { ClaimManagementConstants } from "@wso2is/admin.claims.v1/constants"; -import { AppConstants, AppState, FeatureConfigInterface, history } from "@wso2is/admin.core.v1"; -import { - VerticalStepper, - VerticalStepperStepInterface -} from "@wso2is/admin.core.v1/components/vertical-stepper/vertical-stepper"; -import { userstoresConfig } from "@wso2is/admin.extensions.v1/configs/userstores"; -import { addUserStore, getAType } from "@wso2is/admin.userstores.v1/api/user-stores"; -import { UserStoreManagementConstants } from "@wso2is/admin.userstores.v1/constants/user-store-constants"; -import { - AttributeMapping, - TypeProperty, - UserStore, - UserStorePostData, - UserStoreProperty, - UserstoreType -} from "@wso2is/admin.userstores.v1/models/user-stores"; -import { IdentityAppsApiException } from "@wso2is/core/exceptions"; -import { AlertLevels, Claim, ClaimsGetParams, TestableComponentInterface } from "@wso2is/core/models"; -import { addAlert } from "@wso2is/core/store"; -import { FormValue, useTrigger } from "@wso2is/forms"; -import { EmphasizedSegment, PageLayout, Text } from "@wso2is/react-components"; -import { AxiosError } from "axios"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import { Dispatch } from "redux"; -import { Divider, Grid } from "semantic-ui-react"; -import { AttributeMappingsComponent, GeneralUserStoreDetails } from "../components"; -import { RemoteUserStoreAccessTypes, RemoteUserStoreConstants, RemoteUserStoreTypes } from "../constants"; - -/** - * Props for the remote customer user store page. - */ -type RemoteCustomerUserStoreCreatePageInterface = TestableComponentInterface; - -/** - * Remote customer user store create page. - * - * @param props - Props injected to the component. - * - * @returns ReactElement - */ -const RemoteCustomerUserStoreCreatePage: FunctionComponent = ( - props: RemoteCustomerUserStoreCreatePageInterface -): ReactElement => { - - const { - ["data-testid"]: testId - } = props; - - const { t } = useTranslation(); - - const dispatch: Dispatch = useDispatch(); - - const featureConfig: FeatureConfigInterface = useSelector((state: AppState) => state.config.ui.features); - const enableIdentityClaims: boolean = useSelector( - (state: AppState) => state?.config?.ui?.enableIdentityClaims); - - const [ triggerBasicDetailsSubmit, setTriggerBasicDetailsSubmit ] = useTrigger(); - const [ triggerAttributeMappingsSubmit, setTriggerAttributeMappingsSubmit ] = useTrigger(); - - const [ userStoreDetails, setUserStoreDetails ] = useState(null); - const [ basicDetails, setBasicDetails ] = useState({ description: "", name: "" }); - const [ userStoreType, setUserStoreType ] = useState(RemoteUserStoreTypes.LDAP); - const [ userStoreAccessType, setUserStoreAccessType ] = useState(RemoteUserStoreAccessTypes.ReadOnly); - const [ isUserStoreNameValid, setUserStoreNameValid ] = useState(false); - const [ isUserStoreDescriptionValid, setUserStoreDescriptionValid ] = useState(false); - const [ inputDescription, setInputDescription ] = useState(""); - const [ isAttributesListRequestLoading, setAttributesListRequestLoading ] = useState(false); - const [ mandatoryAttributes, setMandatoryAttributes ] = useState(null); - - useEffect(() => { - getLocalClaims(null, null, null, null, !enableIdentityClaims); - }, []); - - useEffect(() => { - getAType(RemoteUserStoreConstants.OUTBOUND_USER_STORE_TYPE_ID, null) - .then((response: UserstoreType) => { - setUserStoreDetails(response); - }); - }, []); - - const handleUserStoreAccessTypeChange = (userStoreAccessType: RemoteUserStoreAccessTypes) => { - setUserStoreAccessType(userStoreAccessType); - }; - - const handleUserStoreTypeChange = (userStoreType: RemoteUserStoreTypes) => { - setUserStoreType(userStoreType); - }; - - /** - * Fetches all the local claims. - * - * @param limit - number - * @param offset - number - * @param sort - string - * @param filter - string - * @param excludeIdentity - boolean - */ - const getLocalClaims = (limit?: number, sort?: string, offset?: number, filter?: string, - excludeIdentity?: boolean) => { - setAttributesListRequestLoading(true); - const params: ClaimsGetParams = { - "exclude-identity-claims": excludeIdentity, - filter: filter || null, - limit: limit || null, - offset: offset || null, - sort: sort || null - }; - - getAllLocalClaims(params).then((attributes: Claim[]) => { - // Set mandatory attributes that the user needs to update. - setMandatoryAttributes(attributes.filter((attribute: Claim) => - attribute.claimURI === ClaimManagementConstants.USER_NAME_CLAIM_URI || - attribute.claimURI === ClaimManagementConstants.USER_ID_CLAIM_URI - )); - }).catch((error: IdentityAppsApiException) => { - dispatch(addAlert( - { - description: error?.response?.data?.description - || t("claims:local.notifications.getClaims.genericError.description"), - level: AlertLevels.ERROR, - message: error?.response?.data?.message - || t("claims:local.notifications.getClaims.genericError.message") - } - )); - }).finally(() => { - setAttributesListRequestLoading(false); - }); - }; - - const serializeProperties = (): UserStoreProperty[] => { - - const userStoreProperties: UserStoreProperty[] = []; - - userStoreDetails?.properties?.Mandatory.map((property: TypeProperty) => { - - userStoreProperties.push({ - name: property?.name, - value: getValueForUserStoreProperty(property?.name) - }); - - }); - - userStoreDetails?.properties?.Optional.map((property: TypeProperty) => { - - userStoreProperties.push({ - name: property?.name, - value: property?.defaultValue - }); - - }); - // todo : Need to move this property to either `userStoreDetails.properties.Mandatory` or - // `userStoreDetails.properties.Optional`. This can be done after the necessary backend fix is complete - userStoreProperties.push( - { - name: RemoteUserStoreConstants.PROPERTY_NAME_READ_ONLY, - value: getValueForUserStoreProperty(RemoteUserStoreConstants.PROPERTY_NAME_READ_ONLY) - } - ); - - return userStoreProperties; - }; - - const getValueForUserStoreProperty = (propertyName: string): string => { - switch (propertyName) { - case RemoteUserStoreConstants.PROPERTY_NAME_CONNECTED_USERSTORE_TYPE: - return userStoreType.toString(); - - case RemoteUserStoreConstants.PROPERTY_NAME_READ_ONLY: - return userStoreAccessType.toString(); - - default: - break; - } - }; - - const handleUserStoreRegistration = (data: UserStorePostData) => { - - // Create the user store in Asgardeo. - addUserStore(data) - .then((response: UserStore) => { - dispatch(addAlert({ - description: t("userstores:notifications.addUserstore.success.description"), - level: AlertLevels.SUCCESS, - message: t("userstores:notifications.addUserstore.success.message") - })); - dispatch(addAlert({ - description: t("userstores:notifications.delay.description"), - level: AlertLevels.WARNING, - message: t("userstores:notifications.delay.message") - })); - - history.push({ - pathname: AppConstants.getPaths().get("USERSTORES_EDIT").replace(":id", response.id - ).replace("edit-user-store", userstoresConfig.userstoreEdit.remoteUserStoreEditPath) - + "#tab=1", search: "?isNew=true" - }); - - }) - .catch((error: AxiosError) => { - if (error.response?.status === 403 && - error.response.data?.code === UserStoreManagementConstants.ERROR_CREATE_LIMIT_REACHED.getErrorCode() - ) { - - dispatch(addAlert({ - description: t(UserStoreManagementConstants.ERROR_CREATE_LIMIT_REACHED.getErrorDescription()), - level: AlertLevels.ERROR, - message: t(UserStoreManagementConstants.ERROR_CREATE_LIMIT_REACHED.getErrorMessage()) - })); - } - - dispatch(addAlert({ - description: t("userstores:notifications." + - "addUserstore.genericError.description"), - level: AlertLevels.ERROR, - message: error?.message ?? t("userstores:notifications.addUserstore" + - ".genericError.message") - })); - }); - }; - - const handleBasicDetailsSubmit = (values: Map) => { - setBasicDetails({ - description: values.get("description").toString(), - name: values.get("name").toString() - }); - }; - - const preventBasicDetailsNext = (): boolean => { - if (inputDescription) { - return (!isUserStoreNameValid || !isUserStoreDescriptionValid); - } else { - return !isUserStoreNameValid; - } - }; - - const handleAttributeMappingsSubmit = (values: Map) => { - if (preventBasicDetailsNext()) { - return; - } - - const attributeMappings: AttributeMapping[] = []; - const userStoreProperties: UserStoreProperty[] = serializeProperties(); - - values.forEach((value: string, key: string) => { - attributeMappings.push({ - "claimURI": key, - "mappedAttribute": value - }); - }); - - const userStoreData: UserStorePostData = { - claimAttributeMappings: attributeMappings, - description: basicDetails.description, - name: basicDetails.name, - properties: userStoreProperties, - typeId: RemoteUserStoreConstants.OUTBOUND_USER_STORE_TYPE_ID - }; - - handleUserStoreRegistration(userStoreData); - }; - - const resolveAttributeMappingSection = () => ( - <> - - { t("extensions:manage.features.userStores.create.pageLayout.steps.attributeMappings.subTitle") } - -