├── .github ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── on-pr-merge.yml │ ├── pr-builder.yml │ └── coverage-generator.yml ├── components ├── org.wso2.carbon.identity.scim2.provider │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring.schemas │ │ │ ├── webapp │ │ │ └── META-INF │ │ │ │ ├── context.xml │ │ │ │ └── webapp-classloading.xml │ │ │ └── java │ │ │ └── org │ │ │ └── wso2 │ │ │ └── carbon │ │ │ └── identity │ │ │ ├── jaxrs │ │ │ └── designator │ │ │ │ └── PATCH.java │ │ │ └── scim2 │ │ │ └── provider │ │ │ ├── resources │ │ │ ├── ServiceProviderConfigResource.java │ │ │ ├── ResourceTypesResource.java │ │ │ ├── SchemaResource.java │ │ │ ├── AbstractResource.java │ │ │ └── BulkResource.java │ │ │ ├── impl │ │ │ └── ApplicationInitializer.java │ │ │ └── util │ │ │ └── SCIMProviderConstants.java │ └── pom.xml └── org.wso2.carbon.identity.scim2.common │ └── src │ ├── test │ ├── resources │ │ ├── dbscripts │ │ │ └── identity.sql │ │ ├── charon-config-test.xml │ │ └── testng.xml │ └── java │ │ └── org │ │ └── wso2 │ │ └── carbon │ │ └── identity │ │ └── scim2 │ │ └── common │ │ ├── test │ │ ├── utils │ │ │ └── CommonTestUtils.java │ │ └── constants │ │ │ └── TestConstants.java │ │ ├── impl │ │ ├── DefaultSCIMUserStoreErrorResolverTest.java │ │ ├── IdentitySCIMManagerTest.java │ │ └── IdentityResourceURLBuilderTest.java │ │ ├── utils │ │ ├── AuthenticationSchemaTest.java │ │ ├── AdminAttributeUtilTest.java │ │ ├── SCIMConfigProcessorTest.java │ │ └── AdminAttributeUtilTestForGroup.java │ │ ├── cache │ │ └── SCIMSystemAttributeSchemaCacheTest.java │ │ └── handlers │ │ └── SCIMClaimOperationEventHandlerTest.java │ └── main │ ├── resources │ └── META-INF │ │ └── component.xml │ └── java │ └── org │ └── wso2 │ └── carbon │ └── identity │ └── scim2 │ └── common │ ├── exceptions │ └── IdentitySCIMException.java │ ├── cache │ ├── SCIMAgentAttributeSchemaCacheEntry.java │ ├── SCIMCustomAttributeSchemaCacheEntry.java │ ├── SCIMSystemAttributeSchemaCacheEntry.java │ ├── SCIMAgentAttributeSchemaCacheKey.java │ ├── SCIMCustomAttributeSchemaCacheKey.java │ ├── SCIMSystemAttributeSchemaCacheKey.java │ ├── SCIMAgentAttributeSchemaCache.java │ ├── SCIMCustomAttributeSchemaCache.java │ └── SCIMSystemAttributeSchemaCache.java │ ├── extenstion │ ├── SCIMUserStoreException.java │ └── SCIMUserStoreErrorResolver.java │ ├── impl │ ├── IdentityResourceURLBuilder.java │ └── DefaultSCIMUserStoreErrorResolver.java │ ├── utils │ ├── AuthenticationSchema.java │ └── SCIMConfigProcessor.java │ ├── listener │ └── SCIMTenantMgtListener.java │ ├── DAO │ └── SQLQueries.java │ └── handlers │ └── SCIMClaimOperationEventHandler.java ├── .gitignore ├── .travis.yml ├── features ├── org.wso2.carbon.identity.scim2.common.feature │ ├── resources │ │ ├── org.wso2.carbon.identity.scim2.common.feature.infer.json │ │ ├── org.wso2.carbon.identity.scim2.common.feature.default.json │ │ ├── p2.inf │ │ ├── charon-config.xml │ │ └── charon-config.xml.j2 │ └── pom.xml ├── org.wso2.carbon.identity.scim2.provider.feature │ ├── resources │ │ └── p2.inf │ └── pom.xml └── org.wso2.carbon.identity.scim2.server.feature │ └── pom.xml ├── codecov.yml ├── issue_template.md ├── .connector-store └── meta.json ├── README.md └── pull_request_template.md /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Report an issue 4 | url: https://github.com/wso2/product-is/issues/new/choose 5 | about: Issue creation for this component is done in the product-is repo. Click "Open" to continue. 6 | 7 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/resources/META-INF/spring.schemas: -------------------------------------------------------------------------------- 1 | http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd 2 | http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd 3 | 4 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/resources/dbscripts/identity.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS IDN_SCIM_GROUP ( 2 | ID INTEGER NOT NULL AUTO_INCREMENT, 3 | TENANT_ID INTEGER NOT NULL, 4 | ROLE_NAME VARCHAR(255) NOT NULL, 5 | ATTR_NAME VARCHAR(1024) NOT NULL, 6 | ATTR_VALUE VARCHAR(1024), 7 | PRIMARY KEY (ID) 8 | ); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target 3 | .classpath 4 | .settings 5 | .project 6 | *.iml 7 | *.iws 8 | *.ipr 9 | .idea 10 | *.DS_Store 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | 20 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 21 | hs_err_pid* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - wget https://archive.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip 3 | - unzip -qq apache-maven-3.3.9-bin.zip 4 | - export M2_HOME=$PWD/apache-maven-3.3.9 5 | - export PATH=$M2_HOME/bin:$PATH 6 | language: java 7 | jdk: 8 | - openjdk8 9 | install: /bin/true 10 | script: mvn clean install 11 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.infer.json: -------------------------------------------------------------------------------- 1 | { 2 | "scim2.primary_authentication_scheme": { 3 | "oauth_bearer_token": { 4 | "scim2.oauth_bearer.primary": true, 5 | "scim2.http_basic.primary": false 6 | }, 7 | "http_basic": { 8 | "scim2.oauth_bearer.primary": false, 9 | "scim2.http_basic.primary": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | notify: 4 | wait_for_ci: yes 5 | max_report_age: false 6 | 7 | coverage: 8 | status: 9 | project: off 10 | patch: off 11 | 12 | flag_management: 13 | default_rules: 14 | carryforward: true 15 | individual_flags: 16 | - name: unit 17 | statuses: 18 | - type: project 19 | target: auto 20 | threshold: null 21 | - type: patch 22 | target: 80% 23 | threshold: 40% 24 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.provider.feature/resources/p2.inf: -------------------------------------------------------------------------------- 1 | instructions.configure = \ 2 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/);\ 3 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/);\ 4 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ 5 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.provider_${feature.version}/scim2.war,target:${installFolder}/../../deployment/server/webapps/scim2.war,overwrite:true);\ -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/resources/org.wso2.carbon.identity.scim2.common.feature.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "scim2.enable_schema_extension": true, 3 | "scim2.enable_custom_schema_extension": true, 4 | "scim2.custom_user_schema_uri": "urn:scim:schemas:extension:custom:User", 5 | "scim2.max_bulk_operations": "1000", 6 | "scim2.max_bulk_payload": "1048576", 7 | "scim2.documentation_uri": "https://is.docs.wso2.com/en/latest/apis/scim2/", 8 | "scim2.oauth_bearer.primary": true, 9 | "scim2.http_basic.primary": false, 10 | "scim2.basic_auth_documentation_uri": "$ref{scim2.documentation_uri}", 11 | "scim2.enable_list_user_schemas": true 12 | } 13 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | **Description:** 2 | 3 | 4 | **Suggested Labels:** 5 | 6 | 7 | **Suggested Assignees:** 8 | 9 | 10 | **Affected Product Version:** 11 | 12 | **OS, DB, other environment details and versions:** 13 | 14 | **Steps to reproduce:** 15 | 16 | 17 | **Related Issues:** 18 | -------------------------------------------------------------------------------- /.connector-store/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SCIM 2.0 Inbound Provisioning Connector", 3 | "owner": "WSO2", 4 | "category": "OAuth Client Authenticator", 5 | "documentationUrl": "https://docs.wso2.com/display/ISCONNECTORS/Configuring+SCIM+2.0+Provisioning+Connector", 6 | "description": "The System for Cross-domain Identity Management (SCIM) specification is designed to make managing user identities in cloud-based applications and services easier. SCIM 2.0 Inbound Provisioning Connector enables you to leverage the Identity Server as an SCIM 2.0 Provider to achieve seamless identity inbound provisioning.", 7 | "status": "Active", 8 | "labels": [ 9 | "inbound-provisioning", 10 | "IS-5.3.0", 11 | "scim2" 12 | ], 13 | "releases": [ 14 | { 15 | "tagName": "1.1.19", 16 | "products": [ 17 | "IS 5.3.0" 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/on-pr-merge.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run when a PR is merged and save the PR information for later use. 2 | 3 | name: 💡 PR Merged 4 | 5 | on: 6 | pull_request: 7 | types: [closed] 8 | branches: [master, main] 9 | 10 | jobs: 11 | save-pr-information: 12 | runs-on: ubuntu-latest 13 | if: github.event.pull_request.merged == true 14 | steps: 15 | - name: ⬇️ Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: ℹ️ Display PR Information 19 | run: echo "PR Number \#${{github.event.number}}" 20 | 21 | - name: 💾 Save PR Number for Later Use 22 | run: echo "${{github.event.number}}" > PR_NUMBER 23 | 24 | - name: 📦 Upload PR Number as Artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: pr-number 28 | path: PR_NUMBER 29 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/webapp/META-INF/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/resources/META-INF/component.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | Identity Provisioning 23 | /permission/admin/configure/security/usermgt/provisioning 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/jaxrs/designator/PATCH.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.wso2.carbon.identity.jaxrs.designator; 18 | 19 | import javax.ws.rs.HttpMethod; 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Creates the PATCH HTTP method, since JAX-RS does not support it natively yet 27 | */ 28 | @Target({ElementType.METHOD}) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @HttpMethod("PATCH") 31 | public @interface PATCH { 32 | } 33 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/exceptions/IdentitySCIMException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.exceptions; 20 | 21 | import org.wso2.carbon.identity.base.IdentityException; 22 | 23 | public class IdentitySCIMException extends IdentityException { 24 | 25 | private static final long serialVersionUID = 3477076930789578976L; 26 | 27 | public IdentitySCIMException(String error) { 28 | super(error); 29 | } 30 | 31 | public IdentitySCIMException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome to the WSO2 Identity Server (IS) identity-inbound-provisioning-scim2. 2 | 3 | WSO2 IS is one of the best Identity Servers, which enables you to offload your identity and user entitlement management burden totally from your application. It comes with many features, supports many industry standards and most importantly it allows you to extent it according to your security requirements. This repo contains Authenticators written to work with different third party systems. 4 | 5 | With WSO2 IS, there are lot of provisioning capabilities available. There are 3 major concepts as Inbound, outbound provisioning and Just-In-Time provisioning. Inbound provisioning means , provisioning users and groups from an external system to IS. Outbound provisioning means , provisioning users from IS to other external systems. JIT provisioning means , once a user tries to login from an external IDP, a user can be created on the fly in IS with JIT. Repos under this account holds such components invlove in communicating with external systems. 6 | 7 | ## Building from the source 8 | 9 | If you want to build **identity-inbound-provisioning-scim2** from the source code: 10 | 11 | 1. Install Java 11 (or Java 17) 12 | 2. Install Apache Maven 3.x.x (https://maven.apache.org/download.cgi#) 13 | 3. Get a clone or download the source from this repository (https://github.com/wso2-extensions/identity-inbound-provisioning-scim2) 14 | 4. Run the Maven command ``mvn clean install`` from the ``identity-inbound-provisioning-scim2`` directory. -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMAgentAttributeSchemaCacheEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.wso2.charon3.core.schema.AttributeSchema; 22 | 23 | import java.io.Serializable; 24 | 25 | /** 26 | * This stores list of custom attributes of SCIM2 agent schema. 27 | */ 28 | public class SCIMAgentAttributeSchemaCacheEntry implements Serializable { 29 | 30 | private static final long serialVersionUID = 3784848233717914595L; 31 | 32 | private final AttributeSchema attributeSchema; 33 | 34 | public SCIMAgentAttributeSchemaCacheEntry(AttributeSchema attributeSchema) { 35 | 36 | this.attributeSchema = attributeSchema; 37 | } 38 | 39 | public AttributeSchema getSCIMAgentAttributeSchema() { 40 | 41 | return attributeSchema; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMCustomAttributeSchemaCacheEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.wso2.charon3.core.schema.AttributeSchema; 22 | 23 | import java.io.Serializable; 24 | 25 | /** 26 | * This stores list of custom attributes of SCIM2 custom schema. 27 | */ 28 | public class SCIMCustomAttributeSchemaCacheEntry implements Serializable { 29 | 30 | private static final long serialVersionUID = -3352517105334401998L; 31 | 32 | private final AttributeSchema attributeSchema; 33 | 34 | public SCIMCustomAttributeSchemaCacheEntry(AttributeSchema attributeSchema) { 35 | 36 | this.attributeSchema = attributeSchema; 37 | } 38 | 39 | public AttributeSchema getSCIMCustomAttributeSchema() { 40 | 41 | return attributeSchema; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMSystemAttributeSchemaCacheEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.wso2.charon3.core.schema.AttributeSchema; 22 | 23 | import java.io.Serializable; 24 | 25 | /** 26 | * This stores list of custom attributes of SCIM2 system schema. 27 | */ 28 | public class SCIMSystemAttributeSchemaCacheEntry implements Serializable { 29 | 30 | private static final long serialVersionUID = 3784848233717914594L; 31 | 32 | private final AttributeSchema attributeSchema; 33 | 34 | public SCIMSystemAttributeSchemaCacheEntry(AttributeSchema attributeSchema) { 35 | 36 | this.attributeSchema = attributeSchema; 37 | } 38 | 39 | public AttributeSchema getSCIMSystemAttributeSchema() { 40 | 41 | return attributeSchema; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/webapp/META-INF/webapp-classloading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 23 | 24 | 25 | 26 | false 27 | 28 | 33 | CXF3,Carbon 34 | 35 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/extenstion/SCIMUserStoreException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.extenstion; 20 | 21 | import org.wso2.carbon.identity.base.IdentityException; 22 | 23 | /** 24 | * This exception is used in the SCIM User Store Error Resolver extension point, to return any internal errors to 25 | * the SCIM API layer. Since SCIM API returns only an error message (detail) and http error code, this 26 | * exception is designed to accept only those two. 27 | */ 28 | public class SCIMUserStoreException extends IdentityException { 29 | 30 | private static final long serialVersionUID = 3477076930782578976L; 31 | private final int httpStatusCode; 32 | 33 | public SCIMUserStoreException(String errorMessage, int httpStatusCode) { 34 | 35 | super(errorMessage); 36 | this.httpStatusCode = httpStatusCode; 37 | } 38 | 39 | public int getHttpStatusCode() { 40 | 41 | return httpStatusCode; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/pr-builder.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build the project on pull requests with tests 2 | # Uses: 3 | # OS: ubuntu-latest 4 | # JDK: Temurin JDK 11 and Adopt JDK 17 5 | 6 | name: PR Builder 7 | 8 | on: 9 | pull_request: 10 | branches: [main, master] 11 | workflow_dispatch: 12 | 13 | env: 14 | MAVEN_OPTS: -Xmx4g -Xms1g 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | env: 21 | JAVA_TOOL_OPTIONS: "-Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true" 22 | 23 | strategy: 24 | matrix: 25 | java-version: [ 11, 17 ] 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Set up Temurin JDK 11 and 17 30 | uses: actions/setup-java@v4 31 | with: 32 | java-version: ${{ matrix.java-version }} 33 | distribution: "temurin" 34 | - name: Cache local Maven repository 35 | id: cache-maven-m2 36 | uses: actions/cache@v4 37 | env: 38 | cache-name: cache-m2 39 | with: 40 | path: ~/.m2/repository 41 | key: ${{ runner.os }}-maven-${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }} 42 | restore-keys: | 43 | ${{ runner.os }}-maven-${{ env.cache-name }}- 44 | ${{ runner.os }}-maven- 45 | ${{ runner.os }}- 46 | - name: Build with Maven 47 | run: mvn clean install -U -B 48 | 49 | - name: Generate coverage report 50 | run: mvn test jacoco:report 51 | 52 | - name: Upload coverage reports to Codecov 53 | uses: codecov/codecov-action@v4 54 | with: 55 | token: ${{ secrets.CODECOV_TOKEN }} 56 | files : target/site/jacoco/jacoco.xml 57 | flags: unit 58 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMAgentAttributeSchemaCacheKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * SCIM Agent Schema Cache key. This contains tenant ID as the key. 25 | */ 26 | public class SCIMAgentAttributeSchemaCacheKey implements Serializable { 27 | 28 | private static final long serialVersionUID = -6137657709191460467L; 29 | 30 | private final int tenantId; 31 | 32 | public SCIMAgentAttributeSchemaCacheKey(int tenantId) { 33 | 34 | this.tenantId = tenantId; 35 | } 36 | 37 | public int getTenantId() { 38 | 39 | return tenantId; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | 45 | if (this == o) { 46 | return true; 47 | } 48 | 49 | if (!(o instanceof SCIMAgentAttributeSchemaCacheKey)) { 50 | return false; 51 | } 52 | 53 | SCIMAgentAttributeSchemaCacheKey that = (SCIMAgentAttributeSchemaCacheKey) o; 54 | return tenantId == that.tenantId; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | 60 | return tenantId; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMCustomAttributeSchemaCacheKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * SCIM Custom Schema Cache key. This contains tenant Id as the key. 25 | */ 26 | public class SCIMCustomAttributeSchemaCacheKey implements Serializable { 27 | 28 | private static final long serialVersionUID = -1332814776225574523L; 29 | 30 | private final int tenantId; 31 | 32 | public SCIMCustomAttributeSchemaCacheKey(int tenantId) { 33 | 34 | this.tenantId = tenantId; 35 | } 36 | 37 | public int getTenantId() { 38 | 39 | return tenantId; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | 45 | if (this == o) { 46 | return true; 47 | } 48 | 49 | if (!(o instanceof SCIMCustomAttributeSchemaCacheKey)) { 50 | return false; 51 | } 52 | 53 | SCIMCustomAttributeSchemaCacheKey that = (SCIMCustomAttributeSchemaCacheKey) o; 54 | return tenantId == that.tenantId; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | return tenantId; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMSystemAttributeSchemaCacheKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * SCIM System Schema Cache key. This contains tenant ID as the key. 25 | */ 26 | public class SCIMSystemAttributeSchemaCacheKey implements Serializable { 27 | 28 | private static final long serialVersionUID = -6137657709191460466L; 29 | 30 | private final int tenantId; 31 | 32 | public SCIMSystemAttributeSchemaCacheKey(int tenantId) { 33 | 34 | this.tenantId = tenantId; 35 | } 36 | 37 | public int getTenantId() { 38 | 39 | return tenantId; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | 45 | if (this == o) { 46 | return true; 47 | } 48 | 49 | if (!(o instanceof SCIMSystemAttributeSchemaCacheKey)) { 50 | return false; 51 | } 52 | 53 | SCIMSystemAttributeSchemaCacheKey that = (SCIMSystemAttributeSchemaCacheKey) o; 54 | return tenantId == that.tenantId; 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | return tenantId; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/resources/ServiceProviderConfigResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | 20 | package org.wso2.carbon.identity.scim2.provider.resources; 21 | 22 | import org.wso2.carbon.identity.scim2.provider.util.SupportUtils; 23 | import org.wso2.charon3.core.protocol.SCIMResponse; 24 | import org.wso2.charon3.core.protocol.endpoints.ServiceProviderConfigResourceManager; 25 | 26 | import javax.ws.rs.*; 27 | import javax.ws.rs.core.MediaType; 28 | import javax.ws.rs.core.Response; 29 | 30 | @Path("/") 31 | public class ServiceProviderConfigResource extends AbstractResource { 32 | @GET 33 | @Produces(MediaType.APPLICATION_JSON) 34 | public Response getUser() { 35 | // create charon-SCIM service provider config endpoint and hand-over the request. 36 | ServiceProviderConfigResourceManager serviceProviderConfigResourceManager = 37 | new ServiceProviderConfigResourceManager(); 38 | 39 | SCIMResponse scimResponse = serviceProviderConfigResourceManager.get(null, null, null, null); 40 | // needs to check the code of the response and return 200 0k or other error codes 41 | // appropriately. 42 | return SupportUtils.buildResponse(scimResponse); 43 | } 44 | } -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/extenstion/SCIMUserStoreErrorResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.extenstion; 20 | 21 | import org.wso2.carbon.user.api.UserStoreException; 22 | 23 | /** 24 | * This extension point can be used to define how internal errors should be mapped to relevant API errors. 25 | */ 26 | public interface SCIMUserStoreErrorResolver { 27 | 28 | /** 29 | * Resolve a given user store exception to a proper Charon Exception with status code. implementation should 30 | * return null if the implementing class does not know or does not wish to translate the exception, so that 31 | * any other translator can get chance to do the resolving. The default resolver will resolve an exception 32 | * ultimately if no custom resolver resolves it. 33 | * 34 | * @param e User store exception thrown. 35 | * @return Resolved charon exception with proper http status code, NULL if the impl doesn't know how to resolve. 36 | */ 37 | SCIMUserStoreException resolve(UserStoreException e); 38 | 39 | /** 40 | * Provide an order value for the implementation. Should be a positive integer. 41 | * implementation with the highest order get picked first. 42 | * 43 | * @return Order of the impl. 44 | */ 45 | int getOrder(); 46 | } 47 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/resources/ResourceTypesResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.resources; 20 | 21 | import org.wso2.carbon.identity.scim2.common.impl.IdentityResourceTypeResourceManager; 22 | import org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants; 23 | import org.wso2.carbon.identity.scim2.provider.util.SupportUtils; 24 | import org.wso2.charon3.core.protocol.SCIMResponse; 25 | import org.wso2.charon3.core.protocol.endpoints.ResourceTypeResourceManager; 26 | 27 | import javax.ws.rs.GET; 28 | import javax.ws.rs.Path; 29 | import javax.ws.rs.Produces; 30 | import javax.ws.rs.core.MediaType; 31 | import javax.ws.rs.core.Response; 32 | 33 | @Path("/") 34 | public class ResourceTypesResource extends AbstractResource { 35 | @GET 36 | @Produces({MediaType.APPLICATION_JSON, SCIMProviderConstants.APPLICATION_SCIM_JSON}) 37 | public Response getUser() { 38 | // create charon-SCIM service provider config endpoint and hand-over the request. 39 | IdentityResourceTypeResourceManager resourceTypeResourceManager = new IdentityResourceTypeResourceManager(); 40 | 41 | SCIMResponse scimResponse = resourceTypeResourceManager.get(null, null, null, null); 42 | // needs to check the code of the response and return 200 0k or other error codes 43 | // appropriately. 44 | return SupportUtils.buildResponse(scimResponse); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/impl/ApplicationInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.impl; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.scim2.common.impl.IdentitySCIMManager; 24 | import org.wso2.charon3.core.exceptions.CharonException; 25 | 26 | import javax.servlet.ServletContextEvent; 27 | import javax.servlet.ServletContextListener; 28 | 29 | /** 30 | * This performs one-time initialization tasks at the application startup. 31 | */ 32 | public class ApplicationInitializer implements ServletContextListener { 33 | 34 | private static final Log logger = LogFactory.getLog(ApplicationInitializer.class); 35 | 36 | @Override 37 | public void contextInitialized(ServletContextEvent servletContextEvent) { 38 | if (logger.isDebugEnabled()) { 39 | logger.debug("Initializing SCIM Webapp..."); 40 | } 41 | try { 42 | //initialize identity scim manager 43 | IdentitySCIMManager.getInstance(); 44 | 45 | } catch (CharonException e) { 46 | logger.error("Error in initializing the IdentitySCIMManager at the initialization of " + 47 | "SCIM webapp", e); 48 | } 49 | } 50 | 51 | @Override 52 | public void contextDestroyed(ServletContextEvent servletContextEvent) { 53 | // Do nothing 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/resources/p2.inf: -------------------------------------------------------------------------------- 1 | instructions.configure = \ 2 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository); \ 3 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/conf); \ 4 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/conf/identity); \ 5 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.common_${feature.version}/charon-config.xml,target:${installFolder}/../../conf/identity/charon-config.xml,overwrite:true); \ 6 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.common_${feature.version}/scim2-schema-extension.config,target:${installFolder}/../../conf/identity/scim2-schema-extension.config,overwrite:true); \ 7 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources); \ 8 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources/conf); \ 9 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources/conf/templates); \ 10 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources/conf/templates/repository); \ 11 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources/conf/templates/repository/conf); \ 12 | org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../repository/resources/conf/templates/repository/conf/identity); \ 13 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.common_${feature.version}/charon-config.xml.j2,target:${installFolder}/../../resources/conf/templates/repository/conf/identity/charon-config.xml.j2,overwrite:true);\ 14 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.common_${feature.version}/org.wso2.carbon.identity.scim2.common.feature.default.json,target:${installFolder}/../../resources/conf/org.wso2.carbon.identity.scim2.common.feature.default.json,overwrite:true);\ 15 | org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.scim2.common_${feature.version}/org.wso2.carbon.identity.scim2.common.feature.infer.json,target:${installFolder}/../../resources/conf/org.wso2.carbon.identity.scim2.common.feature.infer.json,overwrite:true);\ 16 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceURLBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.impl; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.core.ServiceURLBuilder; 24 | import org.wso2.carbon.identity.core.URLBuilderException; 25 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 26 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants; 27 | import org.wso2.charon3.core.exceptions.NotFoundException; 28 | import org.wso2.charon3.core.protocol.endpoints.DefaultResourceURLBuilder; 29 | 30 | /** 31 | * Class responsible for constructing SCIM2 resource endpoints with tenant context. 32 | */ 33 | public class IdentityResourceURLBuilder extends DefaultResourceURLBuilder { 34 | 35 | private static final Log log = LogFactory.getLog(IdentityResourceURLBuilder.class); 36 | 37 | @Override 38 | public String build(String resource) throws NotFoundException { 39 | 40 | if (IdentityTenantUtil.isTenantQualifiedUrlsEnabled()) { 41 | try { 42 | String scimURL = ServiceURLBuilder.create().addPath(SCIMCommonConstants.SCIM2_ENDPOINT).build() 43 | .getAbsolutePublicURL(); 44 | return scimURL + resource; 45 | } catch (URLBuilderException e) { 46 | if (log.isDebugEnabled()) { 47 | log.debug("Error occurred while building the SCIM2 endpoint with tenant " + 48 | "qualified URL.", e); 49 | } 50 | // Fallback to super class build method during error scenarios. 51 | return super.build(resource); 52 | } 53 | } else { 54 | return super.build(resource); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/resources/charon-config-test.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | true 21 | true 22 | http://example.com/help/scim.html 23 | true 24 | 1000 25 | 1048576 26 | true 27 | 200 28 | true 29 | false 30 | false 31 | 100 32 | 33 | 34 | OAuth Bearer Token 35 | Authentication scheme using the OAuth Bearer Token Standard 36 | http://www.rfc-editor.org/info/rfc6750 37 | http://example.com/help/oauth.html 38 | oauthbearertoken 39 | true 40 | 41 | 42 | HTTP Basic 43 | Authentication scheme using the HTTP Basic Standard 44 | http://www.rfc-editor.org/info/rfc2617 45 | http://example.com/help/httpBasic.html 46 | httpbasic 47 | false 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/test/utils/CommonTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.test.utils; 20 | 21 | 22 | import org.wso2.carbon.base.CarbonBaseConstants; 23 | import org.wso2.carbon.base.MultitenantConstants; 24 | import org.wso2.carbon.context.PrivilegedCarbonContext; 25 | 26 | import java.nio.file.Paths; 27 | 28 | public class CommonTestUtils { 29 | 30 | private CommonTestUtils() { 31 | } 32 | 33 | public static void initPrivilegedCarbonContext(String tenantDomain, int tenantID, String userName) throws Exception { 34 | String carbonHome = Paths.get(System.getProperty("user.dir"), "target").toString(); 35 | System.setProperty(CarbonBaseConstants.CARBON_HOME, carbonHome); 36 | PrivilegedCarbonContext.startTenantFlow(); 37 | PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain); 38 | PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantID); 39 | PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(userName); 40 | } 41 | 42 | public static void initPrivilegedCarbonContext(String tenantDomain, String userName) throws Exception { 43 | int tenantID = MultitenantConstants.SUPER_TENANT_ID; 44 | initPrivilegedCarbonContext(tenantDomain, tenantID, userName); 45 | } 46 | 47 | public static void initPrivilegedCarbonContext(String tenantDomain) throws Exception { 48 | int tenantID = MultitenantConstants.SUPER_TENANT_ID; 49 | String userName = "testUser"; 50 | 51 | initPrivilegedCarbonContext(tenantDomain, tenantID, userName); 52 | } 53 | 54 | public static void initPrivilegedCarbonContext() throws Exception { 55 | String tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; 56 | int tenantID = MultitenantConstants.SUPER_TENANT_ID; 57 | String userName = "testUser"; 58 | 59 | initPrivilegedCarbonContext(tenantDomain, tenantID, userName); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | > Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. 3 | 4 | ## Goals 5 | > Describe the solutions that this feature/fix will introduce to resolve the problems described above 6 | 7 | ## Approach 8 | > Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. 9 | 10 | ## User stories 11 | > Summary of user stories addressed by this change> 12 | 13 | ## Developer Checklist (Mandatory) 14 | - [ ] Complete the **Developer Checklist** in the related `product-is` issue to track any behavioral change or migration impact. 15 | 16 | ## Release note 17 | > Brief description of the new feature or bug fix as it will appear in the release notes 18 | 19 | ## Documentation 20 | > Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact 21 | 22 | ## Training 23 | > Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable 24 | 25 | ## Certification 26 | > Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. 27 | 28 | ## Marketing 29 | > Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable 30 | 31 | ## Automation tests 32 | - Unit tests 33 | > Code coverage information 34 | - Integration tests 35 | > Details about the test cases and coverage 36 | 37 | ## Security checks 38 | - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no 39 | - Ran FindSecurityBugs plugin and verified report? yes/no 40 | - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no 41 | 42 | ## Samples 43 | > Provide high-level details about the samples related to this feature 44 | 45 | ## Related PRs 46 | > List any other related PRs 47 | 48 | ## Migrations (if applicable) 49 | > Describe migration steps and platforms on which migration has been tested 50 | 51 | ## Test environment 52 | > List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested 53 | 54 | ## Learning 55 | > Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem. -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | true 21 | true 22 | urn:scim:schemas:extension:custom:User 23 | true 24 | http://example.com/help/scim.html 25 | true 26 | 1000 27 | 1048576 28 | true 29 | 200 30 | true 31 | false 32 | false 33 | 100 34 | true 35 | 36 | 37 | OAuth Bearer Token 38 | Authentication scheme using the OAuth Bearer Token Standard 39 | http://www.rfc-editor.org/info/rfc6750 40 | http://example.com/help/oauth.html 41 | oauthbearertoken 42 | true 43 | 44 | 45 | HTTP Basic 46 | Authentication scheme using the HTTP Basic Standard 47 | http://www.rfc-editor.org/info/rfc2617 48 | http://example.com/help/httpBasic.html 49 | httpbasic 50 | false 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/resources/testng.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/resources/charon-config.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | {{scim2.enable_schema_extension}} 22 | {{scim2.enable_custom_schema_extension}} 23 | {{scim2.custom_user_schema_uri}} 24 | {{scim2.max_bulk_operations}} 25 | {{scim2.max_bulk_payload}} 26 | {{scim2.documentation_uri}} 27 | true 28 | true 29 | true 30 | 200 31 | true 32 | false 33 | false 34 | 100 35 | scim2.enable_list_user_schemas 36 | 37 | 38 | OAuth Bearer Token 39 | Authentication scheme using the OAuth Bearer Token Standard 40 | http://www.rfc-editor.org/info/rfc6750 41 | {{scim2.basic_auth_documentation_uri}} 42 | oauthbearertoken 43 | {{scim2.oauth_bearer.primary}} 44 | 45 | 46 | HTTP Basic 47 | Authentication scheme using the HTTP Basic Standard 48 | http://www.rfc-editor.org/info/rfc2617 49 | {{scim2.oauth_bearer_auth_documentation_uri}} 50 | httpbasic 51 | {{scim2.http_basic.primary}} 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/AuthenticationSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.utils; 20 | 21 | import java.util.Map; 22 | 23 | /** 24 | * this class is the blue print of authentication schemas used in ServiceProvidesConfig. 25 | */ 26 | public class AuthenticationSchema { 27 | 28 | private String name; 29 | private String description; 30 | private String specUri; 31 | private String documentationUri; 32 | private String type; 33 | private String primary; 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | 43 | public String getDescription() { 44 | return description; 45 | } 46 | 47 | public void setDescription(String description) { 48 | this.description = description; 49 | } 50 | 51 | public String getSpecUri() { 52 | return specUri; 53 | } 54 | 55 | public void setSpecUri(String specUri) { 56 | this.specUri = specUri; 57 | } 58 | 59 | public String getDocumentationUri() { 60 | return documentationUri; 61 | } 62 | 63 | public void setDocumentationUri(String documentationUri) { 64 | this.documentationUri = documentationUri; 65 | } 66 | 67 | public String getType() { 68 | return type; 69 | } 70 | 71 | public void setType(String type) { 72 | this.type = type; 73 | } 74 | 75 | public String getPrimary() { 76 | return primary; 77 | } 78 | 79 | public void setPrimary(String primary) { 80 | this.primary = primary; 81 | } 82 | 83 | public void setProperties(Map properties) { 84 | for (Map.Entry property : properties.entrySet()) { 85 | if (property.getKey().equals("name")) { 86 | setName(property.getValue()); 87 | } else if (property.getKey().equals("description")) { 88 | setDescription(property.getValue()); 89 | } else if (property.getKey().equals("specUri")) { 90 | setSpecUri(property.getValue()); 91 | } else if (property.getKey().equals("documentationUri")) { 92 | setDocumentationUri(property.getValue()); 93 | } else if (property.getKey().equals("type")) { 94 | setType(property.getValue()); 95 | } else if (property.getKey().equals("primary")) { 96 | setPrimary(property.getValue()); 97 | } 98 | } 99 | } 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/util/SCIMProviderConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.util; 20 | 21 | public class SCIMProviderConstants { 22 | 23 | public static final String AUTHORIZATION = "Authorization"; 24 | public static final String CONTENT_TYPE = "Content-Type"; 25 | public static final String ATTRIBUTES = "attributes"; 26 | public static final String EXCLUDE_ATTRIBUTES = "excludedAttributes"; 27 | public static final String FILTER = "filter"; 28 | public static final String START_INDEX = "startIndex"; 29 | public static final String COUNT = "count"; 30 | public static final String SORT_BY = "sortBy"; 31 | public static final String SORT_ORDER = "sortOder"; 32 | public static final String SCIM_VERSION = "scimVersion"; 33 | public static final String SCIM_VERSION_V3 = "v3"; 34 | public static final String APPLICATION_SCIM_JSON = "application/scim+json"; 35 | public static final String APPLICATION__JSON = "application/json"; 36 | public static final String APPLICATION_ALL = "application/*"; 37 | public static final String CHARSET_UTF8= "charset=utf-8"; 38 | public static final String SEMI_COLON = ";"; 39 | public static final String CHARSET= "charset"; 40 | public static final String ACCEPT_HEADER = "Accept"; 41 | public static final String ID = "id"; 42 | public static final String DOMAIN = "domain"; 43 | public static final String GROUPS = "groups"; 44 | public static final String USERS = "users"; 45 | 46 | public static final String RESOURCE_STRING = "RESOURCE_STRING"; 47 | public static final String HTTP_VERB = "HTTP_VERB"; 48 | public static final String SEARCH = ".search"; 49 | public static final String DEFAULT_USERNAME = "admin"; 50 | public static final String ADD = "add"; 51 | public static final String OPERATIONS = "Operations"; 52 | public static final String OP = "op"; 53 | public static final String PATH = "path"; 54 | public static final String REMOVE = "remove"; 55 | public static final String REPLACE = "replace"; 56 | public static final String VALUE_EQ = "value eq"; 57 | 58 | public static final String BULK_CREATE_ROLE_OPERATION_NAME = "createRole"; 59 | public static final String BULK_UPDATE_ROLE_OPERATION_NAME = "updateRole"; 60 | public static final String BULK_DELETE_ROLE_OPERATION_NAME = "deleteRole"; 61 | 62 | public static final String SKIP_ENFORCE_ROLE_OPERATION_PERMISSION = "RoleMgt.SkipEnforceRoleOperationPermission"; 63 | 64 | /* 65 | * This class contains constants related to SCIM Role operations. 66 | */ 67 | public static class RoleV2Operations { 68 | 69 | public static final String UPDATE_ROLE_PERMISSIONS = "updateRolePermissions"; 70 | public static final String UPDATE_ROLE_MEMBERS = "updateRoleMembers"; 71 | public static final String UPDATE_ROLE_GROUPS = "updateRoleGroups"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/resources/SchemaResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.resources; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 24 | import org.wso2.carbon.identity.scim2.common.impl.IdentitySCIMManager; 25 | import org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants; 26 | import org.wso2.carbon.identity.scim2.provider.util.SupportUtils; 27 | import org.wso2.charon3.core.exceptions.CharonException; 28 | import org.wso2.charon3.core.extensions.UserManager; 29 | import org.wso2.charon3.core.protocol.SCIMResponse; 30 | import org.wso2.charon3.core.protocol.endpoints.SchemaResourceManager; 31 | import org.wso2.charon3.core.schema.SCIMConstants; 32 | 33 | import javax.ws.rs.GET; 34 | import javax.ws.rs.Path; 35 | import javax.ws.rs.PathParam; 36 | import javax.ws.rs.Produces; 37 | import javax.ws.rs.core.MediaType; 38 | import javax.ws.rs.core.Response; 39 | 40 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils.getTenantDomainFromContext; 41 | 42 | public class SchemaResource extends AbstractResource { 43 | 44 | private static final Log logger = LogFactory.getLog(SchemaResource.class); 45 | 46 | @GET 47 | @Produces(MediaType.APPLICATION_JSON) 48 | public Response getSchemas() { 49 | 50 | try { 51 | UserManager userManager = IdentitySCIMManager.getInstance().getUserManager(); 52 | 53 | // create charon-SCIM schemas endpoint and hand-over the request. 54 | SchemaResourceManager schemaResourceManager = new SchemaResourceManager(); 55 | SCIMResponse scimResponse = schemaResourceManager.get(null, userManager, null, null); 56 | 57 | return SupportUtils.buildResponse(scimResponse); 58 | 59 | } catch (CharonException e) { 60 | return handleCharonException(e); 61 | } 62 | } 63 | 64 | @GET 65 | @Path("/{id}") 66 | @Produces({MediaType.APPLICATION_JSON, SCIMProviderConstants.APPLICATION_SCIM_JSON}) 67 | public Response getSchemasById(@PathParam(SCIMConstants.CommonSchemaConstants.ID) String id) { 68 | 69 | try { 70 | UserManager userManager = IdentitySCIMManager.getInstance().getUserManager(); 71 | 72 | if (IdentityTenantUtil.isTenantQualifiedUrlsEnabled()) { 73 | String tenantDomain = getTenantDomainFromContext(); 74 | } 75 | 76 | // create charon-SCIM schemas endpoint and hand-over the request. 77 | SchemaResourceManager schemaResourceManager = new SchemaResourceManager(); 78 | SCIMResponse scimResponse = schemaResourceManager.get(id, userManager, null, null); 79 | 80 | return SupportUtils.buildResponse(scimResponse); 81 | } catch (CharonException e) { 82 | return handleCharonException(e); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/DefaultSCIMUserStoreErrorResolverTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.impl; 20 | 21 | import org.apache.http.HttpStatus; 22 | import org.testng.annotations.DataProvider; 23 | import org.testng.annotations.Test; 24 | import org.wso2.carbon.identity.scim2.common.extenstion.SCIMUserStoreException; 25 | import org.wso2.carbon.user.api.UserStoreException; 26 | 27 | import static org.testng.Assert.assertEquals; 28 | import static org.testng.Assert.assertNull; 29 | 30 | /** 31 | * Contains the unit test cases for DefaultSCIMUserStoreErrorResolver. 32 | */ 33 | public class DefaultSCIMUserStoreErrorResolverTest { 34 | 35 | @Test 36 | public void testGetOrder() { 37 | 38 | DefaultSCIMUserStoreErrorResolver defaultSCIMUserStoreErrorResolver = new DefaultSCIMUserStoreErrorResolver(); 39 | assertEquals(defaultSCIMUserStoreErrorResolver.getOrder(), 0); 40 | } 41 | 42 | @DataProvider(name = "dataProviderForResolveUserNameMandatory") 43 | public Object[][] dataProviderForResolveUserNameMandatory() { 44 | 45 | return new Object[][]{ 46 | {new UserStoreException("error: 30007"), HttpStatus.SC_NOT_FOUND}, 47 | {new org.wso2.carbon.user.core.UserStoreException("error", "32102"), HttpStatus.SC_BAD_REQUEST}, 48 | {new org.wso2.carbon.user.core.UserStoreClientException("error", "32103"), HttpStatus.SC_BAD_REQUEST}, 49 | {new org.wso2.carbon.user.core.UserStoreClientException("error", "321xx"), HttpStatus.SC_BAD_REQUEST}, 50 | {new org.wso2.carbon.user.core.UserStoreClientException("30012 - error", "xxx"), HttpStatus.SC_CONFLICT}, 51 | {new org.wso2.carbon.user.core.UserStoreClientException("error", "65019"), HttpStatus.SC_CONFLICT}, 52 | {new org.wso2.carbon.user.core.UserStoreClientException("error"), HttpStatus.SC_BAD_REQUEST}, 53 | }; 54 | } 55 | 56 | @Test(dataProvider = "dataProviderForResolveUserNameMandatory") 57 | public void testResolveHappyPath(Object userStoreException, int expected) { 58 | 59 | DefaultSCIMUserStoreErrorResolver defaultSCIMUserStoreErrorResolver = new DefaultSCIMUserStoreErrorResolver(); 60 | SCIMUserStoreException scimUserStoreException = defaultSCIMUserStoreErrorResolver. 61 | resolve((UserStoreException) userStoreException); 62 | assertEquals(scimUserStoreException.getHttpStatusCode(), expected); 63 | } 64 | 65 | @DataProvider(name = "dataProviderForResolveUnHappyPath") 66 | public Object[][] dataProviderForResolveUnHappyPath() { 67 | 68 | return new Object[][]{ 69 | {new UserStoreException("error: 30008")}, 70 | {new org.wso2.carbon.user.core.UserStoreException("error", "32103")} 71 | }; 72 | } 73 | 74 | @Test(dataProvider = "dataProviderForResolveUnHappyPath") 75 | public void testResolveUnHappyPath(Object userStoreException) { 76 | 77 | DefaultSCIMUserStoreErrorResolver defaultSCIMUserStoreErrorResolver = new DefaultSCIMUserStoreErrorResolver(); 78 | SCIMUserStoreException scimUserStoreException = defaultSCIMUserStoreErrorResolver. 79 | resolve((UserStoreException) userStoreException); 80 | assertNull(scimUserStoreException); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/listener/SCIMTenantMgtListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.listener; 20 | 21 | import org.apache.commons.lang.StringUtils; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.wso2.carbon.identity.core.AbstractIdentityTenantMgtListener; 25 | import org.wso2.carbon.identity.organization.management.service.OrganizationManager; 26 | import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; 27 | import org.wso2.carbon.identity.organization.management.service.util.Utils; 28 | import org.wso2.carbon.identity.scim2.common.internal.component.SCIMCommonComponentHolder; 29 | import org.wso2.carbon.identity.scim2.common.utils.AdminAttributeUtil; 30 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 31 | import org.wso2.carbon.stratos.common.exception.StratosException; 32 | import org.wso2.carbon.user.api.Tenant; 33 | import org.wso2.carbon.user.api.UserStoreException; 34 | import org.wso2.carbon.user.core.service.RealmService; 35 | 36 | /** 37 | * Tenant activation listener for SCIM component to do the task when the tenant get create. 38 | * 39 | */ 40 | public class SCIMTenantMgtListener extends AbstractIdentityTenantMgtListener { 41 | 42 | private static final Log log = LogFactory.getLog(SCIMTenantMgtListener.class); 43 | @Override 44 | public void onTenantInitialActivation(int tenantId) throws StratosException { 45 | 46 | boolean isEnabled = isEnable(); 47 | if (!isEnabled) { 48 | if (log.isDebugEnabled()) { 49 | log.debug("SCIMTenantMgtListener is disabled"); 50 | } 51 | return; 52 | } 53 | RealmService realmService = SCIMCommonComponentHolder.getRealmService(); 54 | try { 55 | Tenant tenant = realmService.getTenantManager().getTenant(tenantId); 56 | /* 57 | If the tenant has an associated organization id, and if the org id satisfies isOrganization() check, that 58 | organization creator is not inside the same organization. No need to update such admin claims. 59 | */ 60 | String organizationID = tenant.getAssociatedOrganizationUUID(); 61 | if (StringUtils.isNotBlank(organizationID)) { 62 | OrganizationManager organizationManager = SCIMCommonComponentHolder.getOrganizationManager(); 63 | int organizationDepth = organizationManager.getOrganizationDepthInHierarchy(organizationID); 64 | if (organizationDepth >= Utils.getSubOrgStartLevel()) { 65 | return; 66 | } 67 | } 68 | if (log.isDebugEnabled()) { 69 | log.debug("SCIMTenantMgtListener is fired for Tenant ID : " + tenantId); 70 | } 71 | // Update admin user attributes. 72 | AdminAttributeUtil.updateAdminUser(tenantId, false); 73 | // Update admin group attributes. 74 | AdminAttributeUtil.updateAdminGroup(tenantId); 75 | // Update meta data of everyone role. 76 | SCIMCommonUtils.updateEveryOneRoleV2MetaData(tenantId); 77 | } catch (UserStoreException | OrganizationManagementException e) { 78 | log.error(e); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/DAO/SQLQueries.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.DAO; 20 | 21 | /** 22 | * SQL Queries for SCIM_IDENTITY_TABLE which persists SCIM_GROUP info. 23 | */ 24 | public class SQLQueries { 25 | 26 | public static final String LIST_SCIM_GROUPS_SQL = 27 | "SELECT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.ATTR_NAME = ?"; 28 | public static final String LIST_SCIM_GROUPS_BY_TENANT_ID_SQL = 29 | "SELECT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND IDN_SCIM_GROUP.ATTR_NAME = ?"; 30 | public static final String GET_ATTRIBUTES_SQL = 31 | "SELECT ATTR_NAME, ATTR_VALUE FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " + 32 | "IDN_SCIM_GROUP.ROLE_NAME=?"; 33 | public static final String GET_GROUP_ID_BY_NAME_SQL = "SELECT ATTR_VALUE FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP" + 34 | ".TENANT_ID=? AND IDN_SCIM_GROUP.ROLE_NAME=? AND IDN_SCIM_GROUP.ATTR_NAME=?"; 35 | public static final String GET_GROUP_NAME_BY_ID_SQL = 36 | "SELECT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " + 37 | "IDN_SCIM_GROUP.ATTR_VALUE=? AND IDN_SCIM_GROUP.ATTR_NAME=?"; 38 | public static final String ADD_ATTRIBUTES_SQL = 39 | "INSERT INTO IDN_SCIM_GROUP (TENANT_ID, ROLE_NAME, ATTR_NAME, ATTR_VALUE) VALUES (?, ?, ?, ?)"; 40 | public static final String ADD_ATTRIBUTES_WITH_AUDIENCE_SQL = 41 | "INSERT INTO IDN_SCIM_GROUP (TENANT_ID, ROLE_NAME, AUDIENCE_REF_ID, ATTR_NAME, ATTR_VALUE) VALUES " + 42 | "(?, ?, ?, ?, ?)"; 43 | public static final String CHECK_EXISTING_ATTRIBUTE_WITH_AUDIENCE_SQL = 44 | "SELECT TENANT_ID, ROLE_NAME, ATTR_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " + 45 | "IDN_SCIM_GROUP.ROLE_NAME=? AND IDN_SCIM_GROUP.ATTR_NAME=? AND IDN_SCIM_GROUP.AUDIENCE_REF_ID=?"; 46 | public static final String UPDATE_ATTRIBUTES_SQL = 47 | "UPDATE IDN_SCIM_GROUP SET ATTR_VALUE=? WHERE TENANT_ID=? AND ROLE_NAME=? AND ATTR_NAME=?"; 48 | public static final String UPDATE_GROUP_NAME_SQL = 49 | "UPDATE IDN_SCIM_GROUP SET ROLE_NAME=? WHERE TENANT_ID=? AND ROLE_NAME=?"; 50 | public static final String DELETE_GROUP_SQL = 51 | "DELETE FROM IDN_SCIM_GROUP WHERE TENANT_ID=? AND ROLE_NAME=?"; 52 | public static final String CHECK_EXISTING_ATTRIBUTE_SQL = 53 | "SELECT TENANT_ID, ROLE_NAME, ATTR_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " + 54 | "IDN_SCIM_GROUP.ROLE_NAME=? AND IDN_SCIM_GROUP.ATTR_NAME=?"; 55 | public static final String LIST_SCIM_GROUPS_SQL_BY_ATT_AND_ATT_VALUE = 56 | "SELECT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " + 57 | "IDN_SCIM_GROUP.ATTR_NAME=? AND ATTR_VALUE LIKE ?"; 58 | public static final String LIST_SCIM_GROUPS_SQL_BY_ATT_AND_ATT_VALUE_AND_ROLE_NAME = 59 | "SELECT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " 60 | + "IDN_SCIM_GROUP.ATTR_NAME=? AND ATTR_VALUE LIKE ? AND IDN_SCIM_GROUP.ROLE_NAME LIKE ?"; 61 | public static final String LIST_SCIM_GROUPS_SQL_BY_ROLE_NAME = 62 | "SELECT DISTINCT ROLE_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID = ? AND " 63 | + "IDN_SCIM_GROUP.ROLE_NAME LIKE ?"; 64 | private SQLQueries(){} 65 | } 66 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMAgentAttributeSchemaCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.application.common.cache.BaseCache; 24 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 25 | import org.wso2.charon3.core.schema.AttributeSchema; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * This stores agent AttributeSchema against tenants. 31 | */ 32 | public class SCIMAgentAttributeSchemaCache 33 | extends BaseCache { 34 | 35 | private static final String SCIM_AGENT_SCHEMA_CACHE = "SCIMAgentAttributeSchemaCache"; 36 | private static final Log LOG = LogFactory.getLog(SCIMAgentAttributeSchemaCache.class); 37 | 38 | private static volatile SCIMAgentAttributeSchemaCache instance; 39 | 40 | private SCIMAgentAttributeSchemaCache() { 41 | 42 | super(SCIM_AGENT_SCHEMA_CACHE); 43 | } 44 | 45 | public static SCIMAgentAttributeSchemaCache getInstance() { 46 | 47 | if (instance == null) { 48 | synchronized (SCIMAgentAttributeSchemaCache.class) { 49 | if (instance == null) { 50 | instance = new SCIMAgentAttributeSchemaCache(); 51 | } 52 | } 53 | } 54 | return instance; 55 | } 56 | 57 | /** 58 | * Add agent attribute schema to cache against tenantId. 59 | * 60 | * @param tenantId TenantId. 61 | * @param agentAttributeSchema AgentAttributeSchema. 62 | */ 63 | public void addSCIMAgentAttributeSchema(int tenantId, AttributeSchema agentAttributeSchema) { 64 | 65 | SCIMAgentAttributeSchemaCacheKey cacheKey = new SCIMAgentAttributeSchemaCacheKey(tenantId); 66 | SCIMAgentAttributeSchemaCacheEntry cacheEntry = new SCIMAgentAttributeSchemaCacheEntry(agentAttributeSchema); 67 | super.addToCache(cacheKey, cacheEntry); 68 | if (LOG.isDebugEnabled()) { 69 | LOG.debug("Successfully added scim agent attributes into SCIMAgentSchemaCache for the tenant:" 70 | + tenantId); 71 | } 72 | } 73 | 74 | /** 75 | * Get SCIM2 Agent AttributeSchema by tenantId. 76 | * 77 | * @param tenantId TenantId. 78 | * @return AttributeSchema. 79 | */ 80 | public AttributeSchema getSCIMAgentAttributeSchemaByTenant(int tenantId) { 81 | 82 | SCIMAgentAttributeSchemaCacheKey cacheKey = new SCIMAgentAttributeSchemaCacheKey(tenantId); 83 | SCIMAgentAttributeSchemaCacheEntry cacheEntry = super.getValueFromCache(cacheKey); 84 | if (cacheEntry != null) { 85 | return cacheEntry.getSCIMAgentAttributeSchema(); 86 | } else { 87 | if (LOG.isDebugEnabled()) { 88 | LOG.debug("Cache entry is null for tenantId: " + tenantId); 89 | } 90 | return null; 91 | } 92 | } 93 | 94 | /** 95 | * Clear SCIM2 Agent AttributeSchema by tenantId. 96 | * 97 | * For v0 organizations, this clears the cache of the current organization only. 98 | * For v1 organizations, this clears the caches of the current organization and its child organizations. 99 | * 100 | * @param tenantId TenantId. 101 | */ 102 | public void clearSCIMAgentAttributeSchemaByTenant(int tenantId) { 103 | 104 | List tenantIdsToBeInvalidated = SCIMCommonUtils.getOrganizationsToInvalidateCaches(tenantId); 105 | for (Integer tenantIdToBeInvalidated : tenantIdsToBeInvalidated) { 106 | if (LOG.isDebugEnabled()) { 107 | LOG.debug("Clearing SCIMAgentAttributeSchemaCache entry by the tenant with id: " + 108 | tenantIdToBeInvalidated); 109 | } 110 | SCIMAgentAttributeSchemaCacheKey cacheKey = new SCIMAgentAttributeSchemaCacheKey(tenantIdToBeInvalidated); 111 | super.clearCacheEntry(cacheKey); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMCustomAttributeSchemaCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.application.common.cache.BaseCache; 24 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 25 | import org.wso2.charon3.core.schema.AttributeSchema; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * This stores custom AttributeSchema against tenants. 31 | */ 32 | public class SCIMCustomAttributeSchemaCache extends BaseCache { 33 | 34 | private static final String SCIM_CUSTOM_SCHEMA_CACHE = "SCIMCustomAttributeSchemaCache"; 35 | private static final Log log = LogFactory.getLog(SCIMCustomAttributeSchemaCache.class); 36 | 37 | private static volatile SCIMCustomAttributeSchemaCache instance; 38 | 39 | private SCIMCustomAttributeSchemaCache() { 40 | 41 | super(SCIM_CUSTOM_SCHEMA_CACHE); 42 | } 43 | 44 | public static SCIMCustomAttributeSchemaCache getInstance() { 45 | 46 | if (instance == null) { 47 | synchronized (SCIMCustomAttributeSchemaCache.class) { 48 | if (instance == null) { 49 | instance = new SCIMCustomAttributeSchemaCache(); 50 | } 51 | } 52 | } 53 | return instance; 54 | } 55 | 56 | /** 57 | * Add custom attribute schema to cache against tenantId. 58 | * 59 | * @param tenantId TenantId. 60 | * @param customAttributeSchema CustomAttributeSchema. 61 | */ 62 | public void addSCIMCustomAttributeSchema(int tenantId, AttributeSchema customAttributeSchema){ 63 | 64 | SCIMCustomAttributeSchemaCacheKey cacheKey = new SCIMCustomAttributeSchemaCacheKey(tenantId); 65 | SCIMCustomAttributeSchemaCacheEntry cacheEntry = new SCIMCustomAttributeSchemaCacheEntry(customAttributeSchema); 66 | super.addToCache(cacheKey, cacheEntry); 67 | if (log.isDebugEnabled()) { 68 | log.debug("Successfully added scim custom attributes into SCIMCustomSchemaCache for the tenant:" 69 | + tenantId); 70 | } 71 | 72 | } 73 | 74 | 75 | /** 76 | * Get SCIM2 Custom AttributeSchema by tenantId. 77 | * 78 | * @param tenantId TenantId. 79 | * @return AttributeSchema. 80 | */ 81 | public AttributeSchema getSCIMCustomAttributeSchemaByTenant(int tenantId) { 82 | 83 | SCIMCustomAttributeSchemaCacheKey cacheKey = new SCIMCustomAttributeSchemaCacheKey(tenantId); 84 | SCIMCustomAttributeSchemaCacheEntry cacheEntry = super.getValueFromCache(cacheKey); 85 | if (cacheEntry != null) { 86 | return cacheEntry.getSCIMCustomAttributeSchema(); 87 | } else { 88 | if (log.isDebugEnabled()) { 89 | log.debug("Cache entry is null for tenantId: " + tenantId); 90 | } 91 | return null; 92 | } 93 | } 94 | 95 | /** 96 | * Clear SCIM2 Custom AttributeSchema by tenantId. 97 | * 98 | * For v0 organizations, this clears the cache of the current organization only. 99 | * For v1 organizations, this clears the caches of the current organization and its child organizations. 100 | * 101 | * @param tenantId TenantId. 102 | */ 103 | public void clearSCIMCustomAttributeSchemaByTenant(int tenantId) { 104 | 105 | List tenantIdsToBeInvalidated = SCIMCommonUtils.getOrganizationsToInvalidateCaches(tenantId); 106 | for (Integer tenantIdToBeInvalidated : tenantIdsToBeInvalidated) { 107 | if (log.isDebugEnabled()) { 108 | log.debug("Clearing SCIMCustomAttributeSchemaCache entry by the tenant with id: " + 109 | tenantIdToBeInvalidated); 110 | } 111 | SCIMCustomAttributeSchemaCacheKey cacheKey = new SCIMCustomAttributeSchemaCacheKey(tenantIdToBeInvalidated); 112 | super.clearCacheEntry(cacheKey); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/cache/SCIMSystemAttributeSchemaCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.wso2.carbon.identity.application.common.cache.BaseCache; 24 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 25 | import org.wso2.charon3.core.schema.AttributeSchema; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * This stores system AttributeSchema against tenants. 31 | */ 32 | public class SCIMSystemAttributeSchemaCache 33 | extends BaseCache { 34 | 35 | private static final String SCIM_SYSTEM_SCHEMA_CACHE = "SCIMSystemAttributeSchemaCache"; 36 | private static final Log log = LogFactory.getLog(SCIMSystemAttributeSchemaCache.class); 37 | 38 | private static volatile SCIMSystemAttributeSchemaCache instance; 39 | 40 | private SCIMSystemAttributeSchemaCache() { 41 | 42 | super(SCIM_SYSTEM_SCHEMA_CACHE); 43 | } 44 | 45 | public static SCIMSystemAttributeSchemaCache getInstance() { 46 | 47 | if (instance == null) { 48 | synchronized (SCIMSystemAttributeSchemaCache.class) { 49 | if (instance == null) { 50 | instance = new SCIMSystemAttributeSchemaCache(); 51 | } 52 | } 53 | } 54 | return instance; 55 | } 56 | 57 | /** 58 | * Add system attribute schema to cache against tenantId. 59 | * 60 | * @param tenantId TenantId. 61 | * @param systemAttributeSchema SystemAttributeSchema. 62 | */ 63 | public void addSCIMSystemAttributeSchema(int tenantId, AttributeSchema systemAttributeSchema){ 64 | 65 | SCIMSystemAttributeSchemaCacheKey cacheKey = new SCIMSystemAttributeSchemaCacheKey(tenantId); 66 | SCIMSystemAttributeSchemaCacheEntry cacheEntry = new SCIMSystemAttributeSchemaCacheEntry(systemAttributeSchema); 67 | super.addToCache(cacheKey, cacheEntry); 68 | if (log.isDebugEnabled()) { 69 | log.debug("Successfully added scim system attributes into SCIMSystemSchemaCache for the tenant:" 70 | + tenantId); 71 | } 72 | 73 | } 74 | 75 | 76 | /** 77 | * Get SCIM2 System AttributeSchema by tenantId. 78 | * 79 | * @param tenantId TenantId. 80 | * @return AttributeSchema. 81 | */ 82 | public AttributeSchema getSCIMSystemAttributeSchemaByTenant(int tenantId) { 83 | 84 | SCIMSystemAttributeSchemaCacheKey cacheKey = new SCIMSystemAttributeSchemaCacheKey(tenantId); 85 | SCIMSystemAttributeSchemaCacheEntry cacheEntry = super.getValueFromCache(cacheKey); 86 | if (cacheEntry != null) { 87 | return cacheEntry.getSCIMSystemAttributeSchema(); 88 | } else { 89 | if (log.isDebugEnabled()) { 90 | log.debug("Cache entry is null for tenantId: " + tenantId); 91 | } 92 | return null; 93 | } 94 | } 95 | 96 | /** 97 | * Clear SCIM2 System AttributeSchema by tenantId. 98 | * 99 | * For v0 organizations, this clears the cache of the current organization only. 100 | * For v1 organizations, this clears the caches of the current organization and its child organizations. 101 | * 102 | * @param tenantId TenantId. 103 | */ 104 | public void clearSCIMSystemAttributeSchemaByTenant(int tenantId) { 105 | 106 | List tenantIdsToBeInvalidated = SCIMCommonUtils.getOrganizationsToInvalidateCaches(tenantId); 107 | for (Integer tenantIdToBeInvalidated : tenantIdsToBeInvalidated) { 108 | if (log.isDebugEnabled()) { 109 | log.debug("Clearing SCIMSystemAttributeSchemaCache entry by the tenant with id: " + 110 | tenantIdToBeInvalidated); 111 | } 112 | SCIMSystemAttributeSchemaCacheKey cacheKey = new SCIMSystemAttributeSchemaCacheKey(tenantIdToBeInvalidated); 113 | super.clearCacheEntry(cacheKey); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/utils/AuthenticationSchemaTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.utils; 20 | 21 | import org.testng.annotations.AfterMethod; 22 | import org.testng.annotations.BeforeMethod; 23 | import org.testng.annotations.Test; 24 | 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | import static org.testng.Assert.assertEquals; 29 | 30 | public class AuthenticationSchemaTest { 31 | 32 | private String dummyName = "dummyName"; 33 | private String dummyDescription = "dummyDescription"; 34 | private String dummySpecUri = "dummySpecUri"; 35 | private String dummyDocumentationUri = "dummyDocumentationUri"; 36 | private String dummyType = "dummyType"; 37 | private String dummyPrimary = "dummyPrimary"; 38 | private AuthenticationSchema authenticationSchema; 39 | 40 | @BeforeMethod 41 | public void setUp() throws Exception { 42 | authenticationSchema = new AuthenticationSchema(); 43 | Map properties = new HashMap<>(); 44 | properties.put("name", dummyName); 45 | properties.put("description", dummyDescription); 46 | properties.put("specUri", dummySpecUri); 47 | properties.put("documentationUri", dummyDocumentationUri); 48 | properties.put("type", dummyType); 49 | properties.put("primary", dummyPrimary); 50 | authenticationSchema.setProperties(properties); 51 | } 52 | 53 | @AfterMethod 54 | public void tearDown() throws Exception { 55 | 56 | } 57 | 58 | @Test 59 | public void testGetName() throws Exception { 60 | assertEquals(authenticationSchema.getName(), dummyName); 61 | } 62 | 63 | @Test 64 | public void testSetName() throws Exception { 65 | dummyName = "dummyName2"; 66 | authenticationSchema.setName(dummyName); 67 | assertEquals(authenticationSchema.getName(), dummyName); 68 | } 69 | 70 | @Test 71 | public void testGetDescription() throws Exception { 72 | assertEquals(authenticationSchema.getDescription(), dummyDescription); 73 | } 74 | 75 | @Test 76 | public void testSetDescription() throws Exception { 77 | dummyDescription = "dummyDescription2"; 78 | authenticationSchema.setDescription(dummyDescription); 79 | assertEquals(authenticationSchema.getDescription(), dummyDescription); 80 | } 81 | 82 | @Test 83 | public void testGetSpecUri() throws Exception { 84 | assertEquals(authenticationSchema.getSpecUri(), dummySpecUri); 85 | } 86 | 87 | @Test 88 | public void testSetSpecUri() throws Exception { 89 | dummySpecUri = "dummySpecUri2"; 90 | authenticationSchema.setSpecUri(dummySpecUri); 91 | assertEquals(authenticationSchema.getSpecUri(), dummySpecUri); 92 | } 93 | 94 | @Test 95 | public void testGetDocumentationUri() throws Exception { 96 | assertEquals(authenticationSchema.getDocumentationUri(), dummyDocumentationUri); 97 | } 98 | 99 | @Test 100 | public void testSetDocumentationUri() throws Exception { 101 | dummyDocumentationUri = "dummyDocumentationUri2"; 102 | authenticationSchema.setDocumentationUri(dummyDocumentationUri); 103 | assertEquals(authenticationSchema.getDocumentationUri(), dummyDocumentationUri); 104 | } 105 | 106 | @Test 107 | public void testGetType() throws Exception { 108 | assertEquals(authenticationSchema.getType(), dummyType); 109 | } 110 | 111 | @Test 112 | public void testSetType() throws Exception { 113 | dummyType = "dummyType2"; 114 | authenticationSchema.setType(dummyType); 115 | assertEquals(authenticationSchema.getType(), dummyType); 116 | } 117 | 118 | @Test 119 | public void testGetPrimary() throws Exception { 120 | assertEquals(authenticationSchema.getPrimary(), dummyPrimary); 121 | } 122 | 123 | @Test 124 | public void testSetPrimary() throws Exception { 125 | dummyPrimary = "dummyPrimary2"; 126 | authenticationSchema.setPrimary(dummyPrimary); 127 | assertEquals(authenticationSchema.getPrimary(), dummyPrimary); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/test/constants/TestConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.test.constants; 20 | 21 | import java.util.Arrays; 22 | import java.util.List; 23 | 24 | /** 25 | * Constants used in the test classes. 26 | */ 27 | public class TestConstants { 28 | 29 | public static final String TEST_USER_ID = "testUserId"; 30 | public static final String TEST_USER_USERNAME = "testUser"; 31 | 32 | public enum Claims { 33 | NEW_SINGLEVALUE_CLAIM1("http://wso2.org/claims/claim1", false, "value11", "value11", null), 34 | UPDATING_SINGLEVALUE_CLAIM2("http://wso2.org/claims/claim2", false, "value21", "value21", "value20"), 35 | UPDATING_MULTI_ATTRIBUTE_SEPARATOR_INCLUDED_SINGLEVALUE_CLAIM3("http://wso2.org/claims/claim3", false, 36 | "value31,value32,value33", "value31,value32,value33", "value30,value31"), 37 | DELETING_SINGLEVALUE_CLAIM4("http://wso2.org/claims/claim4", false, "value41", "", "value41"), 38 | DELETING_MULTI_ATTRIBUTE_SEPARATOR_INCLUDED_SINGLEVALUE_CLAIM5("http://wso2.org/claims/claim5", false, 39 | "value51,value52", "", "value51,value52"), 40 | NEW_MULTIVALUE_INPUT_VALUE_AS_STRING_LIST_CLAIM6("http://wso2.org/claims/claim6", true, 41 | Arrays.asList("value61", "value62", "value63"), 42 | new String[]{"value61", "value62", "value63"}, null), 43 | NEW_MULTIVALUE_INPUT_VALUE_AS_STRING_CLAIM6("http://wso2.org/claims/claim6", true, "value61,value62,value63", 44 | new String[]{"value61", "value62", "value63"}, null), 45 | UPDATING_MULTIVALUE_INPUT_VALUE_AS_STRING_LIST_CLAIM7("http://wso2.org/claims/claim7", true, 46 | Arrays.asList("value71", "value72"), 47 | new String[]{"value70", "value73", "value71", "value72"}, "value70,value73"), 48 | UPDATING_MULTIVALUE_INPUT_VALUE_AS_STRING_CLAIM7("http://wso2.org/claims/claim7", true, "value71,value72", 49 | new String[]{"value71", "value72"}, "value70,value73"), 50 | DELETING_MULTIVALUE_INPUT_VALUE_AS_STRING_LIST_CLAIM8("http://wso2.org/claims/claim8", true, 51 | Arrays.asList("value81", "value82"), 52 | new String[]{"value80"}, "value81,value80,value82"), 53 | DELETING_MULTIVALUE_INPUT_VALUE_AS_STRING_CLAIM8("http://wso2.org/claims/claim8", true, 54 | "value81,value80", 55 | new String[]{}, "value81,value80,value82"), 56 | FLOW_INITIATOR_SINGLEVALUE_IDENTITY_CLAIM1("http://wso2.org/claims/identity/adminForcedPasswordReset", false, "true", "false", null); 57 | 58 | private String claimURI; 59 | private boolean isMultiValued; 60 | private Object inputValue; 61 | private Object expectedValueInDTO; 62 | private String existingValueInUser; 63 | 64 | Claims(String claimURI, boolean isMultiValued, Object inputValue, Object expectedValueInDTO, 65 | String existingValueInUser) { 66 | 67 | this.claimURI = claimURI; 68 | this.isMultiValued = isMultiValued; 69 | this.inputValue = inputValue; 70 | this.expectedValueInDTO = expectedValueInDTO; 71 | this.existingValueInUser = existingValueInUser; 72 | } 73 | 74 | public String getClaimURI() { 75 | 76 | return claimURI; 77 | } 78 | 79 | public boolean isMultiValued() { 80 | 81 | return isMultiValued; 82 | } 83 | 84 | public String getInputValueAsString() { 85 | 86 | return String.valueOf(inputValue); 87 | } 88 | 89 | public List getInputValueAsStringList() { 90 | 91 | return (List) inputValue; 92 | } 93 | 94 | public String getExpectedValueInDTOAsString() { 95 | 96 | return String.valueOf(expectedValueInDTO); 97 | } 98 | 99 | public String[] getExpectedValueInDTOAsStringArray() { 100 | 101 | return (String[]) expectedValueInDTO; 102 | } 103 | 104 | public String getExistingValueInUser() { 105 | 106 | return existingValueInUser; 107 | } 108 | } 109 | 110 | private TestConstants() { 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/utils/AdminAttributeUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2024, WSO2 LLC. (http://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.utils; 20 | 21 | import org.mockito.ArgumentCaptor; 22 | import org.mockito.Mock; 23 | import org.mockito.MockedStatic; 24 | import org.testng.annotations.AfterMethod; 25 | import org.testng.annotations.BeforeMethod; 26 | import org.testng.annotations.DataProvider; 27 | import org.testng.annotations.Test; 28 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 29 | import org.wso2.carbon.identity.scim2.common.internal.component.SCIMCommonComponentHolder; 30 | import org.wso2.carbon.stratos.common.util.ClaimsMgtUtil; 31 | import org.wso2.carbon.user.api.UserRealm; 32 | import org.wso2.carbon.user.api.UserStoreException; 33 | import org.wso2.carbon.user.core.UserStoreManager; 34 | import org.wso2.carbon.user.core.service.RealmService; 35 | 36 | import java.util.Map; 37 | 38 | import static org.mockito.ArgumentMatchers.anyInt; 39 | import static org.mockito.ArgumentMatchers.anyString; 40 | import static org.mockito.ArgumentMatchers.eq; 41 | import static org.mockito.Mockito.mockStatic; 42 | import static org.mockito.Mockito.verify; 43 | import static org.mockito.Mockito.when; 44 | import static org.mockito.MockitoAnnotations.initMocks; 45 | 46 | public class AdminAttributeUtilTest { 47 | 48 | @Mock 49 | RealmService realmService; 50 | 51 | @Mock 52 | UserRealm userRealm; 53 | 54 | @Mock 55 | UserStoreManager userStoreManager; 56 | 57 | AdminAttributeUtil adminAttributeUtil; 58 | 59 | private MockedStatic scimCommonComponentHolder; 60 | private MockedStatic claimsMgtUtil; 61 | private MockedStatic identityTenantUtil; 62 | 63 | @BeforeMethod 64 | public void setUp() throws Exception { 65 | initMocks(this); 66 | adminAttributeUtil = new AdminAttributeUtil(); 67 | scimCommonComponentHolder = mockStatic(SCIMCommonComponentHolder.class); 68 | claimsMgtUtil = mockStatic(ClaimsMgtUtil.class); 69 | identityTenantUtil = mockStatic(IdentityTenantUtil.class); 70 | } 71 | 72 | @AfterMethod 73 | public void tearDown() throws Exception { 74 | scimCommonComponentHolder.close(); 75 | claimsMgtUtil.close(); 76 | identityTenantUtil.close(); 77 | } 78 | 79 | @DataProvider(name = "testUpdateAdminUserData") 80 | public Object[][] testUpdateAdminUserData() { 81 | return new Object[][]{ 82 | {true}, 83 | {false} 84 | }; 85 | } 86 | 87 | @Test(dataProvider = "testUpdateAdminUserData") 88 | public void testUpdateAdminUser(boolean validateSCIMID) throws Exception { 89 | String adminUsername = "admin"; 90 | 91 | scimCommonComponentHolder.when(() -> SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 92 | when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm); 93 | when(userRealm.getUserStoreManager()).thenReturn(userStoreManager); 94 | when(userStoreManager.isSCIMEnabled()).thenReturn(true); 95 | claimsMgtUtil.when(() -> ClaimsMgtUtil.getAdminUserNameFromTenantId(eq(realmService), anyInt())).thenReturn(adminUsername); 96 | identityTenantUtil.when(() -> IdentityTenantUtil.getRealmService()).thenReturn(realmService); 97 | when(userStoreManager.getUserClaimValue(anyString(), anyString(), anyString())).thenReturn(""); 98 | 99 | ArgumentCaptor argument = ArgumentCaptor.forClass(Map.class); 100 | adminAttributeUtil.updateAdminUser(1, validateSCIMID); 101 | verify(userStoreManager).setUserClaimValues(anyString(), argument.capture(), anyString()); 102 | } 103 | 104 | @Test(expectedExceptions = UserStoreException.class) 105 | public void testUpdateAdminUser1() throws Exception { 106 | scimCommonComponentHolder.when(() -> SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 107 | when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm); 108 | when(userRealm.getUserStoreManager()).thenReturn(userStoreManager); 109 | when(userStoreManager.isSCIMEnabled()).thenThrow(new UserStoreException()); 110 | 111 | adminAttributeUtil.updateAdminUser(1, true); 112 | verify(userStoreManager.isSCIMEnabled()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.server.feature/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | org.wso2.carbon.identity.inbound.provisioning.scim2 23 | identity-inbound-provisioning-scim2 24 | ../../pom.xml 25 | 3.4.223-SNAPSHOT 26 | 27 | 28 | 4.0.0 29 | org.wso2.carbon.identity.scim2.server.feature 30 | pom 31 | SCIM2 Feature 32 | http://wso2.org 33 | This aggregator feature contains the bundles required for Identity SCIM2 functionality 34 | 35 | 36 | 37 | org.wso2.carbon.identity.inbound.provisioning.scim2 38 | org.wso2.carbon.identity.scim2.common.feature 39 | zip 40 | 41 | 42 | org.wso2.carbon.identity.inbound.provisioning.scim2 43 | org.wso2.carbon.identity.scim2.provider.feature 44 | zip 45 | 46 | 47 | org.jacoco 48 | jacoco-maven-plugin 49 | ${jacoco.version} 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.wso2.maven 57 | carbon-p2-plugin 58 | ${carbon.p2.plugin.version} 59 | 60 | 61 | 4-p2-feature-generation 62 | package 63 | 64 | p2-feature-gen 65 | 66 | 67 | org.wso2.carbon.identity.scim2.server 68 | ../etc/feature.properties 69 | 70 | 71 | org.wso2.carbon.p2.category.type:server 72 | 73 | 74 | 75 | 76 | org.wso2.carbon.identity.inbound.provisioning.scim2:org.wso2.carbon.identity.scim2.common.feature 77 | 78 | 79 | org.wso2.carbon.identity.inbound.provisioning.scim2:org.wso2.carbon.identity.scim2.provider.feature 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.jacoco 88 | jacoco-maven-plugin 89 | ${jacoco.version} 90 | 91 | 92 | 93 | prepare-agent 94 | 95 | 96 | 97 | report 98 | test 99 | 100 | report 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/impl/DefaultSCIMUserStoreErrorResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.impl; 20 | 21 | import org.apache.commons.lang.StringUtils; 22 | import org.apache.http.HttpStatus; 23 | import org.wso2.carbon.identity.scim2.common.extenstion.SCIMUserStoreErrorResolver; 24 | import org.wso2.carbon.identity.scim2.common.extenstion.SCIMUserStoreException; 25 | import org.wso2.carbon.user.api.UserStoreException; 26 | import org.wso2.carbon.user.core.UserCoreConstants; 27 | import org.wso2.carbon.user.core.UserStoreClientException; 28 | import org.wso2.carbon.user.core.constants.UserCoreErrorConstants; 29 | 30 | import static org.wso2.carbon.user.core.constants.UserCoreErrorConstants.ErrorMessages.ERROR_CODE_GROUP_ALREADY_EXISTS; 31 | 32 | /** 33 | * Default implementation of SCIMUserStoreErrorResolver. Should be used to resolve errors thrown by default 34 | * user store managers packed in the product. 35 | */ 36 | public class DefaultSCIMUserStoreErrorResolver implements SCIMUserStoreErrorResolver { 37 | 38 | private static final String ERROR_CODE_READ_ONLY_USERSTORE = "30002"; 39 | private static final String ERROR_CODE_USER_NOT_FOUND = "30007"; 40 | private static final String ERROR_CODE_EXISTING_ROLE_NAME = "30012"; 41 | 42 | @Override 43 | public SCIMUserStoreException resolve(UserStoreException e) { 44 | 45 | if (e.getMessage().contains(ERROR_CODE_USER_NOT_FOUND)) { 46 | String msg = e.getMessage().substring(e.getMessage().indexOf(":") + 1).trim(); 47 | return new SCIMUserStoreException(msg, HttpStatus.SC_NOT_FOUND); 48 | } else if (e instanceof org.wso2.carbon.user.core.UserStoreException && 49 | (UserCoreConstants.ErrorCode.USER_DELETION_WORKFLOW_CREATED.equals(((org.wso2.carbon.user.core.UserStoreException) e). 50 | getErrorCode()))) { 51 | return new SCIMUserStoreException("User deletion has sent for the approval", HttpStatus.SC_ACCEPTED); 52 | } else if (e.getMessage().contains(ERROR_CODE_EXISTING_ROLE_NAME) || 53 | (e instanceof org.wso2.carbon.user.core.UserStoreClientException && 54 | ((UserStoreClientException) e).getErrorCode() != null && 55 | ((UserStoreClientException) e).getErrorCode() 56 | .contains(ERROR_CODE_GROUP_ALREADY_EXISTS.getCode()))) { 57 | String groupName = e.getMessage().substring(e.getMessage().indexOf(":") + 1).trim().split("\\s+")[0]; 58 | String msg = 59 | "Group name: " + groupName + " is already there in the system. Please pick another group name."; 60 | return new SCIMUserStoreException(msg, HttpStatus.SC_CONFLICT); 61 | } else if (e.getMessage().contains(ERROR_CODE_READ_ONLY_USERSTORE) || 62 | (e instanceof org.wso2.carbon.user.core.UserStoreException && StringUtils 63 | .equals(UserCoreErrorConstants.ErrorMessages.ERROR_CODE_READONLY_USER_STORE.getCode(), 64 | ((org.wso2.carbon.user.core.UserStoreException) e).getErrorCode()))) { 65 | String msg = "Invalid operation. User store is read only"; 66 | return new SCIMUserStoreException(msg, HttpStatus.SC_BAD_REQUEST); 67 | } else if (e instanceof org.wso2.carbon.user.core.UserStoreException && StringUtils 68 | .equals(UserCoreErrorConstants.ErrorMessages.ERROR_CODE_USERNAME_CANNOT_BE_EMPTY.getCode(), 69 | ((org.wso2.carbon.user.core.UserStoreException) e).getErrorCode())) { 70 | return new SCIMUserStoreException("Unable to create the user. Username is a mandatory field.", 71 | HttpStatus.SC_BAD_REQUEST); 72 | } else if (e instanceof org.wso2.carbon.user.core.UserStoreClientException && UserCoreErrorConstants 73 | .ErrorMessages.ERROR_CODE_INVALID_DOMAIN_NAME.getCode().equals(((UserStoreClientException) e) 74 | .getErrorCode())) { 75 | return new SCIMUserStoreException("Unable to proceed. Invalid domain name.", HttpStatus.SC_BAD_REQUEST); 76 | } else if (e instanceof org.wso2.carbon.user.core.UserStoreClientException) { 77 | String description = e.getMessage(); 78 | if (StringUtils.isBlank(description)) { 79 | description = "Invalid Request"; 80 | } 81 | return new SCIMUserStoreException(description, HttpStatus.SC_BAD_REQUEST); 82 | } 83 | return null; 84 | } 85 | 86 | @Override 87 | public int getOrder() { 88 | 89 | return 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/IdentitySCIMManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.impl; 20 | 21 | import org.mockito.Mock; 22 | import org.mockito.MockedStatic; 23 | import org.testng.annotations.AfterMethod; 24 | import org.testng.annotations.BeforeMethod; 25 | import org.testng.annotations.Test; 26 | import org.testng.annotations.Listeners; 27 | import org.mockito.testng.MockitoTestNGListener; 28 | import org.wso2.carbon.identity.scim2.common.internal.component.SCIMCommonComponentHolder; 29 | import org.wso2.carbon.identity.scim2.common.test.utils.CommonTestUtils; 30 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 31 | import org.wso2.carbon.identity.scim2.common.utils.SCIMConfigProcessor; 32 | import org.wso2.carbon.user.api.UserRealm; 33 | import org.wso2.carbon.user.api.UserStoreException; 34 | import org.wso2.carbon.user.core.service.RealmService; 35 | import org.wso2.carbon.user.core.tenant.TenantManager; 36 | import org.wso2.charon3.core.exceptions.CharonException; 37 | import org.wso2.charon3.core.extensions.UserManager; 38 | 39 | import java.nio.file.Paths; 40 | 41 | import static org.testng.Assert.assertNotNull; 42 | import static org.testng.Assert.fail; 43 | import static org.mockito.Mockito.when; 44 | import static org.mockito.Mockito.mockStatic; 45 | import static org.mockito.ArgumentMatchers.anyString; 46 | import static org.mockito.ArgumentMatchers.anyInt; 47 | 48 | /** 49 | * Contains the unit test cases for IdentitySCIMManager. 50 | */ 51 | @Listeners(MockitoTestNGListener.class) 52 | public class IdentitySCIMManagerTest { 53 | 54 | @Mock 55 | RealmService realmService; 56 | 57 | @Mock 58 | TenantManager mockedTenantManager; 59 | 60 | @Mock 61 | UserRealm mockedUserRealm; 62 | 63 | private SCIMConfigProcessor scimConfigProcessor; 64 | private IdentitySCIMManager identitySCIMManager; 65 | 66 | private MockedStatic scimCommonUtils; 67 | private MockedStatic scimCommonComponentHolder; 68 | 69 | @BeforeMethod 70 | public void setUp() throws Exception { 71 | scimCommonUtils = mockStatic(SCIMCommonUtils.class); 72 | scimCommonUtils.when(() -> SCIMCommonUtils.getSCIMUserURL()).thenReturn("http://scimUserUrl:9443"); 73 | 74 | scimCommonComponentHolder = mockStatic(SCIMCommonComponentHolder.class); 75 | scimConfigProcessor = SCIMConfigProcessor.getInstance(); 76 | String filePath = Paths 77 | .get(System.getProperty("user.dir"), "src", "test", "resources", "charon-config-test.xml").toString(); 78 | scimConfigProcessor.buildConfigFromFile(filePath); 79 | identitySCIMManager = IdentitySCIMManager.getInstance(); 80 | 81 | CommonTestUtils.initPrivilegedCarbonContext(); 82 | } 83 | 84 | @AfterMethod 85 | public void tearDown() { 86 | scimCommonComponentHolder.close(); 87 | scimCommonUtils.close(); 88 | } 89 | 90 | @Test 91 | public void testGetInstance() throws Exception { 92 | 93 | assertNotNull(identitySCIMManager, "Returning a null"); 94 | assertNotNull(identitySCIMManager, "Returning a null"); 95 | } 96 | 97 | @Test 98 | public void testGetEncoder() throws Exception { 99 | 100 | assertNotNull(identitySCIMManager.getEncoder()); 101 | } 102 | 103 | @Test 104 | public void testGetUserManager() throws Exception { 105 | 106 | when(SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 107 | when(realmService.getTenantManager()).thenReturn(mockedTenantManager); 108 | when(realmService.getTenantUserRealm(anyInt())).thenReturn(mockedUserRealm); 109 | UserManager userManager = identitySCIMManager.getUserManager(); 110 | assertNotNull(userManager); 111 | } 112 | 113 | @Test 114 | public void testGetUserManagerWithException() throws Exception { 115 | 116 | try { 117 | when(SCIMCommonComponentHolder.getRealmService()).thenReturn(null); 118 | identitySCIMManager.getUserManager(); 119 | fail("getUserManager() method should have thrown a CharonException"); 120 | } catch (CharonException e) { 121 | assertNotNull(e); 122 | } 123 | } 124 | 125 | @Test 126 | public void testGetUserManagerWithException2() throws Exception { 127 | 128 | when(SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 129 | when(realmService.getTenantManager()).thenReturn(mockedTenantManager); 130 | when(mockedTenantManager.getTenantId(anyString())).thenThrow(new UserStoreException()); 131 | 132 | try { 133 | identitySCIMManager.getUserManager(); 134 | fail("getUserManager() method should have thrown a CharonException"); 135 | } catch (CharonException e) { 136 | assertNotNull(e); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/utils/SCIMConfigProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.utils; 20 | 21 | import org.testng.annotations.BeforeMethod; 22 | import org.testng.annotations.DataProvider; 23 | import org.testng.annotations.Test; 24 | import org.wso2.charon3.core.exceptions.CharonException; 25 | 26 | import java.nio.file.Paths; 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | import static org.testng.Assert.assertEquals; 33 | import static org.testng.Assert.assertNotNull; 34 | 35 | public class SCIMConfigProcessorTest { 36 | private SCIMConfigProcessor scimConfigProcessor; 37 | private AuthenticationSchema authenticationSchema; 38 | 39 | @DataProvider(name = "propertyProvider") 40 | public static Object[][] propertyProvider() { 41 | return new Object[][] { { "Name", "SCIM2" }, { "Address", null } }; 42 | } 43 | 44 | @DataProvider(name="filePathProvider") 45 | public static Object[][] filePathProvider() { 46 | String errorFileNamePath = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", 47 | "charon-config-tst.xml").toString(); 48 | 49 | String noFilepath = Paths.get(System.getProperty("user.dir"), "src", "resources", "charon-config-test.xml") 50 | .toString(); 51 | 52 | String errorFilePath = Paths.get(System.getProperty("user.dir"), "srcTest", "test", "resources", 53 | "charon-config-test.xml").toString(); 54 | return new Object[][] { { noFilepath }, { errorFilePath }, { errorFileNamePath } }; 55 | } 56 | 57 | @BeforeMethod 58 | public void setUp() throws Exception { 59 | scimConfigProcessor = new SCIMConfigProcessor(); 60 | 61 | scimConfigProcessor.properties.put("Name", "SCIM2"); 62 | scimConfigProcessor.properties.put("Age", "24"); 63 | 64 | authenticationSchema = new AuthenticationSchema(); 65 | authenticationSchema.setName("SCIM"); 66 | authenticationSchema.setDescription("SCIM2"); 67 | authenticationSchema.setSpecUri("https://localhost:9443/scim2"); 68 | authenticationSchema.setDocumentationUri("https://localhost:9443/scim2/docs"); 69 | authenticationSchema.setType("Authentication"); 70 | authenticationSchema.setPrimary("true"); 71 | scimConfigProcessor.authenticationSchemas = new ArrayList<>(); 72 | scimConfigProcessor.authenticationSchemas.add(authenticationSchema); 73 | } 74 | 75 | @Test 76 | public void testGetProperties() throws Exception { 77 | Map expected = new HashMap(); 78 | expected.put("Name", "SCIM2"); 79 | expected.put("Age", "24"); 80 | 81 | Map map = scimConfigProcessor.getProperties(); 82 | 83 | assertEquals(map.get("Name"), expected.get("Name")); 84 | assertEquals(map.get("Age"), expected.get("Age")); 85 | } 86 | 87 | @Test(dataProvider = "propertyProvider") 88 | public void testGetProperty(String property, String expectedResult) throws Exception { 89 | assertEquals(scimConfigProcessor.getProperty(property), expectedResult); 90 | } 91 | 92 | @Test 93 | public void testGetAuthenticationSchemas() throws Exception { 94 | List authenticationSchemaList = scimConfigProcessor.getAuthenticationSchemas(); 95 | for (AuthenticationSchema authenticationSchema1 : authenticationSchemaList) { 96 | assertEquals(authenticationSchema1.getName(), authenticationSchema.getName()); 97 | assertEquals(authenticationSchema1.getDescription(), authenticationSchema.getDescription()); 98 | assertEquals(authenticationSchema1.getSpecUri(), authenticationSchema.getSpecUri()); 99 | assertEquals(authenticationSchema1.getDocumentationUri(), authenticationSchema.getDocumentationUri()); 100 | assertEquals(authenticationSchema1.getType(), authenticationSchema.getType()); 101 | assertEquals(authenticationSchema1.getPrimary(), authenticationSchema.getPrimary()); 102 | } 103 | } 104 | 105 | @Test(dataProvider = "filePathProvider", expectedExceptions = CharonException.class) 106 | public void testBuildConfigFromFile(String filePath) throws Exception { 107 | scimConfigProcessor.buildConfigFromFile(filePath); 108 | } 109 | 110 | @Test 111 | public void testBuildConfigFromFileHappy() throws Exception { 112 | String filePath = Paths.get(System.getProperty("user.dir"), "src", "test", "resources", 113 | "charon-config-test.xml").toString(); 114 | scimConfigProcessor.buildConfigFromFile(filePath); 115 | 116 | } 117 | 118 | @Test 119 | public void testGetInstance() throws Exception { 120 | SCIMConfigProcessor scimConfigProcessor1 = scimConfigProcessor.getInstance(); 121 | assertNotNull(scimConfigProcessor1); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/impl/IdentityResourceURLBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.impl; 20 | 21 | import org.mockito.Mock; 22 | 23 | import org.mockito.MockedStatic; 24 | import org.testng.annotations.AfterMethod; 25 | import org.testng.annotations.BeforeMethod; 26 | import org.testng.annotations.DataProvider; 27 | import org.testng.annotations.Test; 28 | import org.wso2.carbon.identity.core.ServiceURL; 29 | import org.wso2.carbon.identity.core.ServiceURLBuilder; 30 | import org.wso2.carbon.identity.core.URLBuilderException; 31 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 32 | import org.wso2.charon3.core.exceptions.NotFoundException; 33 | 34 | import java.util.HashMap; 35 | import java.util.Map; 36 | 37 | import static org.mockito.ArgumentMatchers.anyString; 38 | import static org.mockito.Mockito.when; 39 | import static org.mockito.MockitoAnnotations.initMocks; 40 | import static org.testng.Assert.assertEquals; 41 | import static org.mockito.Mockito.mockStatic; 42 | 43 | /** 44 | * Contains the unit test cases for IdentityResourceURLBuilder. 45 | */ 46 | public class IdentityResourceURLBuilderTest { 47 | 48 | private static final Map DUMMY_ENDPOINT_URI_MAP = new HashMap() {{ 49 | put("Users", "https://localhost:9444/scim2/Users"); 50 | put("Groups", "https://localhost:9444/scim2/Groups"); 51 | }}; 52 | 53 | @Mock 54 | ServiceURLBuilder mockServiceURLBuilder; 55 | 56 | @Mock 57 | ServiceURL mockServiceUrl; 58 | 59 | private MockedStatic serviceURLBuilder; 60 | private MockedStatic identityTenantUtil; 61 | 62 | @BeforeMethod 63 | public void setUpMethod() { 64 | 65 | initMocks(this); 66 | serviceURLBuilder = mockStatic(ServiceURLBuilder.class); 67 | serviceURLBuilder.when(() -> ServiceURLBuilder.create()).thenReturn(mockServiceURLBuilder); 68 | when(mockServiceURLBuilder.addPath(anyString())).thenReturn(mockServiceURLBuilder); 69 | identityTenantUtil = mockStatic(IdentityTenantUtil.class); 70 | } 71 | 72 | @AfterMethod 73 | public void tearDownMethod() { 74 | serviceURLBuilder.close(); 75 | identityTenantUtil.close(); 76 | } 77 | 78 | @DataProvider(name = "dataProviderForBuild") 79 | public Object[][] dataProviderForBuild() { 80 | 81 | return new Object[][]{ 82 | {true, "https://localhost:9444/scim2/", "Users", false, "https://localhost:9444/scim2/Users"}, 83 | {true, "https://localhost:9444/scim2/", "Groups", true, "https://localhost:9444/scim2/Groups"}, 84 | {false, "https://localhost:9444/scim2/", "Users", false, "https://localhost:9444/scim2/Users"}, 85 | {true, "https://localhost:9444/scim2/", "InvalidResource", true, null}, 86 | {false, "https://localhost:9444/scim2/", "InvalidResource", false, null}, 87 | }; 88 | } 89 | 90 | @Test(dataProvider = "dataProviderForBuild") 91 | public void testBuild(boolean isTenantQualifiedUrlsEnabled, String url, String resource, boolean throwError, 92 | String expected) throws NotFoundException, URLBuilderException { 93 | 94 | identityTenantUtil.when(() -> IdentityTenantUtil.isTenantQualifiedUrlsEnabled()).thenReturn(isTenantQualifiedUrlsEnabled); 95 | when(mockServiceURLBuilder.build()).thenAnswer(invocationOnMock -> { 96 | if (throwError) { 97 | throw new URLBuilderException("Protocol of service URL is not available."); 98 | } 99 | return mockServiceUrl; 100 | }); 101 | when(mockServiceUrl.getAbsolutePublicURL()).thenReturn(url); 102 | IdentityResourceURLBuilder identityResourceURLBuilder = new IdentityResourceURLBuilder(); 103 | identityResourceURLBuilder.setEndpointURIMap(DUMMY_ENDPOINT_URI_MAP); 104 | String buildValue = identityResourceURLBuilder.build(resource); 105 | assertEquals(buildValue, expected); 106 | } 107 | 108 | @DataProvider(name = "dataProviderForBuildThrowingNotFoundException") 109 | public Object[][] dataProviderForBuildThrowingNotFoundException() { 110 | 111 | return new Object[][]{ 112 | {true, "InvalidResource"}, 113 | {false, "InvalidResource"}, 114 | }; 115 | } 116 | 117 | @Test(expectedExceptions = NotFoundException.class, dataProvider = "dataProviderForBuildThrowingNotFoundException") 118 | public void testBuildThrowingNotFoundException(boolean isTenantQualifiedUrlsEnabled, String resource) 119 | throws URLBuilderException, NotFoundException { 120 | 121 | identityTenantUtil.when(() -> IdentityTenantUtil.isTenantQualifiedUrlsEnabled()).thenReturn(isTenantQualifiedUrlsEnabled); 122 | when(mockServiceURLBuilder.build()).thenThrow( 123 | new URLBuilderException("Protocol of service URL is not available.")); 124 | IdentityResourceURLBuilder identityResourceURLBuilder = new IdentityResourceURLBuilder(); 125 | identityResourceURLBuilder.build(resource); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/resources/AbstractResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.resources; 20 | 21 | import org.apache.commons.lang.StringUtils; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants; 25 | import org.wso2.carbon.identity.scim2.provider.util.SupportUtils; 26 | import org.wso2.charon3.core.exceptions.BadRequestException; 27 | import org.wso2.charon3.core.exceptions.CharonException; 28 | import org.wso2.charon3.core.exceptions.ForbiddenException; 29 | import org.wso2.charon3.core.exceptions.FormatNotSupportedException; 30 | import org.wso2.charon3.core.protocol.endpoints.AbstractResourceManager; 31 | 32 | import javax.ws.rs.core.Response; 33 | 34 | public class AbstractResource { 35 | private static final Log logger = LogFactory.getLog(AbstractResource.class); 36 | 37 | //identify the output format 38 | public boolean isValidOutputFormat(String format) { 39 | 40 | if (format == null) { 41 | return true; 42 | } 43 | if (!StringUtils.contains(format, ",")) { 44 | return isValidInputFormat(format); 45 | } else { 46 | String[] responseFormats = format.split(","); 47 | for (String responseFormat : responseFormats) { 48 | if (responseFormat != null) { 49 | responseFormat = responseFormat.trim(); 50 | boolean validJSONOutputFormat = isValidJSONOutputFormat(responseFormat); 51 | if (validJSONOutputFormat) { 52 | return true; 53 | } 54 | } 55 | } 56 | return false; 57 | } 58 | } 59 | //identify the input format 60 | public boolean isValidInputFormat(String format) { 61 | 62 | if (isCharsetDefined(format)) { 63 | String[] inputFormats = format.split(SCIMProviderConstants.SEMI_COLON); 64 | String encodingFormat = inputFormats[1].trim(); 65 | if (StringUtils.isNotEmpty(encodingFormat) && 66 | encodingFormat.equalsIgnoreCase(SCIMProviderConstants.CHARSET_UTF8)) { 67 | String contentType = inputFormats[0].trim(); 68 | return isValidContentType(contentType); 69 | } 70 | } 71 | return isValidContentType(format); 72 | } 73 | 74 | /** 75 | * To check whether the request has charset param in Content-Type. 76 | * 77 | * @param format the input format 78 | * @return true or false 79 | */ 80 | private boolean isCharsetDefined(String format) { 81 | 82 | return format != null && format.toLowerCase().contains(SCIMProviderConstants.CHARSET); 83 | } 84 | 85 | /** 86 | * To validate the Content-Type. 87 | * 88 | * @param format the input format 89 | * @return true or false 90 | */ 91 | private boolean isValidContentType(String format) { 92 | 93 | return format == null || "*/*".equals(format) || 94 | format.equalsIgnoreCase(SCIMProviderConstants.APPLICATION__JSON) 95 | || format.equalsIgnoreCase(SCIMProviderConstants.APPLICATION_SCIM_JSON) 96 | || format.equalsIgnoreCase(SCIMProviderConstants.APPLICATION_ALL); 97 | } 98 | 99 | /** 100 | * Build an error message for a Charon exception. 101 | * 102 | * @param e CharonException 103 | * @return 104 | */ 105 | protected Response handleCharonException(CharonException e) { 106 | if (logger.isDebugEnabled()) { 107 | logger.debug(e.getMessage(), e); 108 | } 109 | 110 | // Log the internal server errors. 111 | if (e.getStatus() == 500) { 112 | logger.error("Server error while handling the request.", e); 113 | } 114 | 115 | return SupportUtils.buildResponse(AbstractResourceManager.encodeSCIMException(e)); 116 | } 117 | 118 | /** 119 | * Build the error response if the requested input or output format is not supported. 120 | * @param e 121 | * @return 122 | */ 123 | protected Response handleFormatNotSupportedException(FormatNotSupportedException e) { 124 | if (logger.isDebugEnabled()) { 125 | logger.debug(e.getMessage(), e); 126 | } 127 | 128 | // use the default JSON encoder to build the error response. 129 | return SupportUtils.buildResponse( 130 | AbstractResourceManager.encodeSCIMException(e)); 131 | } 132 | 133 | protected boolean isValidJSONOutputFormat(String format) { 134 | 135 | return "*/*".equals(format) || 136 | format.equalsIgnoreCase(SCIMProviderConstants.APPLICATION__JSON) 137 | || format.equalsIgnoreCase(SCIMProviderConstants.APPLICATION_SCIM_JSON) || 138 | format.equalsIgnoreCase("application/*"); 139 | } 140 | 141 | protected Response handleForbiddenException(ForbiddenException e) { 142 | 143 | if (logger.isDebugEnabled()) { 144 | logger.debug(e.getMessage(), e); 145 | } 146 | return SupportUtils.buildResponse(AbstractResourceManager.encodeSCIMException(e)); 147 | } 148 | 149 | protected Response handleBadRequestException(BadRequestException e) { 150 | 151 | if (logger.isDebugEnabled()) { 152 | logger.debug(e.getMessage(), e); 153 | } 154 | return SupportUtils.buildResponse(AbstractResourceManager.encodeSCIMException(e)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/cache/SCIMSystemAttributeSchemaCacheTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.cache; 20 | 21 | import org.mockito.MockedStatic; 22 | import org.testng.annotations.AfterClass; 23 | import org.testng.annotations.BeforeClass; 24 | import org.testng.annotations.BeforeMethod; 25 | import org.testng.annotations.Test; 26 | import org.wso2.carbon.identity.common.testng.WithCarbonHome; 27 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 28 | import org.wso2.charon3.core.schema.AttributeSchema; 29 | 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.Collections; 33 | import java.util.List; 34 | 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.mockStatic; 37 | import static org.testng.Assert.assertEquals; 38 | import static org.testng.Assert.assertNotEquals; 39 | import static org.testng.Assert.assertNotNull; 40 | import static org.testng.Assert.assertNull; 41 | import static org.testng.Assert.assertSame; 42 | 43 | @WithCarbonHome 44 | public class SCIMSystemAttributeSchemaCacheTest { 45 | 46 | private SCIMSystemAttributeSchemaCache cache; 47 | private AttributeSchema mockAttributeSchema; 48 | private MockedStatic scimCommonUtilsStaticMock; 49 | 50 | private final List v0TenantList = new ArrayList<>(Collections.singletonList(1)); 51 | private final List v1TenantList = new ArrayList<>(Arrays.asList(1, 111)); 52 | 53 | @BeforeClass 54 | public void setUpTests() { 55 | 56 | scimCommonUtilsStaticMock = mockStatic(SCIMCommonUtils.class); 57 | } 58 | 59 | @BeforeMethod 60 | public void setUp() { 61 | 62 | cache = SCIMSystemAttributeSchemaCache.getInstance(); 63 | mockAttributeSchema = mock(AttributeSchema.class); 64 | } 65 | 66 | @Test 67 | public void testAddSCIMSystemAttributeSchema() { 68 | 69 | cache.addSCIMSystemAttributeSchema(1, mockAttributeSchema); 70 | AttributeSchema result = cache.getSCIMSystemAttributeSchemaByTenant(1); 71 | assertNotNull(result); 72 | assertEquals(mockAttributeSchema, result); 73 | } 74 | 75 | @Test 76 | public void testGetSCIMSystemAttributeSchemaByTenant() { 77 | 78 | cache.addSCIMSystemAttributeSchema(1, mockAttributeSchema); 79 | AttributeSchema result = cache.getSCIMSystemAttributeSchemaByTenant(1); 80 | assertNotNull(result); 81 | assertEquals(mockAttributeSchema, result); 82 | } 83 | 84 | @Test 85 | public void testGetSCIMSystemAttributeSchemaByTenant_NotFound() { 86 | 87 | AttributeSchema result = cache.getSCIMSystemAttributeSchemaByTenant(2); 88 | assertNull(result); 89 | } 90 | 91 | @Test 92 | public void testClearSCIMSystemAttributeSchemaByTenant() { 93 | 94 | scimCommonUtilsStaticMock.when(() -> SCIMCommonUtils.getOrganizationsToInvalidateCaches(1)) 95 | .thenReturn(v0TenantList); 96 | cache.addSCIMSystemAttributeSchema(1, mockAttributeSchema); 97 | cache.addSCIMSystemAttributeSchema(111, mockAttributeSchema); 98 | cache.clearSCIMSystemAttributeSchemaByTenant(1); 99 | AttributeSchema result = cache.getSCIMSystemAttributeSchemaByTenant(1); 100 | assertNull(result); 101 | AttributeSchema subOrgResult = cache.getSCIMSystemAttributeSchemaByTenant(111); 102 | assertNotNull(subOrgResult); 103 | } 104 | 105 | @Test 106 | public void testClearSCIMSystemAttributeSchemaByV1Tenant() { 107 | 108 | scimCommonUtilsStaticMock.when(() -> SCIMCommonUtils.getOrganizationsToInvalidateCaches(1)) 109 | .thenReturn(v1TenantList); 110 | cache.addSCIMSystemAttributeSchema(1, mockAttributeSchema); 111 | cache.addSCIMSystemAttributeSchema(111, mockAttributeSchema); 112 | cache.clearSCIMSystemAttributeSchemaByTenant(1); 113 | AttributeSchema result = cache.getSCIMSystemAttributeSchemaByTenant(1); 114 | assertNull(result); 115 | AttributeSchema subOrgResult = cache.getSCIMSystemAttributeSchemaByTenant(111); 116 | assertNull(subOrgResult); 117 | } 118 | 119 | @Test 120 | public void testSingletonInstance() { 121 | 122 | SCIMSystemAttributeSchemaCache instance1 = SCIMSystemAttributeSchemaCache.getInstance(); 123 | SCIMSystemAttributeSchemaCache instance2 = SCIMSystemAttributeSchemaCache.getInstance(); 124 | assertSame(instance1, instance2); 125 | } 126 | 127 | @Test 128 | public void testClearSCIMSystemAttributeSchemaByTenant_NonExistentTenant() { 129 | 130 | cache.clearSCIMSystemAttributeSchemaByTenant(2); 131 | // No exception should be thrown. 132 | } 133 | 134 | @Test 135 | public void testCacheKeyEquality() { 136 | 137 | SCIMSystemAttributeSchemaCacheKey key1 = new SCIMSystemAttributeSchemaCacheKey(1); 138 | SCIMSystemAttributeSchemaCacheKey key2 = new SCIMSystemAttributeSchemaCacheKey(1); 139 | SCIMSystemAttributeSchemaCacheKey key3 = new SCIMSystemAttributeSchemaCacheKey(2); 140 | 141 | assertEquals(key1, key2); 142 | assertNotEquals(key1, key3); 143 | } 144 | 145 | @Test 146 | public void testCacheKeyHashCode() { 147 | 148 | SCIMSystemAttributeSchemaCacheKey key1 = new SCIMSystemAttributeSchemaCacheKey(1); 149 | SCIMSystemAttributeSchemaCacheKey key2 = new SCIMSystemAttributeSchemaCacheKey(1); 150 | 151 | assertEquals(key1.hashCode(), key2.hashCode()); 152 | } 153 | 154 | @AfterClass 155 | public void tearDown() { 156 | 157 | scimCommonUtilsStaticMock.close(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /.github/workflows/coverage-generator.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage Generator 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # Daily 22:00 UTC (3.30 AM SL time). 7 | - cron: '00 22 * * *' 8 | 9 | jobs: 10 | build-source: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Adopt JDK 11 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: 11 21 | distribution: "adopt" 22 | 23 | - name: Build with Maven 24 | run: | 25 | mvn clean install -U -B -Dmaven.test.skip=true 26 | - name: Cache source code 27 | uses: actions/cache@v4 28 | with: 29 | path: . 30 | key: ${{ runner.os }}-source-${{ github.sha }} 31 | 32 | oidc-conformance-report: 33 | needs: build-source 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - name: Restore source code 38 | uses: actions/cache@v4 39 | with: 40 | path: . 41 | key: ${{ runner.os }}-source-${{ github.sha }} 42 | restore-keys: | 43 | ${{ runner.os }}-source- 44 | - name: Get the latest Jacoco report URL 45 | id: get-artifact-url-oidc 46 | run: | 47 | GITHUB_API_URL="https://api.github.com" 48 | OWNER="wso2" 49 | REPO="product-is" 50 | WORKFLOW_ID="oidc-conformance-test.yml" 51 | GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" 52 | # Get the latest successful workflow run 53 | WORKFLOW_RUNS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs?status=success&per_page=1") 54 | RUN_ID=$(echo $WORKFLOW_RUNS | jq -r '.workflow_runs[0].id') 55 | if [ "$RUN_ID" == "null" ]; then 56 | echo "No successful workflow runs found" 57 | exit 1 58 | fi 59 | # Get the artifacts for the workflow run 60 | ARTIFACTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/runs/$RUN_ID/artifacts") 61 | ARTIFACT_URL=$(echo $ARTIFACTS | jq -r '.artifacts[] | select(.name == "jacoco-xml") | .archive_download_url') 62 | if [ "$ARTIFACT_URL" == "null" ]; then 63 | echo "Artifact not found" 64 | exit 1 65 | fi 66 | echo "::set-output name=artifact-url::$ARTIFACT_URL" 67 | - name: Download latest Jacoco report 68 | run: | 69 | curl -L -o artifact-oidc.zip \ 70 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 71 | ${{ steps.get-artifact-url-oidc.outputs.artifact-url }} 72 | - name: Unzip Jacoco report 73 | run: | 74 | unzip artifact-oidc.zip -d ./artifacts-oidc 75 | - name: Upload coverage reports to Codecov for OIDC 76 | uses: codecov/codecov-action@v4 77 | with: 78 | token: ${{ secrets.CODECOV_TOKEN }} 79 | files: ./artifacts-oidc/jacoco.xml 80 | flags: conformance-oidc 81 | disable_search: true 82 | 83 | fapi-conformance-report: 84 | needs: build-source 85 | runs-on: ubuntu-latest 86 | 87 | steps: 88 | - name: Restore source code 89 | uses: actions/cache@v4 90 | with: 91 | path: . 92 | key: ${{ runner.os }}-source-${{ github.sha }} 93 | restore-keys: | 94 | ${{ runner.os }}-source- 95 | - name: Get the latest Jacoco report URL 96 | id: get-artifact-url-fapi 97 | run: | 98 | GITHUB_API_URL="https://api.github.com" 99 | OWNER="wso2" 100 | REPO="product-is" 101 | WORKFLOW_ID="fapi-oidc-conformance-test.yml" 102 | GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" 103 | # Get the latest successful workflow run 104 | WORKFLOW_RUNS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs?status=success&per_page=1") 105 | RUN_ID=$(echo $WORKFLOW_RUNS | jq -r '.workflow_runs[0].id') 106 | if [ "$RUN_ID" == "null" ]; then 107 | echo "No successful workflow runs found" 108 | exit 1 109 | fi 110 | # Get the artifacts for the workflow run 111 | ARTIFACTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/runs/$RUN_ID/artifacts") 112 | ARTIFACT_URL=$(echo $ARTIFACTS | jq -r '.artifacts[] | select(.name == "jacoco-xml") | .archive_download_url') 113 | if [ "$ARTIFACT_URL" == "null" ]; then 114 | echo "Artifact not found" 115 | exit 1 116 | fi 117 | echo "::set-output name=artifact-url::$ARTIFACT_URL" 118 | - name: Download the latest Jacoco report 119 | run: | 120 | curl -L -o artifact-fapi.zip \ 121 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 122 | ${{ steps.get-artifact-url-fapi.outputs.artifact-url }} 123 | - name: Unzip Jacoco report 124 | run: | 125 | unzip artifact-fapi.zip -d ./artifacts-fapi 126 | - name: Upload coverage reports to Codecov for FAPI 127 | uses: codecov/codecov-action@v4 128 | with: 129 | token: ${{ secrets.CODECOV_TOKEN }} 130 | files: ./artifacts-fapi/jacoco.xml 131 | flags: conformance-fapi 132 | disable_search: true 133 | 134 | integration-test-report: 135 | needs: build-source 136 | runs-on: ubuntu-latest 137 | 138 | steps: 139 | - name: Restore source code 140 | uses: actions/cache@v4 141 | with: 142 | path: . 143 | key: ${{ runner.os }}-source-${{ github.sha }} 144 | restore-keys: | 145 | ${{ runner.os }}-source- 146 | - name: Download integration Jacoco XML report 147 | run: | 148 | mkdir artifacts-integration 149 | curl -L -o ./artifacts-integration/jacoco.xml https://wso2.org/jenkins/job/products/job/product-is/lastSuccessfulBuild/artifact/modules/integration/tests-integration/tests-backend/target/jacoco/coverage/jacoco.xml 150 | - name: Upload coverage reports to Codecov for integration tests 151 | uses: codecov/codecov-action@v4 152 | with: 153 | token: ${{ secrets.CODECOV_TOKEN }} 154 | files: ./artifacts-integration/jacoco.xml 155 | flags: integration 156 | disable_search: true 157 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | org.wso2.carbon.identity.inbound.provisioning.scim2 23 | identity-inbound-provisioning-scim2 24 | ../../pom.xml 25 | 3.4.223-SNAPSHOT 26 | 27 | 28 | 4.0.0 29 | org.wso2.carbon.identity.scim2.provider 30 | WSO2 Carbon - SCIM 2.0 - Service Provider Component 31 | war 32 | 33 | 34 | 35 | javax.ws.rs 36 | javax.ws.rs-api 37 | provided 38 | 39 | 40 | org.apache.cxf 41 | cxf-core 42 | provided 43 | 44 | 45 | org.wso2.carbon 46 | org.wso2.carbon.utils 47 | provided 48 | 49 | 50 | org.wso2.carbon.identity.inbound.auth.oauth2 51 | org.wso2.carbon.identity.oauth.stub 52 | provided 53 | 54 | 55 | org.wso2.carbon.identity.inbound.auth.oauth2 56 | org.wso2.carbon.identity.oauth 57 | provided 58 | 59 | 60 | org.wso2.carbon.identity.framework 61 | org.wso2.carbon.identity.core 62 | provided 63 | 64 | 65 | org.wso2.carbon.identity.framework 66 | org.wso2.carbon.identity.provisioning 67 | provided 68 | 69 | 70 | org.wso2.carbon.identity.inbound.provisioning.scim2 71 | org.wso2.carbon.identity.scim2.common 72 | provided 73 | 74 | 75 | org.wso2.carbon.identity.framework 76 | org.wso2.carbon.user.mgt 77 | provided 78 | 79 | 80 | org.wso2.carbon.identity.framework 81 | org.wso2.carbon.identity.role.mgt.core 82 | provided 83 | 84 | 85 | org.wso2.carbon.identity.framework 86 | org.wso2.carbon.identity.role.v2.mgt.core 87 | provided 88 | 89 | 90 | org.wso2.carbon.identity.governance 91 | org.wso2.carbon.identity.recovery 92 | provided 93 | 94 | 95 | org.jacoco 96 | jacoco-maven-plugin 97 | ${jacoco.version} 98 | 99 | 100 | 101 | 102 | 103 | maven-compiler-plugin 104 | 105 | 1.8 106 | 1.8 107 | 108 | 2.3.2 109 | 110 | 111 | maven-war-plugin 112 | 3.1.0 113 | 114 | scim2 115 | 116 | 117 | 118 | org.apache.maven.plugins 119 | maven-compiler-plugin 120 | 121 | 1.8 122 | 1.8 123 | 124 | 125 | 126 | com.github.spotbugs 127 | spotbugs-maven-plugin 128 | 129 | Medium 130 | 131 | 132 | 133 | org.jacoco 134 | jacoco-maven-plugin 135 | ${jacoco.version} 136 | 137 | 138 | 139 | prepare-agent 140 | 141 | 142 | 143 | report 144 | test 145 | 146 | report 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/utils/SCIMConfigProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package org.wso2.carbon.identity.scim2.common.utils; 19 | 20 | import org.apache.axiom.om.OMElement; 21 | import org.apache.axiom.om.impl.builder.StAXOMBuilder; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.wso2.charon3.core.exceptions.CharonException; 25 | 26 | import javax.xml.namespace.QName; 27 | import javax.xml.stream.XMLStreamException; 28 | import java.io.File; 29 | import java.io.FileInputStream; 30 | import java.io.FileNotFoundException; 31 | import java.io.InputStream; 32 | import java.io.IOException; 33 | import java.util.ArrayList; 34 | import java.util.HashMap; 35 | import java.util.Iterator; 36 | import java.util.List; 37 | import java.util.Map; 38 | 39 | /** 40 | * Class responsible for building a programmatic representation of provisioning-config.xml. 41 | * Any application using this library can pass the file path 42 | * in expected format to get it parsed. 43 | */ 44 | public class SCIMConfigProcessor { 45 | 46 | private static SCIMConfigProcessor scimConfigProcessor = new SCIMConfigProcessor(); 47 | 48 | //map to keep the properties values 49 | Map properties = new HashMap(); 50 | //list to keep the authentication schemas 51 | List authenticationSchemas = null; 52 | 53 | private static final Log logger = LogFactory.getLog(SCIMConfigProcessor.class); 54 | 55 | public Map getProperties() { 56 | return properties; 57 | } 58 | 59 | public String getProperty(String property) { 60 | if (properties.get(property) != null) { 61 | return properties.get(property); 62 | } 63 | return null; 64 | } 65 | 66 | public List getAuthenticationSchemas() { 67 | return authenticationSchemas; 68 | } 69 | 70 | public void buildConfigFromFile(String filePath) throws CharonException { 71 | try { 72 | InputStream inputStream = null; 73 | File provisioningConfig = new File(filePath); 74 | if (provisioningConfig.exists()) { 75 | inputStream = new FileInputStream(provisioningConfig); 76 | StAXOMBuilder staxOMBuilder = new StAXOMBuilder(inputStream); 77 | OMElement documentElement = staxOMBuilder.getDocumentElement(); 78 | if (inputStream != null) { 79 | inputStream.close(); 80 | } 81 | buildConfigFromRootElement(documentElement); 82 | } else { 83 | throw new FileNotFoundException(); 84 | } 85 | } catch (FileNotFoundException e) { 86 | throw new CharonException(SCIMCommonConstants.CHARON_CONFIG_NAME + "not found."); 87 | } catch (XMLStreamException e) { 88 | throw new CharonException("Error in building the configuration file: " + 89 | SCIMCommonConstants.CHARON_CONFIG_NAME); 90 | } catch (IOException e) { 91 | throw new CharonException("Error in building the configuration file: " + 92 | SCIMCommonConstants.CHARON_CONFIG_NAME); 93 | } 94 | } 95 | 96 | private void buildConfigFromRootElement(OMElement rootElement) { 97 | 98 | 99 | //read any properties defined. 100 | Iterator propertiesIterator = rootElement.getChildrenWithName( 101 | new QName(SCIMCommonConstants.ELEMENT_NAME_PROPERTY)); 102 | 103 | while (propertiesIterator.hasNext()) { 104 | OMElement propertyElement = propertiesIterator.next(); 105 | String propertyName = propertyElement.getAttributeValue( 106 | new QName(SCIMCommonConstants.ATTRIBUTE_NAME_NAME)); 107 | String propertyValue = propertyElement.getText(); 108 | properties.put(propertyName, propertyValue); 109 | } 110 | 111 | OMElement scimAuthenticationSchemaElement = rootElement.getFirstChildWithName( 112 | new QName(SCIMCommonConstants.ELEMENT_NAME_AUTHENTICATION_SCHEMES)); 113 | 114 | //iterate over the individual elements and create authentication schema map. 115 | Iterator authenticationSchemasIterator = 116 | scimAuthenticationSchemaElement.getChildrenWithName(new QName(SCIMCommonConstants.ELEMENT_NAME_SCHEMA)); 117 | 118 | //build authentication schema map 119 | if (authenticationSchemasIterator != null) { 120 | authenticationSchemas = buildAuthenticationSchemasMap(authenticationSchemasIterator); 121 | } 122 | } 123 | 124 | 125 | private List buildAuthenticationSchemasMap 126 | (Iterator schemasIterator) { 127 | 128 | List schemasList = new ArrayList<>(); 129 | 130 | while (schemasIterator.hasNext()) { 131 | OMElement schemaElement = schemasIterator.next(); 132 | AuthenticationSchema authenticationSchema = new AuthenticationSchema(); 133 | Map propertiesMap = new HashMap(); 134 | 135 | //read schema properties 136 | Iterator propertiesIterator = schemaElement.getChildrenWithName( 137 | new QName(SCIMCommonConstants.ELEMENT_NAME_PROPERTY)); 138 | while (propertiesIterator.hasNext()) { 139 | OMElement propertyElement = propertiesIterator.next(); 140 | String propertyName = propertyElement.getAttributeValue( 141 | new QName(SCIMCommonConstants.ATTRIBUTE_NAME_NAME)); 142 | String propertyValue = propertyElement.getText(); 143 | propertiesMap.put(propertyName, propertyValue); 144 | } 145 | authenticationSchema.setProperties(propertiesMap); 146 | schemasList.add(authenticationSchema); 147 | } 148 | 149 | return schemasList; 150 | } 151 | 152 | public static SCIMConfigProcessor getInstance() { 153 | return scimConfigProcessor; 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/handlers/SCIMClaimOperationEventHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.handlers; 20 | 21 | import org.mockito.MockedStatic; 22 | import org.testng.annotations.AfterClass; 23 | import org.testng.annotations.BeforeClass; 24 | import org.testng.annotations.BeforeMethod; 25 | import org.testng.annotations.Test; 26 | import org.wso2.carbon.identity.common.testng.WithCarbonHome; 27 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 28 | import org.wso2.carbon.identity.event.IdentityEventConstants; 29 | import org.wso2.carbon.identity.event.IdentityEventException; 30 | import org.wso2.carbon.identity.event.event.Event; 31 | import org.wso2.carbon.identity.organization.management.service.util.Utils; 32 | import org.wso2.carbon.identity.scim2.common.cache.SCIMCustomAttributeSchemaCache; 33 | import org.wso2.carbon.identity.scim2.common.cache.SCIMSystemAttributeSchemaCache; 34 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants; 35 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 36 | import org.wso2.charon3.core.schema.AttributeSchema; 37 | 38 | import java.util.HashMap; 39 | import java.util.Map; 40 | 41 | import static org.mockito.Mockito.mock; 42 | import static org.mockito.Mockito.mockStatic; 43 | import static org.mockito.Mockito.when; 44 | import static org.testng.Assert.assertNotNull; 45 | import static org.testng.Assert.assertNull; 46 | import static org.wso2.carbon.identity.scim2.common.handlers.SCIMClaimOperationEventHandler.WSO2_CARBON_DIALECT; 47 | 48 | @WithCarbonHome 49 | public class SCIMClaimOperationEventHandlerTest { 50 | 51 | private SCIMClaimOperationEventHandler handler; 52 | private Event mockEvent; 53 | private MockedStatic identityTenantUtilStaticMock; 54 | private MockedStatic utilsStaticMock; 55 | 56 | private final String FOO_TENANT_DOMAIN = "foo.com"; 57 | 58 | @BeforeClass 59 | public void setUpTests() { 60 | 61 | identityTenantUtilStaticMock = mockStatic(IdentityTenantUtil.class); 62 | utilsStaticMock = mockStatic(Utils.class); 63 | identityTenantUtilStaticMock.when(() -> IdentityTenantUtil.getTenantDomain(1)).thenReturn(FOO_TENANT_DOMAIN); 64 | identityTenantUtilStaticMock.when(() -> IdentityTenantUtil.getTenantId(FOO_TENANT_DOMAIN)).thenReturn(1); 65 | utilsStaticMock.when(() -> Utils.isClaimAndOIDCScopeInheritanceEnabled(FOO_TENANT_DOMAIN)).thenReturn(false); 66 | } 67 | 68 | @BeforeMethod 69 | public void setUp() { 70 | 71 | handler = new SCIMClaimOperationEventHandler(); 72 | mockEvent = mock(Event.class); 73 | } 74 | 75 | @Test 76 | public void handleEvent_SCIMSystemUserClaimDialect_ClearsSystemCache() throws IdentityEventException { 77 | 78 | Map eventProperties = new HashMap<>(); 79 | eventProperties.put(IdentityEventConstants.EventProperty.TENANT_ID, 1); 80 | eventProperties.put(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI, SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT); 81 | when(mockEvent.getEventProperties()).thenReturn(eventProperties); 82 | 83 | SCIMSystemAttributeSchemaCache cache = SCIMSystemAttributeSchemaCache.getInstance(); 84 | cache.addSCIMSystemAttributeSchema(1, mock(AttributeSchema.class)); 85 | 86 | handler.handleEvent(mockEvent); 87 | 88 | assertNull(cache.getSCIMSystemAttributeSchemaByTenant(1)); 89 | } 90 | 91 | @Test 92 | public void handleEvent_CustomSchemaUri_ClearsCustomCache() throws IdentityEventException { 93 | 94 | Map eventProperties = new HashMap<>(); 95 | eventProperties.put(IdentityEventConstants.EventProperty.TENANT_ID, 1); 96 | eventProperties.put(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI, SCIMCommonUtils.getCustomSchemaURI()); 97 | when(mockEvent.getEventProperties()).thenReturn(eventProperties); 98 | 99 | SCIMCustomAttributeSchemaCache cache = SCIMCustomAttributeSchemaCache.getInstance(); 100 | cache.addSCIMCustomAttributeSchema(1, mock(AttributeSchema.class)); 101 | 102 | handler.handleEvent(mockEvent); 103 | 104 | assertNull(cache.getSCIMCustomAttributeSchemaByTenant(1)); 105 | } 106 | 107 | @Test 108 | public void handleEvent_LocalClaimUpdate_ClearsBothCaches() throws IdentityEventException { 109 | 110 | Map eventProperties = new HashMap<>(); 111 | eventProperties.put(IdentityEventConstants.EventProperty.TENANT_ID, 1); 112 | eventProperties.put(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI, WSO2_CARBON_DIALECT); 113 | when(mockEvent.getEventProperties()).thenReturn(eventProperties); 114 | 115 | SCIMSystemAttributeSchemaCache systemCache = SCIMSystemAttributeSchemaCache.getInstance(); 116 | SCIMCustomAttributeSchemaCache customCache = SCIMCustomAttributeSchemaCache.getInstance(); 117 | systemCache.addSCIMSystemAttributeSchema(1, mock(AttributeSchema.class)); 118 | customCache.addSCIMCustomAttributeSchema(1, mock(AttributeSchema.class)); 119 | 120 | handler.handleEvent(mockEvent); 121 | 122 | assertNull(systemCache.getSCIMSystemAttributeSchemaByTenant(1)); 123 | assertNull(customCache.getSCIMCustomAttributeSchemaByTenant(1)); 124 | } 125 | 126 | @Test 127 | public void handleEvent_CustomSchemaDisabled_DoesNotClearCache() throws IdentityEventException { 128 | 129 | Map eventProperties = new HashMap<>(); 130 | eventProperties.put(IdentityEventConstants.EventProperty.TENANT_ID, 1); 131 | eventProperties.put(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI, SCIMCommonUtils.getCustomSchemaURI()); 132 | when(mockEvent.getEventProperties()).thenReturn(eventProperties); 133 | 134 | MockedStatic mockUtils = mockStatic(SCIMCommonUtils.class); 135 | mockUtils.when(SCIMCommonUtils::isCustomSchemaEnabled).thenReturn(false); 136 | SCIMCustomAttributeSchemaCache cache = SCIMCustomAttributeSchemaCache.getInstance(); 137 | cache.addSCIMCustomAttributeSchema(1, mock(AttributeSchema.class)); 138 | 139 | handler.handleEvent(mockEvent); 140 | 141 | assertNotNull(cache.getSCIMCustomAttributeSchemaByTenant(1)); 142 | 143 | mockUtils.close(); 144 | } 145 | 146 | @AfterClass 147 | public void tearDown() { 148 | 149 | identityTenantUtilStaticMock.close(); 150 | utilsStaticMock.close(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.common.feature/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | org.wso2.carbon.identity.inbound.provisioning.scim2 23 | identity-inbound-provisioning-scim2 24 | ../../pom.xml 25 | 3.4.223-SNAPSHOT 26 | 27 | 28 | 4.0.0 29 | org.wso2.carbon.identity.scim2.common.feature 30 | pom 31 | WSO2 Carbon - SCIM2 - Common Feature 32 | http://wso2.org 33 | This feature contains the bundles required for the SCIM2 Common Feature 34 | 35 | 36 | 37 | org.wso2.carbon.identity.inbound.provisioning.scim2 38 | org.wso2.carbon.identity.scim2.common 39 | 40 | 41 | org.wso2.charon 42 | org.wso2.charon3.core 43 | 44 | 45 | org.jacoco 46 | jacoco-maven-plugin 47 | ${jacoco.version} 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-compiler-plugin 56 | 57 | UTF-8 58 | 1.8 59 | 1.8 60 | 61 | 62 | 63 | maven-resources-plugin 64 | 65 | 66 | copy-resources 67 | generate-resources 68 | 69 | copy-resources 70 | 71 | 72 | src/main/resources 73 | 74 | 75 | resources 76 | 77 | charon-config.xml 78 | scim2-schema-extension.config 79 | p2.inf 80 | charon-config.xml.j2 81 | *.json 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.wso2.maven 91 | carbon-p2-plugin 92 | ${carbon.p2.plugin.version} 93 | 94 | 95 | p2-feature-generation 96 | package 97 | 98 | p2-feature-gen 99 | 100 | 101 | org.wso2.carbon.identity.scim2.common 102 | ../etc/feature.properties 103 | 104 | 105 | org.wso2.carbon.p2.category.type:server 106 | org.eclipse.equinox.p2.type.group:false 107 | 108 | 109 | 110 | org.wso2.charon:org.wso2.charon3.core 111 | org.wso2.carbon.identity.inbound.provisioning.scim2:org.wso2.carbon.identity.scim2.common 112 | 113 | 114 | 115 | 116 | 117 | 118 | org.apache.maven.plugins 119 | maven-antrun-plugin 120 | 1.1 121 | 122 | 123 | clean_target 124 | install 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | run 134 | 135 | 136 | 137 | 138 | 139 | org.jacoco 140 | jacoco-maven-plugin 141 | ${jacoco.version} 142 | 143 | 144 | 145 | prepare-agent 146 | 147 | 148 | 149 | report 150 | test 151 | 152 | report 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/test/java/org/wso2/carbon/identity/scim2/common/utils/AdminAttributeUtilTestForGroup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.utils; 20 | 21 | import org.mockito.ArgumentCaptor; 22 | import org.mockito.Mock; 23 | import org.mockito.MockedConstruction; 24 | import org.mockito.MockedStatic; 25 | import org.testng.annotations.AfterMethod; 26 | import org.testng.annotations.BeforeMethod; 27 | import org.testng.annotations.DataProvider; 28 | import org.testng.annotations.Test; 29 | import org.wso2.carbon.CarbonConstants; 30 | import org.wso2.carbon.identity.core.util.IdentityTenantUtil; 31 | import org.wso2.carbon.identity.core.util.IdentityUtil; 32 | import org.wso2.carbon.identity.scim2.common.exceptions.IdentitySCIMException; 33 | import org.wso2.carbon.identity.scim2.common.group.SCIMGroupHandler; 34 | import org.wso2.carbon.identity.scim2.common.internal.component.SCIMCommonComponentHolder; 35 | import org.wso2.carbon.stratos.common.util.ClaimsMgtUtil; 36 | import org.wso2.carbon.user.api.RealmConfiguration; 37 | import org.wso2.carbon.user.api.UserRealm; 38 | import org.wso2.carbon.user.core.common.AbstractUserStoreManager; 39 | import org.wso2.carbon.user.core.service.RealmService; 40 | import org.wso2.carbon.user.core.util.UserCoreUtil; 41 | 42 | import static org.mockito.ArgumentMatchers.any; 43 | 44 | import static org.mockito.ArgumentMatchers.anyInt; 45 | import static org.mockito.ArgumentMatchers.anyString; 46 | import static org.mockito.Mockito.mockConstruction; 47 | import static org.mockito.Mockito.mockStatic; 48 | import static org.mockito.Mockito.verify; 49 | import static org.mockito.Mockito.when; 50 | import static org.mockito.MockitoAnnotations.initMocks; 51 | import static org.testng.Assert.assertEquals; 52 | 53 | public class AdminAttributeUtilTestForGroup { 54 | 55 | @Mock 56 | RealmService realmService; 57 | 58 | @Mock 59 | RealmConfiguration realmConfiguration; 60 | 61 | @Mock 62 | UserRealm userRealm; 63 | 64 | @Mock 65 | AbstractUserStoreManager userStoreManager; 66 | 67 | @Mock 68 | SCIMGroupHandler scimGroupHandler; 69 | 70 | AdminAttributeUtil adminAttributeUtil; 71 | 72 | private MockedStatic scimCommonComponentHolder; 73 | private MockedStatic claimsMgtUtil; 74 | private MockedStatic identityTenantUtil; 75 | private MockedStatic userCoreUtil; 76 | private MockedStatic identityUtil; 77 | private MockedStatic scimCommonUtils; 78 | 79 | @BeforeMethod 80 | public void setUp() throws Exception { 81 | initMocks(this); 82 | adminAttributeUtil = new AdminAttributeUtil(); 83 | scimCommonComponentHolder = mockStatic(SCIMCommonComponentHolder.class); 84 | claimsMgtUtil = mockStatic(ClaimsMgtUtil.class); 85 | identityTenantUtil = mockStatic(IdentityTenantUtil.class); 86 | userCoreUtil = mockStatic(UserCoreUtil.class); 87 | identityUtil = mockStatic(IdentityUtil.class); 88 | scimCommonUtils = mockStatic(SCIMCommonUtils.class); 89 | } 90 | 91 | @AfterMethod 92 | public void tearDown() { 93 | scimCommonComponentHolder.close(); 94 | claimsMgtUtil.close(); 95 | identityTenantUtil.close(); 96 | userCoreUtil.close(); 97 | identityUtil.close(); 98 | scimCommonUtils.close(); 99 | } 100 | 101 | @DataProvider(name = "testUpdateAdminUserData") 102 | public Object[][] testUpdateAdminUserData() { 103 | return new Object[][]{ 104 | {true}, 105 | {false} 106 | }; 107 | } 108 | 109 | @DataProvider(name = "testUpdateAdminGroupData") 110 | public Object[][] testUpdateAdminGroupData() { 111 | return new Object[][]{ 112 | {"testDomain"}, 113 | {null} 114 | }; 115 | } 116 | 117 | @Test(dataProvider = "testUpdateAdminGroupData") 118 | public void testUpdateAdminGroup(String domainName) throws Exception { 119 | String roleNameWithDomain = "TESTDOMAIN/admin"; 120 | 121 | scimCommonComponentHolder.when(() -> SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 122 | when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm); 123 | when(userRealm.getUserStoreManager()).thenReturn(userStoreManager); 124 | when(userStoreManager.isSCIMEnabled()).thenReturn(true); 125 | when(userStoreManager.getTenantId()).thenReturn(1); 126 | when(userStoreManager.getRealmConfiguration()).thenReturn(realmConfiguration); 127 | when(userStoreManager.isRoleAndGroupSeparationEnabled()).thenReturn(true); 128 | when(realmConfiguration.getAdminRoleName()).thenReturn("admin"); 129 | userCoreUtil.when(() -> UserCoreUtil.getDomainName((RealmConfiguration) any())).thenReturn(domainName); 130 | identityUtil.when(() -> IdentityUtil.getPrimaryDomainName()).thenReturn("TESTDOMAIN"); 131 | userCoreUtil.when(() -> UserCoreUtil.addDomainToName(anyString(), anyString())).thenReturn(roleNameWithDomain); 132 | scimCommonUtils.when(() -> SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn(roleNameWithDomain); 133 | 134 | try (MockedConstruction mocked = mockConstruction(SCIMGroupHandler.class, (mock, context) -> { 135 | when(mock.isGroupExisting(anyString())).thenReturn(false); 136 | })) { 137 | ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); 138 | CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = true; 139 | adminAttributeUtil.updateAdminGroup(1); 140 | verify(mocked.constructed().get(0)).addMandatoryAttributes(argument.capture()); 141 | 142 | assertEquals(argument.getValue(), roleNameWithDomain); 143 | } 144 | } 145 | 146 | @Test(expectedExceptions = IdentitySCIMException.class) 147 | public void testUpdateAdminGroup1() throws Exception { 148 | String roleNameWithDomain = "TESTDOMAIN/admin"; 149 | 150 | scimCommonComponentHolder.when(() -> SCIMCommonComponentHolder.getRealmService()).thenReturn(realmService); 151 | when(realmService.getTenantUserRealm(anyInt())).thenReturn(userRealm); 152 | when(userRealm.getUserStoreManager()).thenReturn(userStoreManager); 153 | when(userStoreManager.isSCIMEnabled()).thenReturn(true); 154 | when(userStoreManager.getTenantId()).thenReturn(1); 155 | when(userStoreManager.getRealmConfiguration()).thenReturn(realmConfiguration); 156 | when(realmConfiguration.getAdminRoleName()).thenReturn("admin"); 157 | userCoreUtil.when(() -> UserCoreUtil.getDomainName((RealmConfiguration) any())).thenReturn("testDomain"); 158 | identityUtil.when(() -> IdentityUtil.getPrimaryDomainName()).thenReturn("TESTDOMAIN"); 159 | userCoreUtil.when(() -> UserCoreUtil.addDomainToName(anyString(), anyString())).thenReturn(roleNameWithDomain); 160 | scimCommonUtils.when(() -> SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn(roleNameWithDomain); 161 | when(scimGroupHandler.isGroupExisting(anyString())).thenThrow(new IdentitySCIMException("testException")); 162 | 163 | adminAttributeUtil.updateAdminGroup(1); 164 | verify(scimGroupHandler.isGroupExisting(anyString())); 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /features/org.wso2.carbon.identity.scim2.provider.feature/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | org.wso2.carbon.identity.inbound.provisioning.scim2 23 | identity-inbound-provisioning-scim2 24 | ../../pom.xml 25 | 3.4.223-SNAPSHOT 26 | 27 | 28 | 4.0.0 29 | org.wso2.carbon.identity.scim2.provider.feature 30 | pom 31 | WSO2 Carbon - SCIM2 - Service Provider Feature 32 | http://wso2.org 33 | This feature contains the bundles required for the SCIM2 Service Provider Feature 34 | 35 | 36 | 37 | org.wso2.carbon.identity.inbound.provisioning.scim2 38 | org.wso2.carbon.identity.scim2.provider 39 | war 40 | 41 | 42 | org.jacoco 43 | jacoco-maven-plugin 44 | ${jacoco.version} 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 54 | UTF-8 55 | 1.8 56 | 1.8 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-dependency-plugin 62 | 2.4 63 | 64 | 65 | copy 66 | package 67 | 68 | copy 69 | 70 | 71 | 72 | 73 | org.wso2.carbon.identity.inbound.provisioning.scim2 74 | org.wso2.carbon.identity.scim2.provider 75 | war 76 | true 77 | ${basedir}/src/main/resources/ 78 | scim2.war 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | maven-resources-plugin 87 | 88 | 89 | copy-resources 90 | generate-resources 91 | 92 | copy-resources 93 | 94 | 95 | src/main/resources 96 | 97 | 98 | resources 99 | 100 | scim2.war 101 | p2.inf 102 | build.properties 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.wso2.maven 112 | carbon-p2-plugin 113 | ${carbon.p2.plugin.version} 114 | 115 | 116 | p2-feature-generation 117 | package 118 | 119 | p2-feature-gen 120 | 121 | 122 | org.wso2.carbon.identity.scim2.provider 123 | ../etc/feature.properties 124 | 125 | 126 | org.wso2.carbon.p2.category.type:server 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-antrun-plugin 136 | 1.1 137 | 138 | 139 | clean_target 140 | install 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | run 150 | 151 | 152 | 153 | 154 | 155 | org.jacoco 156 | jacoco-maven-plugin 157 | ${jacoco.version} 158 | 159 | 160 | 161 | prepare-agent 162 | 163 | 164 | 165 | report 166 | test 167 | 168 | report 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.common/src/main/java/org/wso2/carbon/identity/scim2/common/handlers/SCIMClaimOperationEventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.common.handlers; 20 | 21 | import org.apache.commons.lang.StringUtils; 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; 25 | import org.wso2.carbon.identity.claim.metadata.mgt.util.DialectConfigParser; 26 | import org.wso2.carbon.identity.core.util.IdentityUtil; 27 | import org.wso2.carbon.identity.event.IdentityEventConstants; 28 | import org.wso2.carbon.identity.event.IdentityEventException; 29 | import org.wso2.carbon.identity.event.event.Event; 30 | import org.wso2.carbon.identity.event.handler.AbstractEventHandler; 31 | import org.wso2.carbon.identity.scim2.common.cache.SCIMAgentAttributeSchemaCache; 32 | import org.wso2.carbon.identity.scim2.common.cache.SCIMCustomAttributeSchemaCache; 33 | import org.wso2.carbon.identity.scim2.common.cache.SCIMSystemAttributeSchemaCache; 34 | import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils; 35 | 36 | import java.util.Arrays; 37 | import java.util.Collections; 38 | import java.util.HashSet; 39 | import java.util.Set; 40 | 41 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.SCIM_AGENT_CLAIM_DIALECT; 42 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.SCIM_CORE_CLAIM_DIALECT; 43 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.SCIM_ENTERPRISE_USER_CLAIM_DIALECT; 44 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.SCIM_SYSTEM_USER_CLAIM_DIALECT; 45 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonConstants.SCIM_USER_CLAIM_DIALECT; 46 | import static org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils.getCustomSchemaURI; 47 | 48 | /** 49 | * This handles the claim metadata operation related events and it will clear the SCIMCustomAttributeSchema 50 | * cache when the event is triggered. This depends on the local claim update, external claim on custom schema 51 | * related operations and deleting of the custom schema. When these relevant events are fired the cache will be 52 | * cleared based on the tenant and the cache will be rebuilt with the next SCIM api request. 53 | */ 54 | public class SCIMClaimOperationEventHandler extends AbstractEventHandler { 55 | 56 | private static final Log log = LogFactory.getLog(SCIMClaimOperationEventHandler.class); 57 | public static final String WSO2_CARBON_DIALECT = "http://wso2.org/claims"; 58 | 59 | private static final Set scimClaimDialects = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( 60 | SCIM_CORE_CLAIM_DIALECT, 61 | SCIM_USER_CLAIM_DIALECT, 62 | SCIM_ENTERPRISE_USER_CLAIM_DIALECT, 63 | SCIM_SYSTEM_USER_CLAIM_DIALECT 64 | ))); 65 | 66 | /** 67 | * This handles the claim related operations that are subscribed and clear the SCIMCustomAttributeSchema which 68 | * contains the all the custom attributes belong to the custom schema of the tenant. 69 | * 70 | * @param event Event. 71 | * @throws IdentityEventException 72 | */ 73 | @Override 74 | public void handleEvent(Event event) throws IdentityEventException { 75 | 76 | int tenantId = (int) event.getEventProperties().get(IdentityEventConstants.EventProperty.TENANT_ID); 77 | if (log.isDebugEnabled()) { 78 | log.debug(event.getEventName() + " event received to SCIMClaimOperationEventHandler for the tenant with " + 79 | "Id: " + tenantId); 80 | } 81 | 82 | if (IdentityEventConstants.Event.PRE_ADD_EXTERNAL_CLAIM.equals(event.getEventName())) { 83 | handleSCIMExternalClaimAddEvent(event); 84 | } 85 | 86 | if (!SCIMCommonUtils.isCustomSchemaEnabled()) { 87 | if (log.isDebugEnabled()) { 88 | log.debug("SCIM2 Custom user schema has disabled in server level."); 89 | } 90 | return; 91 | } 92 | 93 | String claimDialectUri = 94 | (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI); 95 | if (!getCustomSchemaURI().equalsIgnoreCase(claimDialectUri) && 96 | !SCIM_SYSTEM_USER_CLAIM_DIALECT.equals(claimDialectUri) && 97 | !WSO2_CARBON_DIALECT.equalsIgnoreCase(claimDialectUri) && 98 | !SCIM_AGENT_CLAIM_DIALECT.equals(claimDialectUri)){ 99 | if (log.isDebugEnabled()) { 100 | String message = "The event triggered in the tenant %s is not related to either local dialect or " + 101 | "SCIM2 system or custom or agent schema dialect. Hence, we skip the logic of clearing the cache."; 102 | log.debug(String.format(message, tenantId)); 103 | } 104 | return; 105 | } 106 | // If claim dialect rename happens, then we need to check whether the custom schema has renamed to another name. 107 | String oldClaimDialectUri = 108 | (String) event.getEventProperties().get(IdentityEventConstants.EventProperty.OLD_CLAIM_DIALECT_URI); 109 | if (StringUtils.isNotBlank(oldClaimDialectUri) 110 | && !oldClaimDialectUri.equalsIgnoreCase(getCustomSchemaURI()) 111 | && !oldClaimDialectUri.equalsIgnoreCase(SCIM_AGENT_CLAIM_DIALECT)) { 112 | if (log.isDebugEnabled()) { 113 | log.debug("Needs to clear the cache only if the SCIM2 custom or agent schema has changed"); 114 | } 115 | return; 116 | } 117 | 118 | if (SCIM_SYSTEM_USER_CLAIM_DIALECT.equalsIgnoreCase(claimDialectUri)) { 119 | SCIMSystemAttributeSchemaCache.getInstance().clearSCIMSystemAttributeSchemaByTenant(tenantId); 120 | } else if(SCIM_AGENT_CLAIM_DIALECT.equalsIgnoreCase(claimDialectUri)){ 121 | // Clear the SCIM Agent Attribute Schema cache. 122 | SCIMAgentAttributeSchemaCache.getInstance().clearSCIMAgentAttributeSchemaByTenant(tenantId); 123 | } 124 | else if (getCustomSchemaURI().equalsIgnoreCase(claimDialectUri)) { 125 | SCIMCustomAttributeSchemaCache.getInstance().clearSCIMCustomAttributeSchemaByTenant(tenantId); 126 | } 127 | 128 | // It is a local claim update. Clear all dynamic claim caches. 129 | SCIMSystemAttributeSchemaCache.getInstance().clearSCIMSystemAttributeSchemaByTenant(tenantId); 130 | SCIMCustomAttributeSchemaCache.getInstance().clearSCIMCustomAttributeSchemaByTenant(tenantId); 131 | SCIMAgentAttributeSchemaCache.getInstance().clearSCIMAgentAttributeSchemaByTenant(tenantId); 132 | } 133 | 134 | @Override 135 | public String getName() { 136 | 137 | return "SCIMClaimOperationEventHandler"; 138 | } 139 | 140 | private void handleSCIMExternalClaimAddEvent(Event event) { 141 | 142 | if (!IdentityEventConstants.Event.PRE_ADD_EXTERNAL_CLAIM.equals(event.getEventName())) { 143 | return; 144 | } 145 | 146 | String claimDialectUri = (String) event.getEventProperties() 147 | .get(IdentityEventConstants.EventProperty.CLAIM_DIALECT_URI); 148 | String externalClaimUri = (String) event.getEventProperties() 149 | .get(IdentityEventConstants.EventProperty.EXTERNAL_CLAIM_URI); 150 | 151 | /* 152 | * All spec-defined claims might not be added to dialects as external claims. All supported claims can be found 153 | * through the schemas.profile config defined in the identity.xml. The DialectConfigParser is used to read the 154 | * final server supported claims. 155 | */ 156 | if (DialectConfigParser.getInstance().getClaimsMap().get(externalClaimUri) != null 157 | && DialectConfigParser.getInstance().getClaimsMap().get(externalClaimUri).equals(claimDialectUri)) { 158 | return; 159 | } 160 | 161 | IdentityUtil.threadLocalProperties.get().remove(ClaimConstants.EXTERNAL_CLAIM_ADDITION_NOT_ALLOWED_FOR_DIALECT); 162 | if (scimClaimDialects.contains(claimDialectUri)) { 163 | /* 164 | * If the claim dialect is a spec-defined SCIM dialect or system schema, then we need to prevent adding 165 | * custom external claims to it. Otherwise, it could lead to a spec violation or conflict. 166 | * Here, by setting the thread local property, we communicate to the ClaimMetadataManagementService 167 | * that claim addition should be prevented. 168 | */ 169 | IdentityUtil.threadLocalProperties.get() 170 | .put(ClaimConstants.EXTERNAL_CLAIM_ADDITION_NOT_ALLOWED_FOR_DIALECT, true); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /components/org.wso2.carbon.identity.scim2.provider/src/main/java/org/wso2/carbon/identity/scim2/provider/resources/BulkResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package org.wso2.carbon.identity.scim2.provider.resources; 20 | 21 | import org.wso2.carbon.identity.authorization.common.AuthorizationUtil; 22 | import org.wso2.carbon.identity.authorization.common.exception.ForbiddenException; 23 | import org.wso2.carbon.identity.core.context.IdentityContext; 24 | import org.wso2.carbon.identity.core.context.model.Flow; 25 | import org.wso2.carbon.identity.scim2.common.impl.IdentitySCIMManager; 26 | import org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants; 27 | import org.wso2.carbon.identity.scim2.provider.util.SupportUtils; 28 | import org.wso2.charon3.core.encoder.JSONDecoder; 29 | import org.wso2.charon3.core.exceptions.BadRequestException; 30 | import org.wso2.charon3.core.exceptions.CharonException; 31 | import org.wso2.charon3.core.exceptions.FormatNotSupportedException; 32 | import org.wso2.charon3.core.exceptions.InternalErrorException; 33 | import org.wso2.charon3.core.exceptions.PayloadTooLargeException; 34 | import org.wso2.charon3.core.extensions.RoleManager; 35 | import org.wso2.charon3.core.extensions.RoleV2Manager; 36 | import org.wso2.charon3.core.extensions.UserManager; 37 | import org.wso2.charon3.core.objects.bulk.BulkRequestContent; 38 | import org.wso2.charon3.core.objects.bulk.BulkRequestData; 39 | import org.wso2.charon3.core.objects.bulk.BulkResponseContent; 40 | import org.wso2.charon3.core.objects.bulk.BulkResponseData; 41 | import org.wso2.charon3.core.protocol.SCIMResponse; 42 | import org.wso2.charon3.core.protocol.endpoints.AbstractResourceManager; 43 | import org.wso2.charon3.core.protocol.endpoints.BulkResourceManager; 44 | 45 | import java.util.ArrayList; 46 | import java.util.List; 47 | 48 | import javax.ws.rs.*; 49 | import javax.ws.rs.core.Response; 50 | 51 | import static org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants.BULK_CREATE_ROLE_OPERATION_NAME; 52 | import static org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants.BULK_DELETE_ROLE_OPERATION_NAME; 53 | import static org.wso2.carbon.identity.scim2.provider.util.SCIMProviderConstants.BULK_UPDATE_ROLE_OPERATION_NAME; 54 | 55 | @Path("/") 56 | public class BulkResource extends AbstractResource { 57 | 58 | @POST 59 | public Response createUser(@HeaderParam(SCIMProviderConstants.CONTENT_TYPE) String inputFormat, 60 | @HeaderParam(SCIMProviderConstants.ACCEPT_HEADER) String outputFormat, 61 | String resourceString) { 62 | try { 63 | SupportUtils.enterFlow(Flow.Name.BULK_RESOURCE_UPDATE); 64 | // content-type header is compulsory in post request. 65 | if (inputFormat == null) { 66 | String error = SCIMProviderConstants.CONTENT_TYPE 67 | + " not present in the request header"; 68 | throw new FormatNotSupportedException(error); 69 | } 70 | 71 | if (!isValidInputFormat(inputFormat)) { 72 | String error = inputFormat + " is not supported."; 73 | throw new FormatNotSupportedException(error); 74 | } 75 | 76 | if (!isValidOutputFormat(outputFormat)) { 77 | String error = outputFormat + " is not supported."; 78 | throw new FormatNotSupportedException(error); 79 | } 80 | 81 | // Obtain the user store manager. 82 | UserManager userManager = IdentitySCIMManager.getInstance().getUserManager(); 83 | // Obtain the role manager. 84 | RoleManager roleManager = IdentitySCIMManager.getInstance().getRoleManager(); 85 | // Obtain the role v2 manager. 86 | RoleV2Manager roleV2Manager = IdentitySCIMManager.getInstance().getRoleV2Manager(); 87 | 88 | List authorizedRoleV2BulkOperations = new ArrayList<>(); 89 | List unauthorizedRoleV2BulkOperations = new ArrayList<>(); 90 | 91 | // create charon-SCIM bulk endpoint and hand-over the request. 92 | BulkResourceManager bulkResourceManager = new BulkResourceManager(); 93 | BulkRequestData bulkRequestData; 94 | try { 95 | bulkRequestData = validateOperationScopes(resourceString, bulkResourceManager, 96 | unauthorizedRoleV2BulkOperations, authorizedRoleV2BulkOperations); 97 | 98 | // Call for process bulk data. 99 | BulkResponseData bulkResponseData = 100 | bulkResourceManager.processBulkData(bulkRequestData, userManager, roleManager, roleV2Manager); 101 | addUnauthorizedOperationsToResponse(bulkResponseData, unauthorizedRoleV2BulkOperations); 102 | SCIMResponse scimResponse = bulkResourceManager.getEncodeSCIMResponse(bulkResponseData); 103 | // Needs to check the code of the response and return 200 0k or other error codes appropriately. 104 | return SupportUtils.buildResponse(scimResponse); 105 | } catch (CharonException | BadRequestException | PayloadTooLargeException | InternalErrorException e) { 106 | return SupportUtils.buildResponse(AbstractResourceManager.encodeSCIMException(e)); 107 | } 108 | 109 | } catch (CharonException e) { 110 | return handleCharonException(e); 111 | } catch (FormatNotSupportedException e) { 112 | return handleFormatNotSupportedException(e); 113 | } finally { 114 | IdentityContext.getThreadLocalIdentityContext().exitFlow(); 115 | } 116 | } 117 | 118 | private BulkRequestData validateOperationScopes(String resourceString, BulkResourceManager bulkResourceManager, 119 | List unAuthorizedBulkOperations, 120 | List authorizedBulkOperations) 121 | throws BadRequestException, CharonException { 122 | 123 | BulkRequestData bulkRequestData = bulkResourceManager.getDecodeBulkRequest(resourceString); 124 | if (!bulkRequestData.getRoleV2OperationRequests().isEmpty()) { 125 | for (BulkRequestContent bulkRequestContent : bulkRequestData.getRoleV2OperationRequests()) { 126 | String operationName = getOperationNameForMethod(bulkRequestContent.getMethod()); 127 | if (operationName != null) { 128 | try { 129 | AuthorizationUtil.validateOperationScopes(operationName); 130 | authorizedBulkOperations.add(bulkRequestContent); 131 | } catch (ForbiddenException e) { 132 | unAuthorizedBulkOperations.add(bulkRequestContent); 133 | } 134 | } 135 | } 136 | bulkRequestData.setRoleV2OperationRequests(authorizedBulkOperations); 137 | } 138 | return bulkRequestData; 139 | } 140 | 141 | private String getOperationNameForMethod(String method) { 142 | 143 | switch (method.toUpperCase()) { 144 | case HttpMethod.POST: 145 | return BULK_CREATE_ROLE_OPERATION_NAME; 146 | case HttpMethod.PUT: 147 | case HttpMethod.PATCH: 148 | return BULK_UPDATE_ROLE_OPERATION_NAME; 149 | case HttpMethod.DELETE: 150 | return BULK_DELETE_ROLE_OPERATION_NAME; 151 | default: 152 | return null; 153 | } 154 | } 155 | 156 | private void addUnauthorizedOperationsToResponse(BulkResponseData bulkResponseData, 157 | List unauthorizedBulkOperations) { 158 | 159 | if (!unauthorizedBulkOperations.isEmpty()) { 160 | for (BulkRequestContent bulkRequestContent : unauthorizedBulkOperations) { 161 | bulkResponseData.addRoleOperation(buildForbiddenBulkResponseContent(bulkRequestContent)); 162 | } 163 | } 164 | } 165 | 166 | private BulkResponseContent buildForbiddenBulkResponseContent(BulkRequestContent bulkRequestContent) { 167 | 168 | BulkResponseContent bulkResponseContent = new BulkResponseContent(); 169 | bulkResponseContent.setBulkID(bulkRequestContent.getBulkID()); 170 | bulkResponseContent.setMethod(bulkRequestContent.getMethod()); 171 | bulkResponseContent.setScimResponse( 172 | AbstractResourceManager.encodeSCIMException( 173 | new org.wso2.charon3.core.exceptions.ForbiddenException( 174 | "Operation is not permitted. You do not have permissions to make this request.", 175 | "You do not have permission to perform this operation."))); 176 | return bulkResponseContent; 177 | } 178 | } 179 | 180 | --------------------------------------------------------------------------------