diff --git a/.changeset/selfish-hairs-mate.md b/.changeset/selfish-hairs-mate.md new file mode 100644 index 00000000000..f45a4004e27 --- /dev/null +++ b/.changeset/selfish-hairs-mate.md @@ -0,0 +1,11 @@ +--- +"@wso2is/admin.extensions.v1": patch +"@wso2is/admin.claims.v1": patch +"@wso2is/admin.users.v1": patch +"@wso2is/admin.core.v1": patch +"@wso2is/myaccount": patch +"@wso2is/console": patch +"@wso2is/core": patch +--- + +Add system schema diff --git a/apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2 b/apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2 index 4dcddaf54b5..b6c40678d6f 100644 --- a/apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2 +++ b/apps/console/java/org.wso2.identity.apps.console.server.feature/resources/deployment.config.json.j2 @@ -1984,6 +1984,9 @@ {% if console.enable_identity_claims is defined %} "enableIdentityClaims": {{ console.enable_identity_claims }}, {% endif %} + {% if scim2.custom_user_schema_uri is defined %} + "customUserSchemaURI": "{{ scim2.custom_user_schema_uri }}", + {% endif %} {% if tenant_mgt.enable_email_domain is defined %} "enableEmailDomain": {{ tenant_mgt.enable_email_domain }}, {% endif %} diff --git a/apps/myaccount/java/org.wso2.identity.apps.myaccount.server.feature/resources/deployment.config.json.j2 b/apps/myaccount/java/org.wso2.identity.apps.myaccount.server.feature/resources/deployment.config.json.j2 index 6fe15fedf3c..ff2f4f69010 100644 --- a/apps/myaccount/java/org.wso2.identity.apps.myaccount.server.feature/resources/deployment.config.json.j2 +++ b/apps/myaccount/java/org.wso2.identity.apps.myaccount.server.feature/resources/deployment.config.json.j2 @@ -377,6 +377,9 @@ {% endfor %} {% endif %} }, + {% if scim2.custom_user_schema_uri is defined %} + "customUserSchemaURI": "{{ scim2.custom_user_schema_uri }}", + {% endif %} "privacyPolicyConfigs": { {% if myaccount.privacy_policy_configs.items() is defined %} {% for key, value in myaccount.privacy_policy_configs.items() %} diff --git a/apps/myaccount/src/api/profile.ts b/apps/myaccount/src/api/profile.ts index af6a4facc5c..e6320aaa32f 100644 --- a/apps/myaccount/src/api/profile.ts +++ b/apps/myaccount/src/api/profile.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2019-2025, 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 @@ -146,8 +146,8 @@ export const getProfileInfo = (): Promise => { const profileResponse: BasicProfileInterface = { emails: response.data.emails || "", name: response.data.name || { familyName: "", givenName: "" }, - pendingEmails: response.data[ProfileConstants.SCIM2_ENT_USER_SCHEMA] - ? response.data[ProfileConstants.SCIM2_ENT_USER_SCHEMA].pendingEmails + pendingEmails: response.data[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA] + ? response.data[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA].pendingEmails : [], phoneNumbers: response.data.phoneNumbers || [], profileUrl: response.data.profileUrl || "", diff --git a/apps/myaccount/src/components/account-recovery/options/email-recovery.tsx b/apps/myaccount/src/components/account-recovery/options/email-recovery.tsx index 3f12bb01f80..a1cc4d9a68e 100644 --- a/apps/myaccount/src/components/account-recovery/options/email-recovery.tsx +++ b/apps/myaccount/src/components/account-recovery/options/email-recovery.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2019-2025, 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 @@ -135,7 +135,7 @@ export const EmailRecovery: React.FunctionComponent = ( } ] : [ emailAddress ], - [ProfileConstants.SCIM2_ENT_USER_SCHEMA]: { + [ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA]: { "verifyEmail": true } }; diff --git a/apps/myaccount/src/components/profile/profile.tsx b/apps/myaccount/src/components/profile/profile.tsx index 73857382d34..cfa918ff4c8 100644 --- a/apps/myaccount/src/components/profile/profile.tsx +++ b/apps/myaccount/src/components/profile/profile.tsx @@ -148,6 +148,7 @@ export const Profile: FunctionComponent = (props: ProfileProps): R state.authenticationInformation.profileInfo.isReadOnly); const hasLocalAccount: boolean = useSelector((state: AppState) => state.authenticationInformation.hasLocalAccount); const config: ConfigReducerStateInterface = useSelector((state: AppState) => state.config); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); const activeForm: string = useSelector((state: AppState) => state.global.activeForm); const supportedI18nLanguages: SupportedLanguagesMeta = useSelector( @@ -431,58 +432,46 @@ export const Profile: FunctionComponent = (props: ProfileProps): R tempProfileInfo.set(schema.name, primaryEmail); } } else { - if (schema.extended - && profileDetails?.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA] - && profileDetails?.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA][schemaNames[0]]) { - tempProfileInfo.set( - schema.name, - profileDetails?.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA] - ? profileDetails?.profileInfo[ - ProfileConstants.SCIM2_ENT_USER_SCHEMA][schemaNames[0] - ] - : "" - ); - - return; - } - - if (schema.extended - && profileDetails?.profileInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA] - && profileDetails?.profileInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA][schemaNames[0]]) { - - const multiValuedAttributes: string[] = [ - EMAIL_ADDRESSES_ATTRIBUTE, - MOBILE_NUMBERS_ATTRIBUTE, - VERIFIED_EMAIL_ADDRESSES_ATTRIBUTE, - VERIFIED_MOBILE_NUMBERS_ATTRIBUTE + if (schema.extended) { + const schemaURIs: string[] = [ + ProfileConstants.SCIM2_ENT_USER_SCHEMA, + ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA, + userSchemaURI ]; - if (multiValuedAttributes.includes(schemaNames[0])) { + for (const schemaURI of schemaURIs) { + if (profileDetails?.profileInfo[schemaURI]?.[schemaNames[0]]) { + + const multiValuedAttributes: string[] = [ + EMAIL_ADDRESSES_ATTRIBUTE, + MOBILE_NUMBERS_ATTRIBUTE, + VERIFIED_EMAIL_ADDRESSES_ATTRIBUTE, + VERIFIED_MOBILE_NUMBERS_ATTRIBUTE + ]; + + if (schemaURI === ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA + && multiValuedAttributes.includes(schemaNames[0])) { + const attributeValue: string | string[] = + profileDetails?.profileInfo[schemaURI]?.[schemaNames[0]]; - const attributeValue: string | string[] = - profileDetails?.profileInfo[ - ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA]?.[schemaNames[0]]; + const formattedValue: string = Array.isArray(attributeValue) + ? attributeValue.join(",") + : ""; - const formattedValue: string = Array.isArray(attributeValue) - ? attributeValue.join(",") - : ""; + tempProfileInfo.set(schema.name, formattedValue); + + return; + } - tempProfileInfo.set(schema.name, formattedValue); + tempProfileInfo.set( + schema.name, + profileDetails?.profileInfo[schemaURI]?.[schemaNames[0]] ?? "" + ); - return; + return; + } } - tempProfileInfo.set( - schema.name, - profileDetails?.profileInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA] - ? profileDetails?.profileInfo[ - ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA - ][schemaNames[0]] - : "" - ); - - return; } - tempProfileInfo.set(schema.name, profileDetails.profileInfo[schemaNames[0]]); } } else { @@ -512,14 +501,20 @@ export const Profile: FunctionComponent = (props: ProfileProps): R } } } else { - if (schema.extended) { - tempProfileInfo.set(schema.name, - profileDetails?.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA]?.[schemaNames[0]] - ? profileDetails - ?.profileInfo[ - ProfileConstants.SCIM2_ENT_USER_SCHEMA - ][schemaNames[0]][schemaNames[1]] - : ""); + if (schema.extended + && profileDetails?.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA]?.[schemaNames[0]]) { + tempProfileInfo.set( + schema.name, + profileDetails.profileInfo[ProfileConstants.SCIM2_ENT_USER_SCHEMA][schemaNames[0]][ + schemaNames[1]] + ?? ""); + } else if (schema.extended && + profileDetails?.profileInfo[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA]?.[schemaNames[0]]) { + tempProfileInfo.set( + schema.name, + profileDetails.profileInfo[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA][schemaNames[0]][ + schemaNames[1]] + ?? ""); } else { const subValue: BasicProfileInterface = profileDetails.profileInfo[schemaNames[0]] && profileDetails.profileInfo[schemaNames[0]].find( @@ -705,7 +700,7 @@ export const Profile: FunctionComponent = (props: ProfileProps): R if (values.get(formName)) { value = { ...value, - [ProfileConstants.SCIM2_ENT_USER_SCHEMA]: { + [ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA]: { "verifyEmail": true } }; @@ -747,7 +742,7 @@ export const Profile: FunctionComponent = (props: ProfileProps): R if (primaryValue) { value = { ...value, - [ProfileConstants.SCIM2_ENT_USER_SCHEMA]: { + [ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA]: { "verifyEmail": true } }; diff --git a/apps/myaccount/src/components/shared/mobile-update-wizard.tsx b/apps/myaccount/src/components/shared/mobile-update-wizard.tsx index 8b04716d7ab..c421f2ac7c9 100644 --- a/apps/myaccount/src/components/shared/mobile-update-wizard.tsx +++ b/apps/myaccount/src/components/shared/mobile-update-wizard.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2020-2025, 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 @@ -93,9 +93,9 @@ export const MobileUpdateWizard: React.FunctionComponent): boolean => { const PENDING_MOBILE_CLAIM: string = "pendingMobileNumber"; - return userData && userData[ProfileConstants.SCIM2_ENT_USER_SCHEMA] && - userData[ProfileConstants.SCIM2_ENT_USER_SCHEMA][PENDING_MOBILE_CLAIM] && - userData[ProfileConstants.SCIM2_ENT_USER_SCHEMA][PENDING_MOBILE_CLAIM] === updatedMobileNumber; + return userData && userData[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA] && + userData[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA][PENDING_MOBILE_CLAIM] && + userData[ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA][PENDING_MOBILE_CLAIM] === updatedMobileNumber; }; @@ -127,7 +127,7 @@ export const MobileUpdateWizard: React.FunctionComponent { if (response.status === 200) { diff --git a/apps/myaccount/src/configs/app.ts b/apps/myaccount/src/configs/app.ts index bc5e7b54367..5afdb4745a8 100644 --- a/apps/myaccount/src/configs/app.ts +++ b/apps/myaccount/src/configs/app.ts @@ -19,6 +19,7 @@ import { I18nModuleInitOptions, I18nModuleOptionsInterface, MetaI18N, generateBackendPaths } from "@wso2is/i18n"; import { I18nConstants } from "../constants"; import { AppConstants } from "../constants/app-constants"; +import { UserManagementConstants } from "../constants/user-management-constants"; // Keep statement as this to avoid cyclic dependency. Do not import from config index. import { SCIMConfigs } from "../extensions/configs/scim"; import { AppUtils } from "../init/app-utils"; @@ -109,8 +110,7 @@ export class Config { this.getDeploymentConfig()?.serverHost }/api/server/v1/configs/home-realm-identifiers`, isReadOnlyUser: `${this.getDeploymentConfig()?.serverHost}/scim2/Me?attributes=${ - SCIMConfigs.scimEnterpriseUserClaimUri.isReadOnlyUser - }`, + SCIMConfigs.scim.systemSchema}:isReadOnlyUser`, issuer: `${this.getDeploymentConfig()?.serverHost}/oauth2/token`, jwks: `${this.getDeploymentConfig()?.serverHost}/oauth2/jwks`, logout: `${this.getDeploymentConfig()?.serverHost}/oidc/logout`, @@ -170,7 +170,9 @@ export class Config { productName: window["AppUtils"]?.getConfig()?.ui?.productName, productVersionConfig: window["AppUtils"]?.getConfig()?.ui?.productVersionConfig, showAppSwitchButton: window["AppUtils"]?.getConfig()?.ui?.showAppSwitchButton, - theme: window["AppUtils"]?.getConfig()?.ui?.theme + theme: window["AppUtils"]?.getConfig()?.ui?.theme, + userSchemaURI: window[ "AppUtils" ]?.getConfig()?.ui?.customUserSchemaURI + ?? UserManagementConstants.DEFAULT_SCIM2_CUSTOM_USER_SCHEMA_URI }; } diff --git a/apps/myaccount/src/constants/user-management-constants.ts b/apps/myaccount/src/constants/user-management-constants.ts index 9cdb408aed3..6ca96bc8393 100644 --- a/apps/myaccount/src/constants/user-management-constants.ts +++ b/apps/myaccount/src/constants/user-management-constants.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2022-2025, 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 @@ -39,4 +39,9 @@ export class UserManagementConstants { .set("USERNAME", "userName") .set("NAME", "name") .set("DISPLAY_NAME", "displayName"); + + /** + * Default scim2 custom user schema URI. + */ + public static readonly DEFAULT_SCIM2_CUSTOM_USER_SCHEMA_URI: string = "urn:scim:schemas:extension:custom:User"; } diff --git a/apps/myaccount/src/extensions/configs/models/scim.ts b/apps/myaccount/src/extensions/configs/models/scim.ts index 4a7d8b4a713..70128d369e8 100644 --- a/apps/myaccount/src/extensions/configs/models/scim.ts +++ b/apps/myaccount/src/extensions/configs/models/scim.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -25,6 +25,7 @@ export interface SCIMConfigInterface { coreSchema: string, enterpriseSchema: string, userSchema: string, + systemSchema: string, customEnterpriseSchema:string }; diff --git a/apps/myaccount/src/extensions/configs/scim.ts b/apps/myaccount/src/extensions/configs/scim.ts index bee3fc8b936..5c7fb18210a 100644 --- a/apps/myaccount/src/extensions/configs/scim.ts +++ b/apps/myaccount/src/extensions/configs/scim.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -31,6 +31,7 @@ export const SCIMConfigs: SCIMConfigInterface = { coreSchema: "urn:ietf:params:scim:schemas:core:2.0", customEnterpriseSchema: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", enterpriseSchema: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + systemSchema: "urn:scim:wso2:schema", userSchema: "urn:ietf:params:scim:schemas:core:2.0:User" }, diff --git a/apps/myaccount/src/models/app-config.ts b/apps/myaccount/src/models/app-config.ts index f18d972b9ec..3e0e4232d72 100644 --- a/apps/myaccount/src/models/app-config.ts +++ b/apps/myaccount/src/models/app-config.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2020-2025, 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 @@ -158,7 +158,7 @@ export interface UIConfigInterface extends CommonUIConfigInterface { * Config for enable MFA user wise. */ enableMFAUserWise?: boolean; - /** + /** * Config for disable MFA for federated users. */ disableMFAForFederatedUsers?: boolean; @@ -174,7 +174,12 @@ export interface UIConfigInterface extends CommonUIConfigInterface { * Config to check whether the multiple emails and mobile numbers per user feature is enabled. */ isMultipleEmailsAndMobileNumbersEnabled?: boolean; - } + /** + * Overridden Scim2 user schema URI. + * If the value is not overridden, the default SCIM2 user schema URI is returned. + */ + userSchemaURI?: string; +} /** * Interface for defining settings and configs of an external app. diff --git a/features/admin.claims.v1/api/use-get-claim-dialects.ts b/features/admin.claims.v1/api/use-get-claim-dialects.ts new file mode 100644 index 00000000000..a92bd55dba0 --- /dev/null +++ b/features/admin.claims.v1/api/use-get-claim-dialects.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2025, 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 useRequest, { + RequestConfigInterface, + RequestErrorInterface, + RequestResultInterface +} from "@wso2is/admin.core.v1/hooks/use-request"; +import { store } from "@wso2is/admin.core.v1/store"; +import { ClaimDialect, ClaimDialectsGetParams, HttpMethods } from "@wso2is/core/models"; + +/** + * Hook to get claim dialects. + * + * @param params - sort, filter, offset, attributes, limit. + * @param shouldFetch - If true, will fetch the data. + * + * @returns claim dialects list. + */ +const useGetClaimDialects = ( + params: ClaimDialectsGetParams, + shouldFetch: boolean = true +): RequestResultInterface => { + + const requestConfig: RequestConfigInterface = { + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + method: HttpMethods.GET, + params, + url: store.getState().config.endpoints.claims + }; + + const { + data, + error, + isLoading, + isValidating, + mutate + } = useRequest(shouldFetch ? requestConfig : null); + + return { + data, + error, + isLoading, + isValidating, + mutate + }; +}; + +export default useGetClaimDialects; diff --git a/features/admin.claims.v1/components/add/add-external-claim.tsx b/features/admin.claims.v1/components/add/add-external-claim.tsx index 777a2e49ccd..9ef1ba8aaec 100644 --- a/features/admin.claims.v1/components/add/add-external-claim.tsx +++ b/features/admin.claims.v1/components/add/add-external-claim.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2020-2025, 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 @@ -18,7 +18,7 @@ import { getAllLocalClaims } from "@wso2is/admin.claims.v1/api"; import { AppConstants, AppState, history } from "@wso2is/admin.core.v1"; -import { SCIMConfigs, attributeConfig } from "@wso2is/admin.extensions.v1"; +import { SCIMConfigs } from "@wso2is/admin.extensions.v1"; import { IdentityAppsApiException } from "@wso2is/core/exceptions"; import { AlertLevels, Claim, ClaimsGetParams, ExternalClaim, TestableComponentInterface } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; @@ -123,6 +123,8 @@ export const AddExternalClaims: FunctionComponent state?.config?.ui?.enableIdentityClaims); + const userSchemaURI: string = useSelector( + (state: AppState) => state?.config?.ui?.userSchemaURI); const { t } = useTranslation(); @@ -131,7 +133,7 @@ export const AddExternalClaims: FunctionComponent { if (attributeType !== "oidc" - && claimDialectUri !== attributeConfig.localAttributes.customDialectURI) { + && claimDialectUri !== userSchemaURI) { if (!serverSupportedClaims || !filteredLocalClaims || serverSupportedClaims.length === 0 || filteredLocalClaims.length === 0) { setEmptyClaims(true); @@ -153,7 +155,7 @@ export const AddExternalClaims: FunctionComponent = ( const hiddenUserStores: string[] = useSelector((state: AppState) => state.config.ui.hiddenUserStores); const primaryUserStoreDomainName: string = useSelector((state: AppState) => state?.config?.ui?.primaryUserStoreDomainName); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); const [ firstStep, setFirstStep ] = useTrigger(); const [ secondStep, setSecondStep ] = useTrigger(); @@ -169,7 +170,7 @@ export const AddLocalClaims: FunctionComponent = ( await attributeConfig.localAttributes.isSCIMCustomDialectAvailable().then((available: string) => { if (available === "") { - addDialect(attributeConfig.localAttributes.customDialectURI); + addDialect(userSchemaURI); } }); @@ -195,8 +196,7 @@ export const AddLocalClaims: FunctionComponent = ( if (!skipSCIM) { attributeConfig.localAttributes.isSCIMCustomDialectAvailable().then((claimId: string) => { addExternalClaim(claimId, { - claimURI: `${ attributeConfig.localAttributes.customDialectURI - }:${ customMappings.get("scim") }`, + claimURI: `${ userSchemaURI }:${ customMappings.get("scim") }`, mappedLocalClaimURI: data.claimURI }).then(() => { fetchUpdatedSchemaList(); diff --git a/features/admin.claims.v1/components/edit/local-claim/edit-basic-details-local-claims.tsx b/features/admin.claims.v1/components/edit/local-claim/edit-basic-details-local-claims.tsx index b90a82d1474..b01bf34dd14 100644 --- a/features/admin.claims.v1/components/edit/local-claim/edit-basic-details-local-claims.tsx +++ b/features/admin.claims.v1/components/edit/local-claim/edit-basic-details-local-claims.tsx @@ -26,7 +26,6 @@ import { Show, useRequiredScopes } from "@wso2is/access-control"; import { AppConstants, AppState, FeatureConfigInterface, history } from "@wso2is/admin.core.v1"; import useUIConfig from "@wso2is/admin.core.v1/hooks/use-ui-configs"; import { attributeConfig } from "@wso2is/admin.extensions.v1"; -import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim"; import { useGetCurrentOrganizationType } from "@wso2is/admin.organizations.v1/hooks/use-get-organization-type"; import { ConnectorPropertyInterface, @@ -43,6 +42,7 @@ import { AlertInterface, AlertLevels, Claim, + ClaimDialect, ExternalClaim, ProfileSchemaInterface, SharedProfileValueResolvingMethod, @@ -81,6 +81,7 @@ import { useDispatch, useSelector } from "react-redux"; import { Dispatch } from "redux"; import { Divider, Grid, Icon, Form as SemanticForm } from "semantic-ui-react"; import { deleteAClaim, getExternalClaims, updateAClaim } from "../../../api"; +import useGetClaimDialects from "../../../api/use-get-claim-dialects"; import { ClaimManagementConstants } from "../../../constants"; /** @@ -138,6 +139,8 @@ export const EditBasicDetailsLocalClaims: FunctionComponent state?.auth?.allowedScopes); const featureConfig: FeatureConfigInterface = useSelector((state: AppState) => state.config.ui.features); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); + const hasAttributeUpdatePermissions: boolean = useRequiredScopes(featureConfig?.attributeDialects?.scopes?.update); const isUpdatingSharedProfilesEnabled: boolean = !featureConfig?.attributeDialects?.disabledFeatures?.includes( "attributeDialects.sharedProfileValueResolvingMethod" @@ -184,6 +187,36 @@ export const EditBasicDetailsLocalClaims: FunctionComponent fetchedDialects?.find( + (dialect: ClaimDialect) => dialect?.dialectURI === userSchemaURI + )?.id || null, [ fetchedDialects ]); + + /** + * Handle the fetch dialects request error. + */ + useEffect(() => { + if (fetchDialectsRequestError) { + dispatch( + addAlert({ + description: t( + "console:manage.features.claims.dialects.notifications.fetchDialects" + + ".genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "console:manage.features.claims.dialects.notifications.fetchDialects" + + ".genericError.message" + ) + }) + ); + } + }, [ fetchDialectsRequestError ]); /** * Get username configuration. @@ -289,7 +322,7 @@ export const EditBasicDetailsLocalClaims: FunctionComponent setMappingChecked(true)); } - }, [ claim ]); + }, [ claim, customUserSchemaID ]); useEffect(() => { getConnectorDetails(ServerConfigurationsConstants.USER_ONBOARDING_CONNECTOR_ID, @@ -363,8 +396,10 @@ export const EditBasicDetailsLocalClaims: FunctionComponent(values?.get("scim")?.toString()); const [ isScimMappingRemoved, setIsScimMappingRemoved ] = useState(false); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); + const nameField: React.MutableRefObject = useRef(null); const claimField: React.MutableRefObject = useRef(null); const regExField: React.MutableRefObject = useRef(null); @@ -397,7 +400,7 @@ export const BasicDetailsLocalClaims = (props: BasicDetailsLocalClaimsPropsInter { setShowScimMappingError(status); diff --git a/features/admin.claims.v1/constants/claim-management-constants.ts b/features/admin.claims.v1/constants/claim-management-constants.ts index 97b2b795a18..52be4c27295 100644 --- a/features/admin.claims.v1/constants/claim-management-constants.ts +++ b/features/admin.claims.v1/constants/claim-management-constants.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2023-2025, 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 @@ -98,7 +98,8 @@ export class ClaimManagementConstants { .set("SCIM2_SCHEMAS_CORE_USER", "dXJuOmlldGY6cGFyYW1zOnNjaW06c2NoZW1hczpjb3JlOjIuMDpVc2Vy") .set("SCIM2_SCHEMAS_EXT_ENT_USER", "dXJuOmlldGY6cGFyYW1zOnNjaW06c2NoZW1hczpleHRlbnNpb246ZW50ZXJwcmlzZToyLjA6VXNlcg") - .set("SCIM_SCHEMAS_CORE", "dXJuOnNjaW06c2NoZW1hczpjb3JlOjEuMA"); + .set("SCIM_SCHEMAS_CORE", "dXJuOnNjaW06c2NoZW1hczpjb3JlOjEuMA") + .set("SCIM2_SCHEMAS_EXT_SYSTEM", "dXJuOnNjaW06d3NvMjpzY2hlbWE"); /** * Set of dialects packed OOTB. @@ -124,7 +125,8 @@ export class ClaimManagementConstants { ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("SCIM2_SCHEMAS_CORE_USER"), ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("SCIM2_SCHEMAS_EXT_ENT_USER"), ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("SCIM_SCHEMAS_CORE"), - ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("XML_SOAP") + ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("XML_SOAP"), + ClaimManagementConstants.ATTRIBUTE_DIALECT_IDS.get("SCIM2_SCHEMAS_EXT_SYSTEM") ]; public static readonly CUSTOM_MAPPING: string = SCIMConfigs.custom; @@ -147,39 +149,45 @@ export class ClaimManagementConstants { isAttributeButtonEnabled: boolean; attributeButtonText: string; }[] = [ - { - attributeButtonText: "", - isAttributeButtonEnabled: false, - name: "Core Schema", - uri: "urn:ietf:params:scim:schemas:core:2.0" - }, - { - attributeButtonText: "claims:external.pageLayout.edit.attributeMappingPrimaryAction", - isAttributeButtonEnabled: true, - name: "User Schema", - uri: "urn:ietf:params:scim:schemas:core:2.0:User" - }, - { - attributeButtonText: "claims:external.pageLayout.edit.attributeMappingPrimaryAction" , - isAttributeButtonEnabled: true, - name: "Enterprise Schema", - uri: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" - }, - { - attributeButtonText: "", - isAttributeButtonEnabled: true, - name: "Core 1.0 Schema", - uri: "urn:scim:schemas:core:1.0" - } - ]; + { + attributeButtonText: "", + isAttributeButtonEnabled: false, + name: "Core Schema", + uri: "urn:ietf:params:scim:schemas:core:2.0" + }, + { + attributeButtonText: "claims:external.pageLayout.edit.attributeMappingPrimaryAction", + isAttributeButtonEnabled: true, + name: "User Schema", + uri: "urn:ietf:params:scim:schemas:core:2.0:User" + }, + { + attributeButtonText: "claims:external.pageLayout.edit.attributeMappingPrimaryAction" , + isAttributeButtonEnabled: true, + name: "Enterprise Schema", + uri: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" + }, + { + attributeButtonText: "claims:external.pageLayout.edit.attributeMappingPrimaryAction" , + isAttributeButtonEnabled: true, + name: "System Schema", + uri: "urn:scim:wso2:schema" + }, + { + attributeButtonText: "", + isAttributeButtonEnabled: true, + name: "Core 1.0 Schema", + uri: "urn:scim:schemas:core:1.0" + } + ]; public static readonly EIDAS_TABS: { name: string; uri: string; }[] = [ - { name: "eIDAS/Legal Person", uri: "http://eidas.europa.eu/attributes/legalperson" }, - { name: "eIDAS/Natural Person", uri: "http://eidas.europa.eu/attributes/naturalperson" } - ]; + { name: "eIDAS/Legal Person", uri: "http://eidas.europa.eu/attributes/legalperson" }, + { name: "eIDAS/Natural Person", uri: "http://eidas.europa.eu/attributes/naturalperson" } + ]; /** * Display names of User Id & Username to @@ -239,6 +247,11 @@ export class ClaimManagementConstants { */ public static readonly REGEX_FIELD_MAX_LENGTH: number = 255; public static readonly REGEX_FIELD_MIN_LENGTH: number = 3; + + /** + * Default scim2 custom user schema URI. + */ + public static readonly DEFAULT_SCIM2_CUSTOM_USER_SCHEMA_URI: string = "urn:scim:schemas:extension:custom:User"; } /** diff --git a/features/admin.claims.v1/pages/attribute-mappings.tsx b/features/admin.claims.v1/pages/attribute-mappings.tsx index 317cbabd801..66beaa02e08 100644 --- a/features/admin.claims.v1/pages/attribute-mappings.tsx +++ b/features/admin.claims.v1/pages/attribute-mappings.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -70,6 +70,8 @@ export const AttributeMappings: FunctionComponent state.config.ui.listAllAttributeDialects ); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); + const { t } = useTranslation(); const { getLink } = useDocumentation(); @@ -356,12 +358,13 @@ export const AttributeMappings: FunctionComponent e.dialectURI - === attributeConfig.localAttributes.customDialectURI).length > 0 ) { - attributeMappings.push(filteredDialect.filter((e: ClaimDialect) => e.dialectURI - === attributeConfig.localAttributes.customDialectURI)[0]); + if (type === ClaimManagementConstants.SCIM && attributeConfig.showCustomDialectInSCIM) { + const customDialect: ClaimDialect = filteredDialect?.find( + (dialect: ClaimDialect) => dialect.dialectURI === userSchemaURI + ); + + if (customDialect) { + attributeMappings.push(customDialect); } } @@ -428,7 +431,7 @@ export const AttributeMappings: FunctionComponent - dialect.dialectURI === attributeConfig.localAttributes.customDialectURI + dialect.dialectURI === userSchemaURI ); if (dialect) { diff --git a/features/admin.claims.v1/pages/claim-dialects.tsx b/features/admin.claims.v1/pages/claim-dialects.tsx index e02bae8ac78..6e3d8fce49f 100644 --- a/features/admin.claims.v1/pages/claim-dialects.tsx +++ b/features/admin.claims.v1/pages/claim-dialects.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2023-2025, 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 @@ -96,7 +96,7 @@ const ClaimDialectsPage: FunctionComponent = ( const listAllAttributeDialects: boolean = useSelector( (state: AppState) => state.config.ui.listAllAttributeDialects ); - + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); const isSAASDeployment: boolean = useSelector((state: AppState) => state?.config?.ui?.isSAASDeployment); /** @@ -153,7 +153,7 @@ const ClaimDialectsPage: FunctionComponent = ( eidas.push(attributeMapping); } else { if (attributeConfig.showCustomDialectInSCIM) { - if (attributeMapping.dialectURI !== attributeConfig.localAttributes.customDialectURI) { + if (attributeMapping.dialectURI !== userSchemaURI) { others.push(attributeMapping); } } else { diff --git a/features/admin.core.v1/configs/app.ts b/features/admin.core.v1/configs/app.ts index 274579b131c..aad1c7aa57e 100644 --- a/features/admin.core.v1/configs/app.ts +++ b/features/admin.core.v1/configs/app.ts @@ -24,6 +24,7 @@ import { getApplicationsResourceEndpoints } from "@wso2is/admin.applications.v1/ import { getBrandingResourceEndpoints } from "@wso2is/admin.branding.v1/configs/endpoints"; import { getCertificatesResourceEndpoints } from "@wso2is/admin.certificates.v1"; import { getClaimResourceEndpoints } from "@wso2is/admin.claims.v1/configs/endpoints"; +import { ClaimManagementConstants } from "@wso2is/admin.claims.v1/constants/claim-management-constants"; import { getConnectionResourceEndpoints } from "@wso2is/admin.connections.v1"; import { getEmailTemplatesResourceEndpoints } from "@wso2is/admin.email-templates.v1"; import { getExtendedFeatureResourceEndpoints } from "@wso2is/admin.extensions.v1/configs/endpoints"; @@ -371,7 +372,9 @@ export class Config { window[ "AppUtils" ]?.getConfig()?.ui?.showStatusLabelForNewAuthzRuntimeFeatures, systemAppsIdentifiers: window[ "AppUtils" ]?.getConfig()?.ui?.systemAppsIdentifiers, theme: window[ "AppUtils" ]?.getConfig()?.ui?.theme, - useRoleClaimAsGroupClaim: window[ "AppUtils" ]?.getConfig()?.ui?.useRoleClaimAsGroupClaim + useRoleClaimAsGroupClaim: window[ "AppUtils" ]?.getConfig()?.ui?.useRoleClaimAsGroupClaim, + userSchemaURI: window[ "AppUtils" ]?.getConfig()?.ui?.customUserSchemaURI + ?? ClaimManagementConstants.DEFAULT_SCIM2_CUSTOM_USER_SCHEMA_URI }; } } diff --git a/features/admin.core.v1/models/config.ts b/features/admin.core.v1/models/config.ts index e491103b082..0bf371bc157 100644 --- a/features/admin.core.v1/models/config.ts +++ b/features/admin.core.v1/models/config.ts @@ -518,6 +518,11 @@ export interface UIConfigInterface extends CommonUIConfigInterface { return identityRegex.test(claim.mappedLocalClaimURI); }; +/** + * SCIM2 custom user schema URI. + */ +const userSchemaURI: string = Config?.getUIConfig()?.userSchemaURI; + export const attributeConfig: AttributeConfig = { addAttributeMapping: true, attributeMappings: { @@ -151,7 +157,7 @@ export const attributeConfig: AttributeConfig = { await getDialects() .then((response: Claim[] | ClaimDialect[]) => { response.map((dialect: Claim | ClaimDialect) => { - if (dialect.dialectURI === "urn:scim:wso2:schema") { + if (dialect.dialectURI === userSchemaURI) { dialectID = dialect.id; } }); @@ -252,7 +258,7 @@ export const attributeConfig: AttributeConfig = { await getDialects() .then((response: Claim[] | ClaimDialect[]) => { response.map((dialect: Claim | ClaimDialect) => { - if (dialect.dialectURI === "urn:scim:wso2:schema") { + if (dialect.dialectURI === userSchemaURI) { dialectID = dialect.id; } }); @@ -262,7 +268,7 @@ export const attributeConfig: AttributeConfig = { await getClaimsForDialect(dialectID) .then((response: Claim[] | ExternalClaim[]) => { response.map((attrib: Claim | ExternalClaim) => { - if (attrib.claimURI === "urn:scim:wso2:schema:" + attributeName) { + if (attrib.claimURI === `${userSchemaURI}:${attributeName}`) { availability.set("SCIM", false); } }); @@ -285,7 +291,6 @@ export const attributeConfig: AttributeConfig = { showRegularExpression: false, showSummary: false }, - customDialectURI: "urn:scim:wso2:schema", getDialect: async (dialectURI: string): Promise => { let dialectObject: Claim | ClaimDialect; @@ -306,7 +311,7 @@ export const attributeConfig: AttributeConfig = { await getDialects() .then((response: Claim[] | ClaimDialect[]) => { response.map((dialect: Claim | ClaimDialect) => { - if (dialect.dialectURI === "urn:scim:wso2:schema") { + if (dialect.dialectURI === userSchemaURI) { dialectID = dialect.id; } }); diff --git a/features/admin.extensions.v1/configs/models/attribute.ts b/features/admin.extensions.v1/configs/models/attribute.ts index 299590aa648..baca31bc439 100644 --- a/features/admin.extensions.v1/configs/models/attribute.ts +++ b/features/admin.extensions.v1/configs/models/attribute.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -72,7 +72,6 @@ export interface AttributeConfig { showSummary: boolean; identifyAsCustomAttrib: boolean; } - customDialectURI: string; oidcDialectURI: string; createCustomDialect: boolean; mapClaimToCustomDialect: boolean; diff --git a/features/admin.extensions.v1/configs/models/scim.ts b/features/admin.extensions.v1/configs/models/scim.ts index 3b36f3efcfb..21542fc2e09 100644 --- a/features/admin.extensions.v1/configs/models/scim.ts +++ b/features/admin.extensions.v1/configs/models/scim.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -24,6 +24,7 @@ export interface SCIMConfigInterface { core1Schema: string, coreSchema: string, enterpriseSchema: string, + systemSchema: string, userSchema: string }; scimEnterpriseUserClaimUri: { @@ -35,8 +36,5 @@ export interface SCIMConfigInterface { oneTimePassword: string, profileUrl: string }; - scimDialectID: { - customEnterpriseSchema: string, - }; serverSupportedClaimsAvailable: string[]; } diff --git a/features/admin.extensions.v1/configs/scim.ts b/features/admin.extensions.v1/configs/scim.ts index 2aad542cfc9..f6aa7e7a9b9 100644 --- a/features/admin.extensions.v1/configs/scim.ts +++ b/features/admin.extensions.v1/configs/scim.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2025, 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 @@ -29,11 +29,9 @@ export const SCIMConfigs: SCIMConfigInterface = { core1Schema: "urn:scim:schemas:core:1.0", coreSchema: "urn:ietf:params:scim:schemas:core:2.0", enterpriseSchema: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + systemSchema: "urn:scim:wso2:schema", userSchema: "urn:ietf:params:scim:schemas:core:2.0:User" }, - scimDialectID: { - customEnterpriseSchema: "dXJuOnNjaW06d3NvMjpzY2hlbWE" - }, scimEnterpriseUserClaimUri: { accountDisabled: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User.accountDisabled", accountLocked: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User.accountLocked", diff --git a/features/admin.extensions.v1/configs/user.ts b/features/admin.extensions.v1/configs/user.ts index 969519f7507..0b8446a2bda 100644 --- a/features/admin.extensions.v1/configs/user.ts +++ b/features/admin.extensions.v1/configs/user.ts @@ -38,5 +38,5 @@ export const userConfig: User = { enableBulkImportSecondaryUserStore: true, enableUsernameValidation: false, hiddenItemsPerPageRemoteUserStoreDropdown: false, - userProfileSchema: ProfileConstants.SCIM2_ENT_USER_SCHEMA + userProfileSchema: ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA }; diff --git a/features/admin.users.v1/components/edit-user.tsx b/features/admin.users.v1/components/edit-user.tsx index e072a7231cd..1bf05312315 100644 --- a/features/admin.users.v1/components/edit-user.tsx +++ b/features/admin.users.v1/components/edit-user.tsx @@ -111,6 +111,7 @@ export const EditUser: FunctionComponent = ( const userRolesDisabledFeatures: string[] = useSelector((state: AppState) => { return state.config.ui.features?.users?.disabledFeatures; }); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); const isUpdatingSharedProfilesEnabled: boolean = !userRolesDisabledFeatures?.includes( UserManagementConstants.FEATURE_DICTIONARY.get("USER_SHARED_PROFILES") @@ -125,7 +126,7 @@ export const EditUser: FunctionComponent = ( || readOnlyUserStores?.includes(userStore?.toString()) || !hasUsersUpdatePermissions || user[ SCIMConfigs.scim.enterpriseSchema ]?.userSourceId - || user[ UserManagementConstants.CUSTOMSCHEMA ]?.isReadOnlyUser === "true" + || user[ userSchemaURI ]?.isReadOnlyUser === "true" ) { setReadOnly(true); } @@ -225,7 +226,7 @@ export const EditUser: FunctionComponent = ( isUserManagedByParentOrg={ isUserManagedByParentOrg } adminUserType={ AdminAccountTypes.INTERNAL } allowDeleteOnly={ - user[ UserManagementConstants.CUSTOMSCHEMA ]?.isReadOnlyUser === "true" + user[ userSchemaURI ]?.isReadOnlyUser === "true" } editUserDisclaimerMessage={ ( diff --git a/features/admin.users.v1/components/user-change-password.tsx b/features/admin.users.v1/components/user-change-password.tsx index fc05a1446e9..f7eafb7d325 100644 --- a/features/admin.users.v1/components/user-change-password.tsx +++ b/features/admin.users.v1/components/user-change-password.tsx @@ -27,7 +27,6 @@ import { import { USERSTORE_REGEX_PROPERTIES } from "@wso2is/admin.userstores.v1/constants/user-store-constants"; import { useValidationConfigData } from "@wso2is/admin.validation.v1/api"; import { ValidationFormInterface } from "@wso2is/admin.validation.v1/models"; -import { ProfileConstants } from "@wso2is/core/constants"; import { IdentityAppsApiException } from "@wso2is/core/exceptions"; import { AlertInterface, AlertLevels, ProfileInfoInterface, TestableComponentInterface } from "@wso2is/core/models"; import { Field, FormValue, Forms, RadioChild, Validation, useTrigger } from "@wso2is/forms"; @@ -218,15 +217,14 @@ export const ChangePasswordComponent: FunctionComponent = ( const supportedI18nLanguages: SupportedLanguagesMeta = useSelector( (state: AppState) => state.global.supportedI18nLanguages ); + const userSchemaURI: string = useSelector((state: AppState) => state?.config?.ui?.userSchemaURI); const featureConfig: FeatureConfigInterface = useSelector((state: AppState) => state.config.ui.features); const { UIConfig } = useUIConfig(); @@ -456,12 +457,12 @@ export const UserProfile: FunctionComponent = ( } if ( - schema.extended && userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA] - && userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA][schemaNames[0]] + schema.extended + && userInfo?.[userSchemaURI]?.[schemaNames[0]] ) { if (UserManagementConstants.MULTI_VALUED_ATTRIBUTES.includes(schemaNames[0])) { const attributeValue: string | string[] = - userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA]?.[schemaNames[0]]; + userInfo[userSchemaURI]?.[schemaNames[0]]; const formattedValue: string = Array.isArray(attributeValue) ? attributeValue.join(",") : ""; @@ -471,7 +472,7 @@ export const UserProfile: FunctionComponent = ( return; } tempProfileInfo.set( - schema.name, userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA][schemaNames[0]] + schema.name, userInfo[userSchemaURI][schemaNames[0]] ); return; @@ -591,12 +592,12 @@ export const UserProfile: FunctionComponent = ( } if ( - schema.extended && userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA] - && userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA][schemaNames[0]] + schema.extended + && userInfo?.[userSchemaURI]?.[schemaNames[0]] ) { if (UserManagementConstants.MULTI_VALUED_ATTRIBUTES.includes(schemaNames[0])) { const attributeValue: string | string[] = - userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA]?.[schemaNames[0]]; + userInfo[userSchemaURI]?.[schemaNames[0]]; const formattedValue: string = Array.isArray(attributeValue) ? attributeValue.join(",") : ""; @@ -606,7 +607,7 @@ export const UserProfile: FunctionComponent = ( return; } tempProfileInfo.set( - schema.name, userInfo[ProfileConstants.SCIM2_WSO2_CUSTOM_SCHEMA][schemaNames[0]] + schema.name, userInfo[userSchemaURI][schemaNames[0]] ); return; @@ -1233,7 +1234,7 @@ export const UserProfile: FunctionComponent = ( /** * The method handles the locking and disabling of user account. */ - const handleDangerActions = (attributeName: string, attributeValue: boolean): Promise => { + const handleDangerActions = (attributeName: string, attributeValue: boolean): void => { let data: PatchRoleDataInterface = { "Operations": [ { @@ -1249,17 +1250,22 @@ export const UserProfile: FunctionComponent = ( }; if (adminUserType === "internal") { + const accountDisabledURI: string = UserManagementConstants.SCIM2_ATTRIBUTES_DICTIONARY + .get("ACCOUNT_LOCKED"); + const accountLockedURI: string = UserManagementConstants.SCIM2_ATTRIBUTES_DICTIONARY + .get("ACCOUNT_DISABLED"); + + const schemaURI: string = accountDisabledURI?.startsWith(ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA) + && accountLockedURI?.startsWith(ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA) + ? ProfileConstants.SCIM2_SYSTEM_USER_SCHEMA + : userSchemaURI; + data = { "Operations": [ { "op": "replace", - "value": { - [ SCIMConfigs?.scimEnterpriseUserClaimUri?.accountDisabled?. - startsWith(ProfileConstants.SCIM2_WSO2_USER_SCHEMA) && - SCIMConfigs?.scimEnterpriseUserClaimUri?.accountLocked?. - startsWith(ProfileConstants.SCIM2_WSO2_USER_SCHEMA) - ? ProfileConstants.SCIM2_WSO2_USER_SCHEMA - : ProfileConstants.SCIM2_ENT_USER_SCHEMA ]: { + value: { + [schemaURI]: { [attributeName]: attributeValue } } @@ -1269,7 +1275,7 @@ export const UserProfile: FunctionComponent = ( }; } - return updateUserInfo(user.id, data) + updateUserInfo(user.id, data) .then(() => { onAlertFired({ description: diff --git a/features/admin.users.v1/components/wizard/bulk-import-user-wizard.tsx b/features/admin.users.v1/components/wizard/bulk-import-user-wizard.tsx index 0a703d334cc..c5f608b5e48 100644 --- a/features/admin.users.v1/components/wizard/bulk-import-user-wizard.tsx +++ b/features/admin.users.v1/components/wizard/bulk-import-user-wizard.tsx @@ -61,7 +61,6 @@ import { SCIMSchemaExtension } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; -import { StringUtils } from "@wso2is/core/utils"; import { CSVFileStrategy, CSVResult, @@ -180,9 +179,6 @@ export const BulkImportUserWizard: FunctionComponent = const dispatch: Dispatch = useDispatch(); - const primaryUserStoreDomainName: string = useSelector((state: AppState) => - state?.config?.ui?.primaryUserStoreDomainName); - const [ selectedCSVFile, setSelectedCSVFile ] = useState(null); const [ userData, setUserData ] = useState(); const [ hasError, setHasError ] = useState(false); @@ -1087,10 +1083,7 @@ export const BulkImportUserWizard: FunctionComponent = selectedUserStore.toLowerCase() !== PRIMARY_USERSTORE.toLowerCase() ? `${selectedUserStore}/${email}` : email, - [ !StringUtils.isEqualCaseInsensitive(userstore, primaryUserStoreDomainName) - ? UserManagementConstants.CUSTOMSCHEMA - : UserManagementConstants.ENTERPRISESCHEMA - ]: { + [ UserManagementConstants.SYSTEMSCHEMA ]: { askPassword: "true" } }; diff --git a/features/admin.users.v1/constants/user-management-constants.ts b/features/admin.users.v1/constants/user-management-constants.ts index d6c804761c3..614f7f363ef 100644 --- a/features/admin.users.v1/constants/user-management-constants.ts +++ b/features/admin.users.v1/constants/user-management-constants.ts @@ -17,7 +17,6 @@ */ // Keep statement as this to avoid cyclic dependency. Do not import from config index. -import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim"; import { ServerConfigurationsConstants } from "@wso2is/admin.server-configurations.v1/constants/server-configurations-constants"; import { ProfileConstants } from "@wso2is/core/constants"; @@ -97,7 +96,12 @@ export class UserManagementConstants { // Schema related constants. public static readonly ENTERPRISESCHEMA: string = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"; + public static readonly SYSTEMSCHEMA: string = "urn:scim:wso2:schema"; + /** + * @deprecated This variable is deprecated. Use `SCIM2_CUSTOM_SCHEMA` instead. + */ public static readonly CUSTOMSCHEMA: string = "urn:scim:wso2:schema"; + public static readonly SCIM2_CUSTOM_SCHEMA: string = "urn:scim:schemas:extension:custom:User"; /** * Set of SCIM2 schema names.apps/myaccount/src/store/actions/authenticate.ts @@ -118,9 +122,9 @@ export class UserManagementConstants { * @defaultValue */ public static readonly SCIM2_ATTRIBUTES_DICTIONARY: Map = new Map() - .set("ACCOUNT_LOCKED", SCIMConfigs.scimEnterpriseUserClaimUri.accountLocked) - .set("ACCOUNT_DISABLED", SCIMConfigs.scimEnterpriseUserClaimUri.accountDisabled) - .set("ONETIME_PASSWORD", SCIMConfigs.scimEnterpriseUserClaimUri.oneTimePassword); + .set("ACCOUNT_LOCKED", "urn:scim:wso2:schema:accountLocked") + .set("ACCOUNT_DISABLED", "urn:scim:wso2:schema:accountDisabled") + .set("ONETIME_PASSWORD", "urn:scim:wso2:schema:oneTimePassword"); public static readonly ROLES: string = "roles"; public static readonly GROUPS: string = "groups"; diff --git a/modules/core/src/constants/profile-constants.ts b/modules/core/src/constants/profile-constants.ts index e839cda2025..b7b3fa13354 100644 --- a/modules/core/src/constants/profile-constants.ts +++ b/modules/core/src/constants/profile-constants.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2023, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2020-2025, 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 @@ -29,10 +29,19 @@ export class ProfileConstants { private constructor() { } // SCIM2 schema IDs + public static readonly SCIM2_CORE_SCHEMA: string = "urn:ietf:params:scim:schemas:core:2.0"; public static readonly SCIM2_CORE_USER_SCHEMA: string = "urn:ietf:params:scim:schemas:core:2.0:User"; public static readonly SCIM2_ENT_USER_SCHEMA: string = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"; + /** + * @deprecated This variable is deprecated. Use `SCIM2_SYSTEM_USER_SCHEMA` instead. + */ public static readonly SCIM2_WSO2_USER_SCHEMA: string = "urn:scim:wso2:schema"; + public static readonly SCIM2_SYSTEM_USER_SCHEMA: string = "urn:scim:wso2:schema"; + /** + * @deprecated This variable is deprecated. Use `SCIM2_CUSTOM_USER_SCHEMA` instead. + */ public static readonly SCIM2_WSO2_CUSTOM_SCHEMA: string = "urn:scim:wso2:schema"; + public static readonly SCIM2_CUSTOM_USER_SCHEMA: string = "urn:scim:schemas:extension:custom:User"; // API errors public static readonly SCHEMA_FETCH_REQUEST_INVALID_RESPONSE_CODE_ERROR: string = "Received an invalid status " +