Repository: wso2-extensions/identity-inbound-provisioning-scim2
Pull request #642: Support canonical values for SCIM schema response
Author: login: sadilchamishka
Created: 2025-05-18T19:39:25Z | Merged: 2025-05-26T06:25:22Z

### Related Issues
- https://github.com/wso2/product-is/issues/24049

### Depends on
- https://github.com/wso2/charon/pull/433
- https://github.com/wso2/carbon-identity-framework/pull/6753

```
{
    "displayName": "Account Type",
    "displayOrder": "20",
    "profiles": {
        "console": {
            "supportedByDefault": true
        }
    },
    "sharedProfileValueResolvingMethod": "FromOrigin",
    "type": "STRING",
    "required": false,
    "uniqueness": "NONE",
    "name": "account_type",
    "canonicalValues": [
        {
            "value": "SVA",
            "displayValue": "SAVINGS Account"
        },
        {
            "value": "CA",
            "displayValue": "CURRENT Account"
        },
        {
            "value": "FD",
            "displayValue": "FIXED Deposit"
        }
    ],
    "supportedByDefault": "false",
    "mutability": "READ_WRITE",
    "multiValued": false,
    "caseExact": false,
    "returned": "DEFAULT"
}
```


Diff:
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 17e79e11e..be50b4632 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
@@ -28,6 +28,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.http.HttpStatus;
+import org.json.JSONArray;
 import org.json.JSONObject;
 import org.wso2.carbon.CarbonConstants;
 import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
@@ -6216,6 +6217,11 @@ private void populateBasicAttributes(LocalClaim mappedLocalClaim, AbstractAttrib
                 attribute.addAttributeProperty(SUPPORTED_BY_DEFAULT_PROPERTY,
                         mappedLocalClaim.getClaimProperty(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY));
             }
+            if (StringUtils.isNotEmpty(mappedLocalClaim.getClaimProperty(ClaimConstants.CANONICAL_VALUES_PROPERTY))) {
+                JSONArray canonicalValues =
+                        new JSONArray(mappedLocalClaim.getClaimProperty(ClaimConstants.CANONICAL_VALUES_PROPERTY));
+                attribute.addAttributeJSONArray(ClaimConstants.CANONICAL_VALUES_PROPERTY, canonicalValues);
+            }
 
             // Add attribute profile properties.
             for (Map.Entry<String, String> entry: mappedLocalClaim.getClaimProperties().entrySet()) {
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 507f6f438..5338b9f0c 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
@@ -19,6 +19,7 @@
 package org.wso2.carbon.identity.scim2.common.impl;
 
 import org.apache.commons.lang.StringUtils;
+import org.json.JSONArray;
 import org.json.JSONObject;
 import org.mockito.Mock;
 import org.mockito.MockedConstruction;
@@ -1349,6 +1350,55 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception {
         assertEquals(userManager.getEnterpriseUserSchema().size(), 2);
     }
 
+    @Test
+    public void testGetSystemUserSchema() throws Exception {
+
+        JSONArray canonicalValues = new JSONArray();
+
+        JSONObject localUser = new JSONObject();
+        localUser.put("value", "LOCAL");
+        localUser.put("displayValue", "Local User");
+
+        JSONObject federatedUser = new JSONObject();
+        federatedUser.put("value", "FEDERATED");
+        federatedUser.put("displayValue", "Federated User");
+
+        canonicalValues.put(localUser);
+        canonicalValues.put(federatedUser);
+
+        Map<String, String> properties = new HashMap<String, String>() {{
+            put("ReadOnly", "true");
+            put("SupportedByDefault", "true");
+            put("Description", "sample");
+            put("Required", "true");
+            put("DataType", "Integer");
+            put("canonicalValues", canonicalValues.toString());
+        }};
+
+        List<LocalClaim> localClaimMap = new ArrayList<LocalClaim>() {{
+            add(new LocalClaim("userType", new ArrayList<>(), properties));
+        }};
+        List<ExternalClaim> externalClaimMap = new ArrayList<ExternalClaim>() {{
+            add(new ExternalClaim(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                    SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT + ":userType",
+                    "userType"));
+        }};
+
+        when(mockClaimMetadataManagementService
+                .getExternalClaims(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                        MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(externalClaimMap);
+        when(mockClaimMetadataManagementService.getLocalClaims(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME))
+                .thenReturn(localClaimMap);
+        scimCommonUtils.when(SCIMCommonUtils::isUserExtensionEnabled).thenReturn(true);
+
+        SCIMUserManager userManager = new SCIMUserManager(mockedUserStoreManager, mockClaimMetadataManagementService,
+                MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
+        List<Attribute> systemSchema = userManager.getSystemUserSchema();
+        assertEquals(systemSchema.size(), 1);
+        JSONObject jsonObject = systemSchema.get(0).getAttributeJSONArray("canonicalValues").getJSONObject(0);
+        assertEquals(jsonObject.get("displayValue"), canonicalValues.getJSONObject(0).get("displayValue"));
+    }
+
     @Test
     public void testGetEnterpriseUserSchemaWhenDisabled() throws Exception {
 
diff --git a/pom.xml b/pom.xml
index eff732b19..97e19fd67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -305,11 +305,11 @@
         <inbound.auth.oauth.version>6.5.3</inbound.auth.oauth.version>
         <commons-collections.version>3.2.0.wso2v1</commons-collections.version>
         <carbon.kernel.version>4.10.47</carbon.kernel.version>
-        <identity.framework.version>7.8.138</identity.framework.version>
+        <identity.framework.version>7.8.150</identity.framework.version>
         <junit.version>4.13.1</junit.version>
         <commons.lang.version>20030203.000129</commons.lang.version>
         <identity.governance.version>1.8.12</identity.governance.version>
-        <charon.version>4.0.36</charon.version>
+        <charon.version>4.0.46</charon.version>
         <org.wso2.carbon.identity.organization.management.core.version>1.0.76
         </org.wso2.carbon.identity.organization.management.core.version>
         <org.wso2.carbon.identity.handler.event.account.lock.version>1.8.13


Reviews:
user: login: Malith-19
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T03:42:58Z
user: login: sadilchamishka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T03:47:00Z
user: login: sadilchamishka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T04:01:11Z
user: login: Malith-19
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T04:14:51Z
user: login: jenkins-is-staging
body: Approving the pull request based on the successful pr build https://github.com/wso2/product-is/actions/runs/15245672211
state: APPROVED
author_association: NONE
submitted_at: 2025-05-26T05:21:42Z

Review comments:
diff_hunk: @@ -1349,6 +1350,61 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception {
         assertEquals(userManager.getEnterpriseUserSchema().size(), 2);
     }
 
+    @Test
+    public void testGetSystemUserSchema() throws Exception {
+
+        JSONArray canonicalValues = new JSONArray();
+
+        JSONObject localUser = new JSONObject();
+        localUser.put("value", "LOCAL");
+        localUser.put("displayValue", "Local User");
+
+        JSONObject federatedUser = new JSONObject();
+        federatedUser.put("value", "FEDERATED");
+        federatedUser.put("displayValue", "Federated User");
+
+        canonicalValues.put(localUser);
+        canonicalValues.put(federatedUser);
+
+        Map<String, String> properties = new HashMap<String, String>() {{
+            put("ReadOnly", "true");
+            put("SupportedByDefault", "true");
+            put("Description", "sample");
+            put("Required", "true");
+            put("DataType", "Integer");
+            put("canonicalValues", canonicalValues.toString());
+        }};
+
+        List<LocalClaim> localClaimMap = new ArrayList<LocalClaim>() {{
+            add(new LocalClaim("userType", new ArrayList<>(), properties));
+        }};
+        List<ExternalClaim> externalClaimMap = new ArrayList<ExternalClaim>() {{
+            add(new ExternalClaim(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                    SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT + ":userType",
+                    "userType"));
+        }};
+
+        when(mockClaimMetadataManagementService
+                .getExternalClaims(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                        MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(externalClaimMap);
+        when(mockClaimMetadataManagementService.getLocalClaims(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME))
+                .thenReturn(localClaimMap);
+        scimCommonUtils.when(SCIMCommonUtils::isUserExtensionEnabled).thenReturn(true);
+
+        SCIMUserSchemaExtensionBuilder sb = spy(new SCIMUserSchemaExtensionBuilder());
path: components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java
user: login: Malith-19
body: what's the reason to use a spy instead of mock here?
created_at: 2025-05-26T03:42:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1349,6 +1350,61 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception {
         assertEquals(userManager.getEnterpriseUserSchema().size(), 2);
     }
 
+    @Test
+    public void testGetSystemUserSchema() throws Exception {
+
+        JSONArray canonicalValues = new JSONArray();
+
+        JSONObject localUser = new JSONObject();
+        localUser.put("value", "LOCAL");
+        localUser.put("displayValue", "Local User");
+
+        JSONObject federatedUser = new JSONObject();
+        federatedUser.put("value", "FEDERATED");
+        federatedUser.put("displayValue", "Federated User");
+
+        canonicalValues.put(localUser);
+        canonicalValues.put(federatedUser);
+
+        Map<String, String> properties = new HashMap<String, String>() {{
+            put("ReadOnly", "true");
+            put("SupportedByDefault", "true");
+            put("Description", "sample");
+            put("Required", "true");
+            put("DataType", "Integer");
+            put("canonicalValues", canonicalValues.toString());
+        }};
+
+        List<LocalClaim> localClaimMap = new ArrayList<LocalClaim>() {{
+            add(new LocalClaim("userType", new ArrayList<>(), properties));
+        }};
+        List<ExternalClaim> externalClaimMap = new ArrayList<ExternalClaim>() {{
+            add(new ExternalClaim(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                    SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT + ":userType",
+                    "userType"));
+        }};
+
+        when(mockClaimMetadataManagementService
+                .getExternalClaims(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                        MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(externalClaimMap);
+        when(mockClaimMetadataManagementService.getLocalClaims(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME))
+                .thenReturn(localClaimMap);
+        scimCommonUtils.when(SCIMCommonUtils::isUserExtensionEnabled).thenReturn(true);
+
+        SCIMUserSchemaExtensionBuilder sb = spy(new SCIMUserSchemaExtensionBuilder());
path: components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java
user: login: sadilchamishka
body: Copied from a similar method https://github.com/wso2-extensions/identity-inbound-provisioning-scim2/pull/642/files#diff-d2cb935864d3a443b7fff1941fff3af72eb0e8c9df667cbb35ab652cc144179bR1342

Will update it as it looks clean. 
created_at: 2025-05-26T03:47:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1349,6 +1350,61 @@ public void testGetEnterpriseUserSchemaWhenEnabled() throws Exception {
         assertEquals(userManager.getEnterpriseUserSchema().size(), 2);
     }
 
+    @Test
+    public void testGetSystemUserSchema() throws Exception {
+
+        JSONArray canonicalValues = new JSONArray();
+
+        JSONObject localUser = new JSONObject();
+        localUser.put("value", "LOCAL");
+        localUser.put("displayValue", "Local User");
+
+        JSONObject federatedUser = new JSONObject();
+        federatedUser.put("value", "FEDERATED");
+        federatedUser.put("displayValue", "Federated User");
+
+        canonicalValues.put(localUser);
+        canonicalValues.put(federatedUser);
+
+        Map<String, String> properties = new HashMap<String, String>() {{
+            put("ReadOnly", "true");
+            put("SupportedByDefault", "true");
+            put("Description", "sample");
+            put("Required", "true");
+            put("DataType", "Integer");
+            put("canonicalValues", canonicalValues.toString());
+        }};
+
+        List<LocalClaim> localClaimMap = new ArrayList<LocalClaim>() {{
+            add(new LocalClaim("userType", new ArrayList<>(), properties));
+        }};
+        List<ExternalClaim> externalClaimMap = new ArrayList<ExternalClaim>() {{
+            add(new ExternalClaim(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                    SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT + ":userType",
+                    "userType"));
+        }};
+
+        when(mockClaimMetadataManagementService
+                .getExternalClaims(SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT,
+                        MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)).thenReturn(externalClaimMap);
+        when(mockClaimMetadataManagementService.getLocalClaims(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME))
+                .thenReturn(localClaimMap);
+        scimCommonUtils.when(SCIMCommonUtils::isUserExtensionEnabled).thenReturn(true);
+
+        SCIMUserSchemaExtensionBuilder sb = spy(new SCIMUserSchemaExtensionBuilder());
path: components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/SCIMUserManagerTest.java
user: login: sadilchamishka
body: Addressed the comment by https://github.com/wso2-extensions/identity-inbound-provisioning-scim2/pull/642/commits/b74a8993c07e2cc0854f1bf99a6471068740cdfe and removed unnecessary test related code from the introduced new test method. 
created_at: 2025-05-26T04:01:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-23T11:24:58Z
body: ## [Codecov](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/pull/642?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 47.32%. Comparing base [(`2a09ec4`)](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/commit/2a09ec406e7ad9f675b1d944bdd2bc810f015ab0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions) to head [(`dfc2666`)](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/commit/dfc2666ccc969e02750da5cacb0b538de683261b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions).
> Report is 17 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master     #642      +/-   ##
============================================
- Coverage     48.78%   47.32%   -1.47%     
- Complexity     1247     1337      +90     
============================================
  Files            45       45              
  Lines          9464    10934    +1470     
  Branches       2995     3484     +489     
============================================
+ Hits           4617     5174     +557     
- Misses         3913     4669     +756     
- Partials        934     1091     +157     
```

| [Flag](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/pull/642/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions) | Coverage Δ | |
|---|---|---|
| [unit](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/pull/642/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions) | `35.53% <100.00%> (+0.90%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/wso2-extensions/identity-inbound-provisioning-scim2/pull/642?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wso2-extensions).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: jenkins-is-staging
created_at: 2025-05-26T03:40:29Z
body: PR builder started 
Link: https://github.com/wso2/product-is/actions/runs/15245672211
author_association: NONE
user: login: jenkins-is-staging
created_at: 2025-05-26T05:21:41Z
body: PR builder completed 
Link: https://github.com/wso2/product-is/actions/runs/15245672211 
Status: **success**
author_association: NONE