From 957e474263b1b999b44857c2d4d79ca91db43c0b Mon Sep 17 00:00:00 2001 From: Amanda Ariyaratne Date: Tue, 7 Jan 2025 12:30:52 +0530 Subject: [PATCH] add SCIM system schema --- .../IdentityResourceTypeResourceManager.java | 34 +- .../scim2/common/impl/SCIMUserManager.java | 117 ++- .../common/internal/SCIMCommonComponent.java | 8 +- .../common/utils/SCIMCommonConstants.java | 4 + .../scim2/common/utils/SCIMCommonUtils.java | 40 +- .../common/impl/SCIMUserManagerTest.java | 18 +- .../resources/charon-config.xml | 3 +- .../resources/charon-config.xml.j2 | 1 + ...identity.scim2.common.feature.default.json | 4 +- .../resources/scim2-schema-extension.config | 690 ++++++++++++++++++ 10 files changed, 867 insertions(+), 52 deletions(-) diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceTypeResourceManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceTypeResourceManager.java index 0381d29a1..32fbf0413 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceTypeResourceManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceTypeResourceManager.java @@ -20,9 +20,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.wso2.carbon.identity.scim2.common.handlers.SCIMClaimOperationEventHandler; +import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; import org.wso2.charon3.core.attributes.MultiValuedAttribute; import org.wso2.charon3.core.attributes.SimpleAttribute; import org.wso2.charon3.core.encoder.JSONDecoder; @@ -176,6 +177,8 @@ private AbstractSCIMObject buildCombinedResourceType(AbstractSCIMObject userObje private String buildUserResourceTypeJsonBody() throws JSONException { JSONObject userResourceTypeObject = new JSONObject(); + SCIMResourceSchemaManager schemaManager = SCIMResourceSchemaManager.getInstance(); + userResourceTypeObject.put(SCIMConstants.CommonSchemaConstants.SCHEMAS, SCIMConstants.RESOURCE_TYPE_SCHEMA_URI); userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.ID, SCIMConstants.USER); userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.NAME, SCIMConstants.USER); @@ -185,16 +188,27 @@ private String buildUserResourceTypeJsonBody() throws JSONException { userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA, SCIMConstants.USER_CORE_SCHEMA_URI); - if (SCIMResourceSchemaManager.getInstance().isExtensionSet()) { - JSONObject extensionSchemaObject = new JSONObject(); - extensionSchemaObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_SCHEMA, - SCIMResourceSchemaManager.getInstance().getExtensionURI()); - extensionSchemaObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_REQUIRED, - SCIMResourceSchemaManager.getInstance().getExtensionRequired()); - - userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS, - extensionSchemaObject); + if (Boolean.TRUE.equals(schemaManager.isExtensionSet())) { + JSONObject extensionSchemaObject = createSchemaExtensionObject( + schemaManager.getExtensionURI(), schemaManager.getExtensionRequired()); + if (SCIMCommonUtils.isExtensionSchemasListingEnabledInResourceTypesAPI()) { + JSONArray schemaExtensions = new JSONArray(); + schemaExtensions.put(extensionSchemaObject); + schemaExtensions.put(createSchemaExtensionObject( + schemaManager.getSystemSchemaExtensionURI(), schemaManager.getSystemSchemaExtensionRequired())); + userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS, schemaExtensions); + } else { + userResourceTypeObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS, extensionSchemaObject); + } } return userResourceTypeObject.toString(); } + + private JSONObject createSchemaExtensionObject(String schemaURI, boolean isRequired) throws JSONException { + + JSONObject extensionSchemaObject = new JSONObject(); + extensionSchemaObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_SCHEMA, schemaURI); + extensionSchemaObject.put(SCIMConstants.ResourceTypeSchemaConstants.SCHEMA_EXTENSIONS_REQUIRED, isRequired); + return extensionSchemaObject; + } } diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java index 0f686bb72..4508a768b 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManager.java @@ -86,6 +86,7 @@ import org.wso2.charon3.core.attributes.MultiValuedAttribute; import org.wso2.charon3.core.attributes.SimpleAttribute; import org.wso2.charon3.core.config.SCIMConfigConstants; +import org.wso2.charon3.core.config.SCIMSystemSchemaExtensionBuilder; import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder; import org.wso2.charon3.core.exceptions.BadRequestException; import org.wso2.charon3.core.exceptions.CharonException; @@ -368,7 +369,7 @@ public User createUser(User user, Map requiredAttributes) throw new BadRequestException(errorMessage, ResponseCodeConstants.INVALID_VALUE); } catch (UserStoreException e) { // Sometimes client exceptions are wrapped in the super class. - // Therefore checking for possible client exception. + // Therefore, checking for possible client exception. Throwable ex = ExceptionUtils.getRootCause(e); if (ex instanceof UserStoreClientException) { String errorMessage = String.format("Error in adding the user: " + maskIfRequired(user.getUserName()) @@ -520,7 +521,7 @@ public void deleteUser(String userId) throws NotFoundException, CharonException, if (log.isDebugEnabled()) { log.debug("Deleting user: " + userId); } - //get the user name of the user with this id + // Get the username of the user with this id. org.wso2.carbon.user.core.common.User coreUser = null; String userName = null; try { @@ -2365,6 +2366,10 @@ private Map getAllAttributes(String domainName) throws CharonExc String extensionURI = SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI(); attributes.putAll(getMappedAttributes(extensionURI, domainName)); } + if (SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema() != null) { + String extensionURI = SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI(); + attributes.putAll(getMappedAttributes(extensionURI, domainName)); + } attributes.putAll(getMappedAttributes(getCustomSchemaURI(), domainName)); } else { @@ -2372,6 +2377,7 @@ private Map getAllAttributes(String domainName) throws CharonExc ClaimMapping[] userClaims; ClaimMapping[] coreClaims; ClaimMapping[] extensionClaims = null; + ClaimMapping[] systemClaims = null; ClaimMapping[] customClaims = null; coreClaims = carbonClaimManager.getAllClaimMappings(SCIMCommonConstants.SCIM_CORE_CLAIM_DIALECT); @@ -2380,6 +2386,10 @@ private Map getAllAttributes(String domainName) throws CharonExc extensionClaims = carbonClaimManager.getAllClaimMappings( SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI()); } + if (SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema() != null) { + systemClaims = carbonClaimManager.getAllClaimMappings( + SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI()); + } customClaims = carbonClaimManager.getAllClaimMappings(getCustomSchemaURI()); for (ClaimMapping claim : coreClaims) { @@ -2393,6 +2403,11 @@ private Map getAllAttributes(String domainName) throws CharonExc attributes.put(claim.getClaim().getClaimUri(), claim.getMappedAttribute(domainName)); } } + if (systemClaims != null) { + for (ClaimMapping claim : systemClaims) { + attributes.put(claim.getClaim().getClaimUri(), claim.getMappedAttribute(domainName)); + } + } if (ArrayUtils.isNotEmpty(customClaims)) { for (ClaimMapping claim : customClaims) { attributes.put(claim.getClaim().getClaimUri(), claim.getMappedAttribute(domainName)); @@ -5718,7 +5733,7 @@ public List getEnterpriseUserSchema() throws CharonException { List enterpriseUserSchemaAttributesList = null; - if (SCIMCommonUtils.isEnterpriseUserExtensionEnabled()) { + if (SCIMCommonUtils.isUserExtensionEnabled()) { Map scimClaimToLocalClaimMap = getMappedLocalClaimsForDialect(SCIMCommonConstants.SCIM_ENTERPRISE_USER_CLAIM_DIALECT, tenantDomain); @@ -5726,7 +5741,7 @@ public List getEnterpriseUserSchema() throws CharonException { Map filteredAttributeMap = getFilteredSchemaAttributes(scimClaimToLocalClaimMap); Map hierarchicalAttributeMap = - buildHierarchicalAttributeMapForEnterpriseSchema(filteredAttributeMap); + buildHierarchicalAttributeMapForEnterpriseSchema(filteredAttributeMap, true, false); enterpriseUserSchemaAttributesList = new ArrayList(hierarchicalAttributeMap.values()); @@ -5741,6 +5756,37 @@ public List getEnterpriseUserSchema() throws CharonException { return enterpriseUserSchemaAttributesList; } + /** + * Returns the schema of the system user extension in SCIM 2.0. + * + * @return List of attributes of system user extension + * @throws CharonException Error while retrieving schema attribute details. + */ + @Override + public List getSystemUserSchema() throws CharonException { + + List systemUserSchemaAttributesList = null; + + if (SCIMCommonUtils.isUserExtensionEnabled()) { + Map scimClaimToLocalClaimMap = + getMappedLocalClaimsForDialect(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT, tenantDomain); + + Map filteredAttributeMap = + getFilteredSchemaAttributes(scimClaimToLocalClaimMap); + Map hierarchicalAttributeMap = + buildHierarchicalAttributeMapForEnterpriseSchema(filteredAttributeMap, false, true); + + systemUserSchemaAttributesList = new ArrayList(hierarchicalAttributeMap.values()); + + if (log.isDebugEnabled()) { + logSchemaAttributes(systemUserSchemaAttributesList); + } + } else { + log.debug("System user schema support disabled."); + } + return systemUserSchemaAttributesList; + } + /** * Get mapped local claims for the claims in specified external claim dialect. * @@ -5810,7 +5856,7 @@ private Map getFilteredUserSchemaAttributes(Map getFilteredSchemaAttributes(Map isSupportedByDefault(entry.getValue())) - .map(e -> getSchemaAttributes(e.getKey(), e.getValue(), true)) - .collect(Collectors.toMap(attr -> attr.getName(), Function.identity())); + .map(e -> getSchemaAttributes(e.getKey(), e.getValue())) + .collect(Collectors.toMap(Attribute::getName, Function.identity())); } private boolean isSupportedByDefault(LocalClaim mappedLocalClaim) { @@ -5851,15 +5897,26 @@ private boolean isUsernameClaim(ExternalClaim scimClaim) { * @param mappedLocalClaim * @return */ - private Attribute getSchemaAttributes(ExternalClaim scimClaim, LocalClaim mappedLocalClaim, - boolean isExtensionAttr) { + private Attribute getSchemaAttributes(ExternalClaim scimClaim, LocalClaim mappedLocalClaim) { String name = scimClaim.getClaimURI(); - String claimDielectURI = scimClaim.getClaimDialectURI(); + String claimDialectURI = scimClaim.getClaimDialectURI(); + boolean isExtensionAttr = false; + boolean isSystemSchemaAttr = false; boolean isCustomSchemaAttr = false; boolean isComplexCustomAttr = false; + + if (SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema() != null + && SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI().equals(claimDialectURI)) { + isExtensionAttr = true; + } + if (SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema() != null + && SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema().getURI() + .equals(claimDialectURI)) { + isSystemSchemaAttr = true; + } if (getCustomSchemaURI() != null) { - isCustomSchemaAttr = getCustomSchemaURI().equalsIgnoreCase(claimDielectURI); + isCustomSchemaAttr = getCustomSchemaURI().equalsIgnoreCase(claimDialectURI); } if (mappedLocalClaim != null && mappedLocalClaim.getClaimProperties() != null) { for (Map.Entry claimProperty : mappedLocalClaim.getClaimProperties().entrySet()) { @@ -5882,7 +5939,7 @@ private Attribute getSchemaAttributes(ExternalClaim scimClaim, LocalClaim mapped attribute = new SimpleAttribute(name, null); } - populateBasicAttributes(mappedLocalClaim, attribute, isExtensionAttr, isCustomSchemaAttr); + populateBasicAttributes(mappedLocalClaim, attribute, isExtensionAttr, isSystemSchemaAttr, isCustomSchemaAttr); return attribute; } @@ -5956,11 +6013,11 @@ private SCIMDefinitions.DataType getCustomAttrDataType(String dataType) { /** * Populates basic Charon Attributes details using the claim metadata. * - * @param mappedLocalClaim - * @param attribute + * @param mappedLocalClaim Mapped local claim. + * @param attribute Charon Attribute. */ private void populateBasicAttributes(LocalClaim mappedLocalClaim, AbstractAttribute attribute, boolean - isEnterpriseExtensionAttr, boolean isCustomSchemaAttr) { + isEnterpriseExtensionAttr, boolean isSystemSchemaAttr, boolean isCustomSchemaAttr) { boolean isMultivaluedCustomAttr = false; String customAttrDataType = null; @@ -5996,9 +6053,12 @@ private void populateBasicAttributes(LocalClaim mappedLocalClaim, AbstractAttrib attribute.setType(SCIMDefinitions.DataType.COMPLEX); } else if (customAttrDataType != null) { attribute.setType(getCustomAttrDataType(customAttrDataType)); - } else if (isEnterpriseExtensionAttr) { - AttributeSchema attributeSchema = SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema() - .getSubAttributeSchema(attribute.getName()); + } else if (isEnterpriseExtensionAttr || isSystemSchemaAttr) { + AttributeSchema attributeSchema = isEnterpriseExtensionAttr + ? SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema() + .getSubAttributeSchema(attribute.getName()) + : SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema() + .getSubAttributeSchema(attribute.getName()); if (attributeSchema != null && attributeSchema.getType() != null) { attribute.setType(attributeSchema.getType()); } else { @@ -6050,11 +6110,11 @@ private void populateBasicAttributes(LocalClaim mappedLocalClaim, AbstractAttrib * @param filteredFlatAttributeMap * @return */ - private Map buildHierarchicalAttributeMapForEnterpriseSchema(Map - filteredFlatAttributeMap) - throws CharonException { + private Map buildHierarchicalAttributeMapForEnterpriseSchema( + Map filteredFlatAttributeMap, boolean isEnterpriseExtensionAttr, + boolean isSystemExtensionAttr) throws CharonException { - return buildHierarchicalAttributeMap(filteredFlatAttributeMap, true); + return buildHierarchicalAttributeMap(filteredFlatAttributeMap, isEnterpriseExtensionAttr, isSystemExtensionAttr); } /** @@ -6068,7 +6128,7 @@ private Map buildHierarchicalAttributeMapForStandardSchema(Ma filteredFlatAttributeMap) throws CharonException { - return buildHierarchicalAttributeMap(filteredFlatAttributeMap, false); + return buildHierarchicalAttributeMap(filteredFlatAttributeMap, false, false); } /** @@ -6078,7 +6138,7 @@ private Map buildHierarchicalAttributeMapForStandardSchema(Ma * @return */ private Map buildHierarchicalAttributeMap(Map filteredFlatAttributeMap, - boolean isEnterpriseExtensionAttr) + boolean isEnterpriseExtensionAttr, boolean isSystemSchemaAttr) throws CharonException { Map simpleAttributeMap = new HashMap<>(); @@ -6090,7 +6150,7 @@ private Map buildHierarchicalAttributeMap(Map buildHierarchicalAttributeMap(Map flatAttributeMap, Map complexAttributeMap, - boolean isEnterpriseExtensionAttr) + boolean isEnterpriseExtensionAttr, boolean isSystemSchemaAttr) throws CharonException { String attributeName = attribute.getName(); @@ -6126,7 +6186,8 @@ private ComplexAttribute handleSubAttribute(Attribute attribute, Map getCustomUserSchemaAttributes() throws CharonException { Map filteredAttributeMap = getFilteredSchemaAttributes(scimClaimToLocalClaimMap); Map hierarchicalAttributeMap = - buildHierarchicalAttributeMapForEnterpriseSchema(filteredAttributeMap); + buildHierarchicalAttributeMapForEnterpriseSchema(filteredAttributeMap, false, false); customUserSchemaAttributesList = new ArrayList(hierarchicalAttributeMap.values()); diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/internal/SCIMCommonComponent.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/internal/SCIMCommonComponent.java index 9994a8dd8..8c3b3aae7 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/internal/SCIMCommonComponent.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/internal/SCIMCommonComponent.java @@ -55,6 +55,7 @@ import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import org.wso2.charon3.core.config.SCIMConfigConstants; import org.wso2.charon3.core.config.SCIMCustomSchemaExtensionBuilder; +import org.wso2.charon3.core.config.SCIMSystemSchemaExtensionBuilder; import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder; import org.wso2.charon3.core.exceptions.CharonException; import org.wso2.charon3.core.exceptions.InternalErrorException; @@ -62,6 +63,8 @@ import java.io.File; +import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.USER_SCHEMA_EXTENSION_ENABLED; + @Component( name = "identity.scim2.common", immediate = true @@ -83,12 +86,13 @@ protected void activate(ComponentContext ctx) { SCIMConfigProcessor scimConfigProcessor = SCIMConfigProcessor.getInstance(); scimConfigProcessor.buildConfigFromFile(filePath); - // reading user schema extension - if (Boolean.parseBoolean(scimConfigProcessor.getProperty("user-schema-extension-enabled"))) { + // Reading schema extensions. + if (Boolean.parseBoolean(scimConfigProcessor.getProperty(USER_SCHEMA_EXTENSION_ENABLED))) { String schemaFilePath = CarbonUtils.getCarbonConfigDirPath() + File.separator + SCIMConfigConstants.SCIM_SCHEMA_EXTENSION_CONFIG; SCIMUserSchemaExtensionBuilder.getInstance().buildUserSchemaExtension(schemaFilePath); + SCIMSystemSchemaExtensionBuilder.getInstance().buildSystemSchemaExtension(schemaFilePath); } // If custom schema is enabled, read it root attribute URI from the file config if it is configured. if (SCIMCommonUtils.isCustomSchemaEnabled()) { diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java index d74f5bf05..b27e95df5 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonConstants.java @@ -47,6 +47,7 @@ public class SCIMCommonConstants { public static final String SCIM_USER_CLAIM_DIALECT = "urn:ietf:params:scim:schemas:core:2.0:User"; public static final String SCIM_ENTERPRISE_USER_CLAIM_DIALECT = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"; + public static final String SCIM_SYSTEM_USER_CLAIM_DIALECT = "urn:scim:wso2:schema"; public static final String EQ = "eq"; public static final String NE = "ne"; @@ -78,7 +79,10 @@ public class SCIMCommonConstants { public static final String BULK_MAX_OPERATIONS = "bulk-maxOperations"; public static final String BULK_MAX_PAYLOAD_SIZE = "bulk-maxPayloadSize"; public static final String FILTER_MAX_RESULTS = "filter-maxResults"; + @Deprecated public static final String ENTERPRISE_USER_EXTENSION_ENABLED = "user-schema-extension-enabled"; + public static final String USER_SCHEMA_EXTENSION_ENABLED = "user-schema-extension-enabled"; + public static final String LIST_USER_EXTENSION_SCHEMAS_ENABLED = "list-user-extension-schemas-enabled"; public static final String PAGINATION_DEFAULT_COUNT = "pagination-default-count"; public static final String CUSTOM_USER_SCHEMA_ENABLED = "custom-user-schema-enabled"; public static final String CUSTOM_USER_SCHEMA_URI = "custom-user-schema-uri"; diff --git a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java index 3de18a22d..974307fe9 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMCommonUtils.java @@ -49,6 +49,7 @@ import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import org.wso2.charon3.core.attributes.SCIMCustomAttribute; import org.wso2.charon3.core.config.SCIMCustomSchemaExtensionBuilder; +import org.wso2.charon3.core.config.SCIMSystemSchemaExtensionBuilder; import org.wso2.charon3.core.config.SCIMUserSchemaExtensionBuilder; import org.wso2.charon3.core.exceptions.CharonException; import org.wso2.charon3.core.exceptions.InternalErrorException; @@ -62,8 +63,6 @@ import java.util.Map; import java.util.Optional; -import static org.wso2.charon3.core.schema.SCIMConstants.CUSTOM_USER_SCHEMA_URI; - /** * This class is to be used as a Util class for SCIM common things. * TODO:rename class name. @@ -451,10 +450,16 @@ public static Map getSCIMtoLocalMappings() throws UserStoreExcep // Get the extension claims, if there are any extensions enabled. if (SCIMUserSchemaExtensionBuilder.getInstance().getExtensionSchema() != null) { - Map extensionClaims = ClaimMetadataHandler.getInstance() + Map enterpriseExtensionClaims = ClaimMetadataHandler.getInstance() .getMappingsMapFromOtherDialectToCarbon(SCIMUserSchemaExtensionBuilder.getInstance() .getExtensionSchema().getURI(), null, tenantDomain, false); - scimToLocalClaimMap.putAll(extensionClaims); + scimToLocalClaimMap.putAll(enterpriseExtensionClaims); + } + if (SCIMSystemSchemaExtensionBuilder.getInstance().getExtensionSchema() != null) { + Map systemExtensionClaims = ClaimMetadataHandler.getInstance() + .getMappingsMapFromOtherDialectToCarbon(SCIMSystemSchemaExtensionBuilder.getInstance() + .getExtensionSchema().getURI(), null, tenantDomain, false); + scimToLocalClaimMap.putAll(systemExtensionClaims); } String userTenantDomain = getTenantDomain(); @@ -614,11 +619,24 @@ public static boolean isHybridRole(String roleName) { * Check if SCIM enterprise user extension has been enabled. * * @return True if enterprise user extension enabled + * @deprecated Use {@link #isUserExtensionEnabled()} instead. */ + @Deprecated public static boolean isEnterpriseUserExtensionEnabled() { return Boolean.parseBoolean(SCIMConfigProcessor.getInstance() - .getProperty(SCIMCommonConstants.ENTERPRISE_USER_EXTENSION_ENABLED)); + .getProperty(SCIMCommonConstants.USER_SCHEMA_EXTENSION_ENABLED)); + } + + /** + * Check if SCIM enterprise user extension has been enabled. + * + * @return True if enterprise user extension enabled + */ + public static boolean isUserExtensionEnabled() { + + return Boolean.parseBoolean(SCIMConfigProcessor.getInstance() + .getProperty(SCIMCommonConstants.USER_SCHEMA_EXTENSION_ENABLED)); } /** @@ -728,7 +746,17 @@ public static String getCustomSchemaURI() { if (StringUtils.isNotBlank(customSchemaURI)) { return customSchemaURI; } - return CUSTOM_USER_SCHEMA_URI; + return SCIMConstants.CUSTOM_EXTENSION_SCHEMA_URI; + } + + public static boolean isExtensionSchemasListingEnabledInResourceTypesAPI() { + + String isExtensionSchemasListingEnabled = + SCIMConfigProcessor.getInstance().getProperty(SCIMCommonConstants.LIST_USER_EXTENSION_SCHEMAS_ENABLED); + if (StringUtils.isNotBlank(isExtensionSchemasListingEnabled)) { + return Boolean.parseBoolean(isExtensionSchemasListingEnabled); + } + return true; } /** diff --git a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java index 649a2686c..2a153df57 100644 --- a/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java +++ b/components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java @@ -50,6 +50,7 @@ import org.wso2.carbon.user.core.UserStoreClientException; import org.wso2.carbon.user.core.common.PaginatedUserResponse; import org.wso2.carbon.user.core.model.UniqueIDUserClaimSearchEntry; +import org.wso2.charon3.core.config.SCIMSystemSchemaExtensionBuilder; import org.wso2.charon3.core.exceptions.NotImplementedException; import org.wso2.charon3.core.extensions.UserManager; import org.wso2.charon3.core.objects.plainobjects.UsersGetResponse; @@ -127,7 +128,10 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import static org.wso2.charon3.core.schema.SCIMConstants.CUSTOM_EXTENSION_SCHEMA_URI; +import static org.wso2.charon3.core.schema.SCIMConstants.ENTERPRISE_USER_SCHEMA_URI; /* * Unit tests for SCIMUserManager @@ -207,6 +211,7 @@ public class SCIMUserManagerTest { @Mock private RolePermissionManagementService mockedRolePermissionManagementService; private MockedStatic scimUserSchemaExtensionBuilder; + private MockedStatic scimSystemSchemaExtensionBuilder; private MockedStatic identityUtil; private MockedStatic scimCommonUtils; private MockedStatic attributeMapper; @@ -228,11 +233,17 @@ public void setUpMethod() { applicationManagementServiceMockedStatic = mockStatic(ApplicationManagementService.class); scimCommonComponentHolder = mockStatic(SCIMCommonComponentHolder.class); scimUserSchemaExtensionBuilder = mockStatic(SCIMUserSchemaExtensionBuilder.class); + scimSystemSchemaExtensionBuilder = mockStatic(SCIMSystemSchemaExtensionBuilder.class); claimMetadataHandler = mockStatic(ClaimMetadataHandler.class); resourceManagerUtil = mockStatic(ResourceManagerUtil.class); SCIMUserSchemaExtensionBuilder mockSCIMUserSchemaExtensionBuilder = mock(SCIMUserSchemaExtensionBuilder.class); + SCIMSystemSchemaExtensionBuilder mockSCIMSystemSchemaExtensionBuilder = mock(SCIMSystemSchemaExtensionBuilder.class); scimUserSchemaExtensionBuilder.when(SCIMUserSchemaExtensionBuilder::getInstance).thenReturn(mockSCIMUserSchemaExtensionBuilder); when(mockSCIMUserSchemaExtensionBuilder.getExtensionSchema()).thenReturn(mockedSCIMAttributeSchema); + when(mockedSCIMAttributeSchema.getURI()).thenReturn(ENTERPRISE_USER_SCHEMA_URI) + .thenReturn(CUSTOM_EXTENSION_SCHEMA_URI); + scimSystemSchemaExtensionBuilder.when(SCIMSystemSchemaExtensionBuilder::getInstance).thenReturn(mockSCIMSystemSchemaExtensionBuilder); + when(mockSCIMSystemSchemaExtensionBuilder.getExtensionSchema()).thenReturn(mockedSCIMAttributeSchema); } @AfterMethod @@ -244,6 +255,7 @@ public void tearDown() { applicationManagementServiceMockedStatic.close(); scimCommonComponentHolder.close(); scimUserSchemaExtensionBuilder.close(); + scimSystemSchemaExtensionBuilder.close(); claimMetadataHandler.close(); resourceManagerUtil.close(); } @@ -1180,7 +1192,7 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception { MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(externalClaimMap); when(mockClaimMetadataManagementService.getLocalClaims(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)) .thenReturn(localClaimMap); - scimCommonUtils.when(() -> SCIMCommonUtils.isEnterpriseUserExtensionEnabled()).thenReturn(true); + scimCommonUtils.when(() -> SCIMCommonUtils.isUserExtensionEnabled()).thenReturn(true); SCIMUserSchemaExtensionBuilder sb = spy(new SCIMUserSchemaExtensionBuilder()); scimUserSchemaExtensionBuilder.when(() -> SCIMUserSchemaExtensionBuilder.getInstance()).thenReturn(sb); @@ -1196,10 +1208,10 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception { @Test public void testGetEnterpriseUserSchemaWhenDisabled() throws Exception { - scimCommonUtils.when(() -> SCIMCommonUtils.isEnterpriseUserExtensionEnabled()).thenReturn(false); + scimCommonUtils.when(SCIMCommonUtils::isUserExtensionEnabled).thenReturn(false); SCIMUserManager userManager = new SCIMUserManager(mockedUserStoreManager, mockClaimMetadataManagementService, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); - assertEquals(userManager.getEnterpriseUserSchema(), null); + assertNull(userManager.getEnterpriseUserSchema()); } @Test diff --git a/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml b/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml index 10a31a5d7..a6baa028b 100644 --- a/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml +++ b/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml @@ -19,7 +19,7 @@ true true - urn:scim:wso2:schema + urn:scim:schemas:extension:custom:User true http://example.com/help/scim.html true @@ -31,6 +31,7 @@ false false 100 + true OAuth Bearer Token diff --git a/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml.j2 b/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml.j2 index 24f611a72..6bfdbb5b6 100644 --- a/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml.j2 +++ b/features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml.j2 @@ -32,6 +32,7 @@ false false 100 + scim2.enable_list_user_schemas OAuth Bearer Token diff --git a/features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.default.json b/features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.default.json index 9c0eb384b..688dcbeeb 100644 --- a/features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.default.json +++ b/features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.default.json @@ -1,12 +1,12 @@ { "scim2.enable_schema_extension": true, "scim2.enable_custom_schema_extension": true, - "scim2.custom_user_schema_uri": "urn:scim:wso2:schema", + "scim2.custom_user_schema_uri": "urn:scim:schemas:extension:custom:User", "scim2.max_bulk_operations": "1000", "scim2.max_bulk_payload": "1048576", "scim2.documentation_uri": "https://is.docs.wso2.com/en/latest/apis/scim2/", "scim2.oauth_bearer.primary": true, "scim2.http_basic.primary": false, "scim2.basic_auth_documentation_uri": "$ref{scim2.documentation_uri}", - "scim2.oauth_bearer_auth_documentation_uri": "$ref{scim2.documentation_uri}" + "scim2.enable_list_user_schemas": true } diff --git a/features/org.wso2.carbon.identity.scim2.common.feature/resources/scim2-schema-extension.config b/features/org.wso2.carbon.identity.scim2.common.feature/resources/scim2-schema-extension.config index e7f96e677..2192bb11e 100755 --- a/features/org.wso2.carbon.identity.scim2.common.feature/resources/scim2-schema-extension.config +++ b/features/org.wso2.carbon.identity.scim2.common.feature/resources/scim2-schema-extension.config @@ -748,5 +748,695 @@ "subAttributes":"verifyEmail askPassword employeeNumber costCenter organization division department manager pendingEmails accountLocked accountState emailOTPDisabled emailVerified failedEmailOTPAttempts failedLoginAttempts failedLoginAttemptsBeforeSuccess failedLoginLockoutCount failedPasswordRecoveryAttempts failedSMSOTPAttempts failedTOTPAttempts isLiteUser lastLoginTime lastLogonTime lastPasswordUpdateTime lockedReason phoneVerified preferredChannel smsOTPDisabled tenantAdminAskPassword unlockTime accountDisabled dateOfBirth isReadOnlyUser pendingMobileNumber forcePasswordReset oneTimePassword verifyMobile country userSourceId totpEnabled backupCodeEnabled enabledAuthenticators failedBackupCodeAttempts managedOrg preferredMFAOption", "canonicalValues":[], "referenceTypes":["external"] +}, +{ +"attributeURI":"urn:scim:wso2:schema:askPassword", +"attributeName":"askPassword", +"dataType":"boolean", +"multiValued":"false", +"description":"Enable password change required notification in the user creation.", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:verifyEmail", +"attributeName":"verifyEmail", +"dataType":"boolean", +"multiValued":"false", +"description":"Enable email confirmation notification in the user creation.", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:pendingEmails.value", +"attributeName":"value", +"dataType":"string", +"multiValued":"false", +"description":"Store email to be updated as a temporary claim till email verification happens.", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:pendingEmails", +"attributeName":"pendingEmails", +"dataType":"complex", +"multiValued":"true", +"description":"The User's email addresses. A complex type that represents verification pending email addresses of the user.", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"value", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:accountLocked", +"attributeName":"accountLocked", +"dataType":"boolean", +"multiValued":"false", +"description":"Account Locked", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:accountState", +"attributeName":"accountState", +"dataType":"string", +"multiValued":"false", +"description":"Account state", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:emailOTPDisabled", +"attributeName":"emailOTPDisabled", +"dataType":"boolean", +"multiValued":"false", +"description":"Store whether email OTP is enabled or disabled", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:emailVerified", +"attributeName":"emailVerified", +"dataType":"boolean", +"multiValued":"false", +"description":"True if the End-User's e-mail address has been verified; otherwise false", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedEmailOTPAttempts", +"attributeName":"failedEmailOTPAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed email OTP attempts", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedLoginAttempts", +"attributeName":"failedLoginAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed login attempts", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedLoginAttemptsBeforeSuccess", +"attributeName":"failedLoginAttemptsBeforeSuccess", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed attempts before a success login", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedLoginLockoutCount", +"attributeName":"failedLoginLockoutCount", +"dataType":"integer", +"multiValued":"false", +"description":"Failed lockout count", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedPasswordRecoveryAttempts", +"attributeName":"failedPasswordRecoveryAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of consecutive failed attempts done for password recovery", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedSMSOTPAttempts", +"attributeName":"failedSMSOTPAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed SMS OTP attempts", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedTOTPAttempts", +"attributeName":"failedTOTPAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed TOTP attempts", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:isLiteUser", +"attributeName":"isLiteUser", +"dataType":"boolean", +"multiValued":"false", +"description":"Store whether the account is a lite user account", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:lastLoginTime", +"attributeName":"lastLoginTime", +"dataType":"string", +"multiValued":"false", +"description":"Last login time", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:lastLogonTime", +"attributeName":"lastLogonTime", +"dataType":"string", +"multiValued":"false", +"description":"Last logon time", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:lastPasswordUpdateTime", +"attributeName":"lastPasswordUpdateTime", +"dataType":"string", +"multiValued":"false", +"description":"Last password update time", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:lockedReason", +"attributeName":"lockedReason", +"dataType":"string", +"multiValued":"false", +"description":"The reason why the user account is locked", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:phoneVerified", +"attributeName":"phoneVerified", +"dataType":"boolean", +"multiValued":"false", +"description":"True if the End-User's phone number has been verified; otherwise false", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:preferredChannel", +"attributeName":"preferredChannel", +"dataType":"string", +"multiValued":"false", +"description":"Preferred Notification Channel", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:smsOTPDisabled", +"attributeName":"smsOTPDisabled", +"dataType":"boolean", +"multiValued":"false", +"description":"Store whether SMS OTP is enabled or disabled", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:tenantAdminAskPassword", +"attributeName":"tenantAdminAskPassword", +"dataType":"boolean", +"multiValued":"false", +"description":"Temporary claim to invoke email tenant admin ask Password feature", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:unlockTime", +"attributeName":"unlockTime", +"dataType":"string", +"multiValued":"false", +"description":"Unlock time", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:accountDisabled", +"attributeName":"accountDisabled", +"dataType":"boolean", +"multiValued":"false", +"description":"Store whether the user account is disabled or not", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:dateOfBirth", +"attributeName":"dateOfBirth", +"dataType":"string", +"multiValued":"false", +"description":"Date of birth", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:isReadOnlyUser", +"attributeName":"isReadOnlyUser", +"dataType":"boolean", +"multiValued":"false", +"description":"States whether the user is in a read only user store or not", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"request", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:pendingMobileNumber", +"attributeName":"pendingMobileNumber", +"dataType":"string", +"multiValued":"false", +"description":"Store user's mobile number to be updated as a temporary claim until mobile number verification happens.", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:forcePasswordReset", +"attributeName":"forcePasswordReset", +"dataType":"boolean", +"multiValued":"false", +"description":"Temporary claim to invoke forced password reset feature", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:oneTimePassword", +"attributeName":"oneTimePassword", +"dataType":"string", +"multiValued":"false", +"description":"One Time Password", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"request", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:verifyMobile", +"attributeName":"verifyMobile", +"dataType":"boolean", +"multiValued":"false", +"description":"Enable mobile verification during mobile number update.", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:country", +"attributeName":"country", +"dataType":"string", +"multiValued":"false", +"description":"Country", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:userSourceId", +"attributeName":"userSourceId", +"dataType":"string", +"multiValued":"false", +"description":"User Provisioned IDP ID", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:totpEnabled", +"attributeName":"totpEnabled", +"dataType":"string", +"multiValued":"false", +"description":"TOTP Enabled", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:backupCodeEnabled", +"attributeName":"backupCodeEnabled", +"dataType":"string", +"multiValued":"false", +"description":"Backup Code Enabled", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:enabledAuthenticators", +"attributeName":"enabledAuthenticators", +"dataType":"string", +"multiValued":"false", +"description":"Enabled Authenticators", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:failedBackupCodeAttempts", +"attributeName":"failedBackupCodeAttempts", +"dataType":"integer", +"multiValued":"false", +"description":"Number of failed backup code attempts", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:managedOrg", +"attributeName":"managedOrg", +"dataType":"string", +"multiValued":"false", +"description":"Organization where the user is managed", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:preferredMFAOption", +"attributeName":"preferredMFAOption", +"dataType":"string", +"multiValued":"false", +"description":"Preferred MFA option", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:emailAddresses", +"attributeName":"emailAddresses", +"dataType":"string", +"multiValued":"true", +"description":"Email Addresses", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:verifiedEmailAddresses", +"attributeName":"verifiedEmailAddresses", +"dataType":"string", +"multiValued":"true", +"description":"Verified Email Addresses", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:mobileNumbers", +"attributeName":"mobileNumbers", +"dataType":"string", +"multiValued":"true", +"description":"Mobile Numbers", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:verifiedMobileNumbers", +"attributeName":"verifiedMobileNumbers", +"dataType":"string", +"multiValued":"true", +"description":"Verified Mobile Numbers", +"required":"false", +"caseExact":"false", +"mutability":"readwrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema:passwordExpiryTime", +"attributeName":"passwordExpiryTime", +"dataType":"string", +"multiValued":"false", +"description":"Password Expiry Time", +"required":"false", +"caseExact":"false", +"mutability":"readOnly", +"returned":"request", +"uniqueness":"none", +"subAttributes":"null", +"canonicalValues":[], +"referenceTypes":[] +}, +{ +"attributeURI":"urn:scim:wso2:schema", +"attributeName":"urn:scim:wso2:schema", +"dataType":"complex", +"multiValued":"false", +"description":"Enterprise User", +"required":"false", +"caseExact":"false", +"mutability":"readWrite", +"returned":"default", +"uniqueness":"none", +"subAttributes":"verifyEmail askPassword pendingEmails accountLocked accountState emailOTPDisabled emailVerified failedEmailOTPAttempts failedLoginAttempts failedLoginAttemptsBeforeSuccess failedLoginLockoutCount failedPasswordRecoveryAttempts failedSMSOTPAttempts failedTOTPAttempts isLiteUser lastLoginTime lastLogonTime lastPasswordUpdateTime lockedReason phoneVerified preferredChannel smsOTPDisabled tenantAdminAskPassword unlockTime accountDisabled dateOfBirth isReadOnlyUser pendingMobileNumber forcePasswordReset oneTimePassword verifyMobile country userSourceId totpEnabled backupCodeEnabled enabledAuthenticators failedBackupCodeAttempts managedOrg preferredMFAOption emailAddresses verifiedEmailAddresses mobileNumbers verifiedMobileNumbers passwordExpiryTime", +"canonicalValues":[], +"referenceTypes":["external"] } ]