diff --git a/LICENSE.txt b/LICENSE.txt
index f5bda5ddbf0..f149a17df4b 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -727,7 +727,8 @@ org.wso2.carbon.identity.api.server.notification.sender.v1-1.2.61.jar
org.wso2.carbon.identity.api.server.fetch.remote.common-1.2.61.jar bundle apache2
org.wso2.carbon.identity.api.user.functionality.common-1.3.13.jar bundle apache2
org.wso2.carbon.identity.api.user.idv.common-1.3.13.jar bundle apache2
-org.wso2.carbon.identity.rest.api.user.recovery.v1-1.3.13.jar bundle apache2
+org.wso2.carbon.identity.rest.api.user.recovery.v1-1.3.20.jar bundle apache2
+org.wso2.carbon.identity.rest.api.user.recovery.v2-1.3.20.jar bundle apache2
org.wso2.carbon.identity.api.server.idv.provider.common-1.2.61.jar bundle apache2
org.wso2.carbon.identity.rest.api.user.authorized.apps.v2-1.3.13.jar bundle apache2
org.wso2.carbon.identity.api.server.identity.governance.common-1.2.61.jar bundle apache2
@@ -758,7 +759,7 @@ org.wso2.carbon.identity.rest.api.user.backupcode.v1-1.3.13.jar
org.wso2.carbon.api.server.consent.mgt-2.5.2.jar bundle apache2
org.wso2.carbon.identity.api.server.oidc.scope.management.common-1.2.61.jar bundle apache2
org.wso2.carbon.identity.api.server.script.library.common-1.2.61.jar bundle apache2
-org.wso2.carbon.identity.api.user.recovery.commons-1.3.13.jar bundle apache2
+org.wso2.carbon.identity.api.user.recovery.commons-1.3.20.jar bundle apache2
org.wso2.carbon.identity.api.server.branding.preference.management.v1-1.2.61.jar bundle apache2
org.wso2.carbon.identity.api.server.authenticators.v1-1.2.61.jar bundle apache2
org.wso2.carbon.identity.api.server.configs.v1-1.2.61.jar bundle apache2
diff --git a/modules/api-resources/api-resources-full/pom.xml b/modules/api-resources/api-resources-full/pom.xml
index 3ec1977ac7b..5a1a77d4e5f 100644
--- a/modules/api-resources/api-resources-full/pom.xml
+++ b/modules/api-resources/api-resources-full/pom.xml
@@ -23,12 +23,12 @@
org.wso2.is
api-resources
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
api-resources-full
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
war
WSO2 Identity Server - All Rest API
@@ -138,6 +138,10 @@
org.wso2.carbon.identity.user.api
org.wso2.carbon.identity.rest.api.user.recovery.v1
+
+ org.wso2.carbon.identity.user.api
+ org.wso2.carbon.identity.rest.api.user.recovery.v2
+
org.wso2.carbon.identity.user.api
org.wso2.carbon.identity.api.user.recovery.commons
@@ -239,6 +243,15 @@
org.wso2.carbon.identity.api.user.organization.common
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.api.resource.v1
+
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.api.resource.common
+
+
org.wso2.carbon.identity.server.api
@@ -480,5 +493,13 @@
org.wso2.carbon.identity.server.api
org.wso2.carbon.identity.api.server.organization.role.management.common
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.organization.configs.v1
+
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.organization.configs.common
+
diff --git a/modules/api-resources/api-resources-full/src/main/webapp/WEB-INF/beans.xml b/modules/api-resources/api-resources-full/src/main/webapp/WEB-INF/beans.xml
index abcc417502e..4a2e1121bd2 100644
--- a/modules/api-resources/api-resources-full/src/main/webapp/WEB-INF/beans.xml
+++ b/modules/api-resources/api-resources-full/src/main/webapp/WEB-INF/beans.xml
@@ -40,6 +40,7 @@
+
@@ -58,6 +59,7 @@
+
@@ -74,6 +76,7 @@
+
@@ -116,7 +119,10 @@
+
+
+
@@ -216,6 +222,7 @@
+
diff --git a/modules/api-resources/pom.xml b/modules/api-resources/pom.xml
index 0c6b3cb6c44..9f52293756a 100644
--- a/modules/api-resources/pom.xml
+++ b/modules/api-resources/pom.xml
@@ -23,12 +23,12 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
api-resources
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
pom
WSO2 Identity Server - Rest API
@@ -228,6 +228,11 @@
org.wso2.carbon.identity.rest.api.user.recovery.v1
${identity.user.api.version}
+
+ org.wso2.carbon.identity.user.api
+ org.wso2.carbon.identity.rest.api.user.recovery.v2
+ ${identity.user.api.version}
+
org.wso2.carbon.identity.user.api
org.wso2.carbon.identity.api.user.recovery.commons
@@ -480,6 +485,16 @@
org.wso2.carbon.identity.api.server.organization.user.invitation.management.common
${identity.server.api.version}
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.organization.configs.v1
+ ${identity.server.api.version}
+
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.organization.configs.common
+ ${identity.server.api.version}
+
org.wso2.carbon.identity.server.api
org.wso2.carbon.identity.api.expired.password.identification.v1
@@ -490,6 +505,16 @@
org.wso2.carbon.identity.api.expired.password.identification.common
${identity.server.api.version}
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.api.resource.v1
+ ${identity.server.api.version}
+
+
+ org.wso2.carbon.identity.server.api
+ org.wso2.carbon.identity.api.server.api.resource.common
+ ${identity.server.api.version}
+
diff --git a/modules/authenticators/pom.xml b/modules/authenticators/pom.xml
index 43b1f5c2af5..70b99e7daa7 100644
--- a/modules/authenticators/pom.xml
+++ b/modules/authenticators/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/callhome/pom.xml b/modules/callhome/pom.xml
index 94b8386ba42..c06d2575543 100644
--- a/modules/callhome/pom.xml
+++ b/modules/callhome/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/connectors/pom.xml b/modules/connectors/pom.xml
index 6bfa1526dff..d2bc8ef22aa 100644
--- a/modules/connectors/pom.xml
+++ b/modules/connectors/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/distribution/pom.xml b/modules/distribution/pom.xml
index ad6a1f1c8d8..12f1d98802b 100755
--- a/modules/distribution/pom.xml
+++ b/modules/distribution/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/distribution/src/repository/resources/conf/catalina.properties b/modules/distribution/src/repository/resources/conf/catalina.properties
index 3e6bacf7d87..8a92d5326bf 100644
--- a/modules/distribution/src/repository/resources/conf/catalina.properties
+++ b/modules/distribution/src/repository/resources/conf/catalina.properties
@@ -239,6 +239,7 @@ org.wso2.carbon.identity.rest.api.server.email.template.v1-*.jar,\
org.wso2.carbon.identity.rest.api.user.application.v1-*.jar,\
org.wso2.carbon.identity.rest.api.user.functionality.v1-*.jar,\
org.wso2.carbon.identity.rest.api.user.recovery.v1-*.jar,\
+org.wso2.carbon.identity.rest.api.user.recovery.v2-*.jar,\
org.wso2.carbon.identity.rest.api.user.totp.common-*.jar,\
org.wso2.carbon.identity.rest.api.user.totp.v1-*.jar,\
org.wso2.carbon.security.mgt.ui_*.jar,\
diff --git a/modules/features/org.wso2.identity.styles.feature/pom.xml b/modules/features/org.wso2.identity.styles.feature/pom.xml
index 3b6289681c0..aa6da25eb1b 100644
--- a/modules/features/org.wso2.identity.styles.feature/pom.xml
+++ b/modules/features/org.wso2.identity.styles.feature/pom.xml
@@ -20,7 +20,7 @@
org.wso2.is
identity-features
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/features/org.wso2.identity.ui.feature/pom.xml b/modules/features/org.wso2.identity.ui.feature/pom.xml
index 03af2a58255..2dfd43062e9 100644
--- a/modules/features/org.wso2.identity.ui.feature/pom.xml
+++ b/modules/features/org.wso2.identity.ui.feature/pom.xml
@@ -20,7 +20,7 @@
org.wso2.is
identity-features
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/features/org.wso2.identity.utils.feature/pom.xml b/modules/features/org.wso2.identity.utils.feature/pom.xml
index 0efa46f2559..02ecf5df78a 100644
--- a/modules/features/org.wso2.identity.utils.feature/pom.xml
+++ b/modules/features/org.wso2.identity.utils.feature/pom.xml
@@ -20,7 +20,7 @@
org.wso2.is
identity-features
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/features/pom.xml b/modules/features/pom.xml
index 15d39383dc5..98e49477e2b 100644
--- a/modules/features/pom.xml
+++ b/modules/features/pom.xml
@@ -17,7 +17,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/pom.xml b/modules/integration/pom.xml
index 54c794dfbfc..e27048cbc42 100644
--- a/modules/integration/pom.xml
+++ b/modules/integration/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-common/admin-clients/pom.xml b/modules/integration/tests-common/admin-clients/pom.xml
index d351be61311..2f63787fb30 100644
--- a/modules/integration/tests-common/admin-clients/pom.xml
+++ b/modules/integration/tests-common/admin-clients/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-common/extensions/pom.xml b/modules/integration/tests-common/extensions/pom.xml
index a5d781434b3..7e7f7bd549d 100644
--- a/modules/integration/tests-common/extensions/pom.xml
+++ b/modules/integration/tests-common/extensions/pom.xml
@@ -22,7 +22,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-common/integration-test-utils/pom.xml b/modules/integration/tests-common/integration-test-utils/pom.xml
index d121e9a61c7..0270b418463 100644
--- a/modules/integration/tests-common/integration-test-utils/pom.xml
+++ b/modules/integration/tests-common/integration-test-utils/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-common/pom.xml b/modules/integration/tests-common/pom.xml
index 423a73d706d..9d2a15fca06 100644
--- a/modules/integration/tests-common/pom.xml
+++ b/modules/integration/tests-common/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/integration/tests-common/ui-pages/pom.xml b/modules/integration/tests-common/ui-pages/pom.xml
index 4f8e1dc0bb4..e3a55746a82 100644
--- a/modules/integration/tests-common/ui-pages/pom.xml
+++ b/modules/integration/tests-common/ui-pages/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-integration/pom.xml b/modules/integration/tests-integration/pom.xml
index 0f071b20b8d..18358dc7872 100644
--- a/modules/integration/tests-integration/pom.xml
+++ b/modules/integration/tests-integration/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/integration/tests-integration/tests-backend/pom.xml b/modules/integration/tests-integration/tests-backend/pom.xml
index 79d5210007f..f3bf49f5623 100644
--- a/modules/integration/tests-integration/tests-backend/pom.xml
+++ b/modules/integration/tests-integration/tests-backend/pom.xml
@@ -18,7 +18,7 @@
org.wso2.is
identity-integration-tests
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ResponseModeTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ResponseModeTestCase.java
new file mode 100644
index 00000000000..a4a73b5df28
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ResponseModeTestCase.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.oauth2;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Lookup;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.cookie.CookieSpecProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.cookie.RFC6265CookieSpecProvider;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.wso2.carbon.automation.engine.context.TestUserMode;
+import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.ApplicationModel;
+import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.ApplicationResponseModel;
+import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.InboundProtocols;
+import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.OpenIDConnectConfiguration;
+import org.wso2.identity.integration.test.utils.DataExtractUtil;
+import org.wso2.identity.integration.test.utils.OAuth2Constant;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class OAuth2ResponseModeTestCase extends OAuth2ServiceAbstractIntegrationTest{
+
+ private String applicationId;
+ private String consumerKey;
+ private String consumerSecret;
+ private static final String CALLBACK_URL = OAuth2Constant.CALLBACK_URL;
+
+ private Lookup cookieSpecRegistry;
+ private RequestConfig requestConfig;
+ private CloseableHttpClient client;
+
+ @BeforeClass(alwaysRun = true)
+ public void testInit() throws Exception {
+
+ super.init(TestUserMode.SUPER_TENANT_USER);
+ cookieSpecRegistry = RegistryBuilder.create()
+ .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider())
+ .build();
+ requestConfig = RequestConfig.custom()
+ .setCookieSpec(CookieSpecs.DEFAULT)
+ .build();
+ client = HttpClientBuilder.create()
+ .disableRedirectHandling()
+ .setDefaultRequestConfig(requestConfig)
+ .setDefaultCookieSpecRegistry(cookieSpecRegistry)
+ .build();
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void atEnd() throws Exception {
+
+ deleteApp(applicationId);
+
+ consumerKey = null;
+ consumerSecret = null;
+ applicationId = null;
+
+ client.close();
+ restClient.closeHttpClient();
+ }
+
+ @Test(groups = "wso2.is", description = "Check Oauth2 application registration")
+ public void testRegisterApplication() throws Exception {
+
+ ApplicationResponseModel application = createApp();
+ Assert.assertNotNull(application, "OAuth App creation failed.");
+
+ OpenIDConnectConfiguration oidcConfig = getOIDCInboundDetailsOfApplication(application.getId());
+
+ consumerKey = oidcConfig.getClientId();
+ Assert.assertNotNull(consumerKey, "Application creation failed.");
+
+ consumerSecret = oidcConfig.getClientSecret();
+ Assert.assertNotNull(consumerSecret, "Application creation failed.");
+ applicationId = application.getId();
+ }
+
+ /**
+ * Provide request data to test response modes.
+ *
+ * @return Object with testAuthCodeGrantSendAuthRequestPost method parameters.
+ */
+ @DataProvider(name = "responseModeDataProvider")
+ private Object[][] responseModeDataProvider() {
+
+ return new Object[][] {
+ // Response type, provided response mode, response mode used in the response
+ {OAuth2Constant.AUTHORIZATION_CODE_NAME, null, OAuth2Constant.RESPONSE_MODE_QUERY},
+ {OAuth2Constant.AUTHORIZATION_CODE_NAME, OAuth2Constant.RESPONSE_MODE_QUERY, OAuth2Constant.RESPONSE_MODE_QUERY},
+ {OAuth2Constant.AUTHORIZATION_CODE_NAME, OAuth2Constant.RESPONSE_MODE_JWT, OAuth2Constant.RESPONSE_MODE_QUERY_JWT},
+ {OAuth2Constant.AUTHORIZATION_CODE_NAME, OAuth2Constant.RESPONSE_MODE_QUERY_JWT, OAuth2Constant.RESPONSE_MODE_QUERY_JWT},
+
+ {OAuth2Constant.RESPONSE_TYPE_CODE_ID_TOKEN, null, OAuth2Constant.RESPONSE_MODE_FRAGMENT},
+ {OAuth2Constant.RESPONSE_TYPE_CODE_ID_TOKEN, OAuth2Constant.RESPONSE_MODE_FRAGMENT, OAuth2Constant.RESPONSE_MODE_FRAGMENT},
+ {OAuth2Constant.RESPONSE_TYPE_CODE_ID_TOKEN, OAuth2Constant.RESPONSE_MODE_JWT, OAuth2Constant.RESPONSE_MODE_FRAGMENT_JWT},
+ {OAuth2Constant.RESPONSE_TYPE_CODE_ID_TOKEN, OAuth2Constant.RESPONSE_MODE_FRAGMENT_JWT, OAuth2Constant.RESPONSE_MODE_FRAGMENT_JWT},
+ };
+ }
+
+ @Test(groups = "wso2.is", description = "Send authorize user request with response types and response modes.",
+ dependsOnMethods = "testRegisterApplication", dataProvider = "responseModeDataProvider")
+ public void testSendAuthRequestPost(String responseType, String responseModeProvided,
+ String responseMode) throws Exception {
+
+ List urlParameters = new ArrayList<>();
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, responseType));
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_MODE, responseModeProvided));
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_CLIENT_ID, consumerKey));
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, CALLBACK_URL));
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_SCOPE, OAuth2Constant.OAUTH2_SCOPE_OPENID_WITH_INTERNAL_LOGIN));
+ urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_NONCE, UUID.randomUUID().toString()));
+
+ HttpResponse response = sendPostRequestWithParameters(client, urlParameters,
+ OAuth2Constant.AUTHORIZE_ENDPOINT_URL);
+ Assert.assertNotNull(response, "Authorization request failed. Authorized response is null.");
+
+ String locationValue = getLocationHeaderValue(response);
+ EntityUtils.consume(response.getEntity());
+
+ String sessionDataKeyConsent = DataExtractUtil.getParamFromURIString(locationValue,
+ OAuth2Constant.SESSION_DATA_KEY_CONSENT);
+
+ String sessionDataKey;
+ if (sessionDataKeyConsent == null) {
+ Assert.assertTrue(locationValue.contains(OAuth2Constant.SESSION_DATA_KEY),
+ "sessionDataKey not found in response.");
+ sessionDataKey = DataExtractUtil.getParamFromURIString(locationValue, OAuth2Constant.SESSION_DATA_KEY);
+ Assert.assertNotNull(sessionDataKey, "sessionDataKey is null.");
+
+ sessionDataKeyConsent = getSessionDataKeyConsent(client, sessionDataKey);
+ }
+
+ response = sendApprovalPost(client, sessionDataKeyConsent);
+ Assert.assertNotNull(response, "Approval request failed. response is invalid.");
+
+ locationValue = getLocationHeaderValue(response);
+
+ AuthorizationCode authorizationCode = getAuthorizationCode(locationValue, responseMode);
+ Assert.assertNotNull(authorizationCode,
+ "Authorization code is null or could not be found.");
+ EntityUtils.consume(response.getEntity());
+ }
+
+ /**
+ * Extract authorization code.
+ * @param url redirection url
+ * @param responseMode response mode which is used to send the request
+ * @return AuthorizationCode object
+ */
+ private AuthorizationCode getAuthorizationCode(String url, String responseMode)
+ throws URISyntaxException {
+
+ String code;
+ if (responseMode.contains("jwt")) {
+ String responseJWT;
+ Assert.assertTrue(url.contains("response"), "Response JWT not found in the response.");
+ if (responseMode.contains("fragment")) {
+ responseJWT = DataExtractUtil.extractParamFromURIFragment(url, "response");
+ Assert.assertNotNull(responseJWT, "Response JWT not found as a fragment parameter.");
+ } else {
+ responseJWT = DataExtractUtil.getParamFromURIString(url, "response");
+ Assert.assertNotNull(responseJWT, "Response JWT not found as a query parameter.");
+ }
+ Assert.assertNotNull(responseJWT, "Response JWT not found.");
+ try {
+ // decode the response and get code
+ JWTClaimsSet jwtClaimsSet = extractJwt(responseJWT);
+ code = jwtClaimsSet.getStringClaim(OAuth2Constant.AUTHORIZATION_CODE_NAME);
+ } catch (ParseException e) {
+ throw new URISyntaxException(url, "Error while parsing JWT token.");
+ }
+ } else {
+ Assert.assertTrue(url.contains("code"), "code not found in the response.");
+ if (responseMode.contains("fragment")) {
+ code = DataExtractUtil.extractParamFromURIFragment(url, OAuth2Constant.AUTHORIZATION_CODE_NAME);
+ } else {
+ code = DataExtractUtil.getParamFromURIString(url, OAuth2Constant.AUTHORIZATION_CODE_NAME);
+ }
+ }
+ Assert.assertNotNull(code, "Authorization code not found.");
+ return new AuthorizationCode(code);
+ }
+
+ /**
+ * Extract JWT token and assign to a map.
+ * @param jwtToken jwt token
+ * @return jwt claim set
+ */
+ private JWTClaimsSet extractJwt(String jwtToken) throws ParseException {
+
+ SignedJWT signedJWT = SignedJWT.parse(jwtToken);
+ return signedJWT.getJWTClaimsSet();
+ }
+
+ /**
+ * Sends a log in post to the IS instance and extract and return the sessionDataKeyConsent from the response.
+ *
+ * @param client CloseableHttpClient object to send the login post.
+ * @param sessionDataKey String sessionDataKey obtained.
+ * @return Extracted sessionDataKeyConsent.
+ * @throws IOException Error
+ * @throws URISyntaxException Error
+ */
+ private String getSessionDataKeyConsent(CloseableHttpClient client, String sessionDataKey)
+ throws IOException, URISyntaxException {
+
+ HttpResponse response = sendLoginPost(client, sessionDataKey);
+ Assert.assertNotNull(response, "Login request failed. response is null.");
+
+ String locationValue = getLocationHeaderValue(response);
+ EntityUtils.consume(response.getEntity());
+
+ // Request will return with a 302 to the authorize end point. Doing a GET will give the sessionDataKeyConsent
+ response = sendGetRequest(client, locationValue);
+ Assert.assertNotNull(response, "GET request response is null.");
+
+ locationValue = getLocationHeaderValue(response);
+ Assert.assertTrue(locationValue.contains(OAuth2Constant.SESSION_DATA_KEY_CONSENT),
+ "sessionDataKeyConsent not found in response.");
+
+ EntityUtils.consume(response.getEntity());
+
+ // Extract sessionDataKeyConsent from the location value.
+ String sessionDataKeyConsent = DataExtractUtil.getParamFromURIString(locationValue,
+ OAuth2Constant.SESSION_DATA_KEY_CONSENT);
+ Assert.assertNotNull(sessionDataKeyConsent, "sessionDataKeyConsent is null.");
+ return sessionDataKeyConsent;
+ }
+
+ /**
+ * Extract the location header value from a HttpResponse.
+ *
+ * @param response HttpResponse object that needs the header extracted.
+ * @return String value of the location header.
+ */
+ private String getLocationHeaderValue(HttpResponse response) {
+
+ Header location = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION);
+ Assert.assertNotNull(location, "Location header is null.");
+ return location.getValue();
+ }
+
+ /**
+ * Create Application with the given app configurations
+ *
+ * @return ApplicationResponseModel
+ * @throws Exception exception
+ */
+ private ApplicationResponseModel createApp() throws Exception {
+
+ ApplicationModel application = new ApplicationModel();
+
+ List grantTypes = new ArrayList<>();
+ Collections.addAll(grantTypes, "authorization_code", "implicit", "password", "client_credentials",
+ "refresh_token", "urn:ietf:params:oauth:grant-type:saml2-bearer", "iwa:ntlm",
+ "urn:ietf:params:oauth:grant-type:device_code");
+
+ List callBackUrls = new ArrayList<>();
+ Collections.addAll(callBackUrls, OAuth2Constant.CALLBACK_URL);
+
+ OpenIDConnectConfiguration oidcConfig = new OpenIDConnectConfiguration();
+ oidcConfig.setGrantTypes(grantTypes);
+ oidcConfig.setCallbackURLs(callBackUrls);
+ oidcConfig.setPublicClient(true);
+
+ InboundProtocols inboundProtocolsConfig = new InboundProtocols();
+ inboundProtocolsConfig.setOidc(oidcConfig);
+
+ application.setInboundProtocolConfiguration(inboundProtocolsConfig);
+ application.setName(OAuth2Constant.OAUTH_APPLICATION_NAME);
+
+ String appId = addApplication(application);
+
+ return getApplication(appId);
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCRPInitiatedLogoutTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCRPInitiatedLogoutTestCase.java
new file mode 100644
index 00000000000..f9729d7711c
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCRPInitiatedLogoutTestCase.java
@@ -0,0 +1,339 @@
+package org.wso2.identity.integration.test.oidc;
+
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Lookup;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.cookie.CookieSpecProvider;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.cookie.RFC6265CookieSpecProvider;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.wso2.identity.integration.test.oidc.bean.OIDCApplication;
+import org.wso2.identity.integration.test.rest.api.user.common.model.Email;
+import org.wso2.identity.integration.test.rest.api.user.common.model.Name;
+import org.wso2.identity.integration.test.rest.api.user.common.model.UserObject;
+import org.wso2.identity.integration.test.utils.DataExtractUtil;
+import org.wso2.identity.integration.test.utils.OAuth2Constant;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This test class tests the OIDC RP-Initiated logout flows
+ */
+public class OIDCRPInitiatedLogoutTestCase extends OIDCAbstractIntegrationTest {
+
+ protected UserObject user;
+ protected String idToken;
+ protected String sessionDataKeyConsent;
+ protected String sessionDataKey;
+ protected AuthorizationCode authorizationCode;
+ CookieStore cookieStore = new BasicCookieStore();
+ protected Lookup cookieSpecRegistry;
+ protected RequestConfig requestConfig;
+ protected HttpClient client;
+ protected List consentParameters = new ArrayList<>();
+ OIDCApplication playgroundAppOne;
+ OIDCApplication playgroundAppTwo;
+
+ @BeforeClass(alwaysRun = true)
+ public void testInit() throws Exception {
+
+ super.init();
+
+ initUser();
+ createUser(user);
+ userInfo.setUserName(user.getUserName());
+ userInfo.setPassword(user.getPassword());
+
+ playgroundAppOne = initApplicationOne();
+ playgroundAppTwo = initApplicationTwo();
+ createApplication(playgroundAppOne);
+ createApplication(playgroundAppTwo);
+
+ cookieSpecRegistry = RegistryBuilder.create()
+ .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider())
+ .build();
+ requestConfig = RequestConfig.custom()
+ .setCookieSpec(CookieSpecs.DEFAULT)
+ .build();
+ client = HttpClientBuilder.create().setDefaultCookieStore(cookieStore)
+ .setDefaultCookieSpecRegistry(cookieSpecRegistry)
+ .setDefaultRequestConfig(requestConfig)
+ .build();
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void testClear() throws Exception {
+
+ deleteUser(user);
+ deleteApplication(playgroundAppOne);
+ deleteApplication(playgroundAppTwo);
+ clear();
+ }
+
+ @AfterMethod
+ public void clearVariables() {
+
+ sessionDataKey = null;
+ sessionDataKeyConsent = null;
+ idToken = null;
+ }
+
+ @Test(groups = "wso2.is", description = "Test RP-initiated logout with client_id parameter")
+ public void testOIDCLogoutWithClientId() throws Exception {
+
+ testInitiateOIDCRequest(playgroundAppOne, client);
+ testOIDCLogin(playgroundAppOne, true);
+ testOIDCConsentApproval(playgroundAppOne);
+ testOIDCLogout(true, playgroundAppOne,
+ new BasicNameValuePair("client_id", playgroundAppOne.getClientId()));
+ }
+
+ @Test(groups = "wso2.is", description = "Test RP-initiated logout with id_token_hint parameter",
+ dependsOnMethods = { "testOIDCLogoutWithClientId" })
+ public void testOIDCLogoutWithIdTokenHint() throws Exception {
+
+ testInitiateOIDCRequest(playgroundAppOne, client);
+ testOIDCLogin(playgroundAppOne, false);
+ testGetIdToken(playgroundAppOne);
+ testOIDCLogout(true, playgroundAppOne, new BasicNameValuePair("id_token_hint", idToken));
+ }
+
+ @Test(groups = "wso2.is", description = "Test RP-initiated logout with both client_id and id_token_hint",
+ dependsOnMethods = { "testOIDCLogoutWithClientId" })
+ public void testOIDCLogoutPrecedence() throws Exception {
+
+ /* the purpose of this test is to verify client_id takes precedence when both parameters are sent.
+ Here, both client_id and id_token are valid but the post_logout_redirect uri matches with the id_token only.
+ So the request should fail.
+ */
+ testInitiateOIDCRequest(playgroundAppTwo, client);
+ testOIDCLogin(playgroundAppTwo, false);
+ testGetIdToken(playgroundAppTwo);
+ testOIDCLogout(false, playgroundAppTwo,
+ new BasicNameValuePair("client_id", playgroundAppOne.getClientId()),
+ new BasicNameValuePair("id_token_hint", idToken));
+ }
+
+ private void testInitiateOIDCRequest(OIDCApplication application, HttpClient client) throws Exception {
+
+ List urlParameters = OIDCUtilTest.getNameValuePairs(application);
+ HttpResponse response = sendPostRequestWithParameters(client, urlParameters, String.format
+ (OIDCUtilTest.targetApplicationUrl, application.getApplicationContext() +
+ OAuth2Constant.PlaygroundAppPaths.appUserAuthorizePath));
+ Assert.assertNotNull(response, "Authorization request failed for " + application.getApplicationName() +
+ ". Authorized response is null.");
+
+ Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION);
+
+ Assert.assertNotNull(locationHeader, "Authorization request failed for " +
+ application.getApplicationName() + ". Authorized response header is null.");
+ EntityUtils.consume(response.getEntity());
+
+ response = sendGetRequest(client, locationHeader.getValue());
+ Assert.assertNotNull(response, "Authorization request failed for " +
+ application.getApplicationName() + ". Authorized user response is null.");
+
+ Map keyPositionMap = new HashMap<>(1);
+ keyPositionMap.put("name=\"sessionDataKey\"", 1);
+ List keyValues = DataExtractUtil.extractDataFromResponse(response,
+ keyPositionMap);
+ Assert.assertNotNull(keyValues, "The sessionDataKey value is null for " +
+ application.getApplicationName());
+
+ sessionDataKey = keyValues.get(0).getValue();
+ Assert.assertNotNull(sessionDataKey, "Invalid sessionDataKey for " + application.getApplicationName());
+
+ EntityUtils.consume(response.getEntity());
+ }
+
+ private void testOIDCLogin(OIDCApplication application, boolean checkConsent) throws Exception {
+
+ HttpResponse response = sendLoginPost(client, sessionDataKey);
+ Assert.assertNotNull(response, "Login request failed for " + application.getApplicationName() +
+ ". response is null.");
+
+ Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION);
+ Assert.assertNotNull(locationHeader, "Login response header is null for " +
+ application.getApplicationName());
+ EntityUtils.consume(response.getEntity());
+
+ response = sendGetRequest(client, locationHeader.getValue());
+ Map keyPositionMap = new HashMap<>(1);
+ if (checkConsent) {
+ keyPositionMap.put("name=\"sessionDataKeyConsent\"", 1);
+ List keyValues = DataExtractUtil.extractSessionConsentDataFromResponse(
+ response, keyPositionMap);
+ Assert.assertNotNull(keyValues, "SessionDataKeyConsent keyValues map is null.");
+ sessionDataKeyConsent = keyValues.get(0).getValue();
+ Assert.assertNotNull(sessionDataKeyConsent, "sessionDataKeyConsent is null.");
+ } else {
+ keyPositionMap.put("Authorization Code", 1);
+ List keyValues = DataExtractUtil.extractTableRowDataFromResponse(response,
+ keyPositionMap);
+ Assert.assertNotNull(keyValues, "Authorization code not received for " +
+ application.getApplicationName());
+
+ authorizationCode = new AuthorizationCode(keyValues.get(0).getValue());
+ Assert.assertNotNull(authorizationCode, "Authorization code not received for " + application
+ .getApplicationName());
+ }
+ EntityUtils.consume(response.getEntity());
+ }
+
+ private void testOIDCConsentApproval(OIDCApplication application) throws Exception {
+
+ HttpResponse response = sendApprovalPostWithConsent(client, sessionDataKeyConsent, consentParameters);
+ Assert.assertNotNull(response, "Approval request failed for " + application.getApplicationName() + ". "
+ + "response is invalid.");
+
+ Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION);
+ Assert.assertNotNull(locationHeader, "Approval request failed for " + application.getApplicationName()
+ + ". Location header is null.");
+ EntityUtils.consume(response.getEntity());
+
+ response = sendPostRequest(client, locationHeader.getValue());
+ Assert.assertNotNull(response, "Authorization code response is invalid for " +
+ application.getApplicationName());
+
+ Map keyPositionMap = new HashMap<>(1);
+ keyPositionMap.put("Authorization Code", 1);
+ List keyValues = DataExtractUtil.extractTableRowDataFromResponse(response,
+ keyPositionMap);
+ Assert.assertNotNull(keyValues, "Authorization code not received for " +
+ application.getApplicationName());
+
+ authorizationCode = new AuthorizationCode(keyValues.get(0).getValue());
+ Assert.assertNotNull(authorizationCode, "Authorization code not received for " + application
+ .getApplicationName());
+ EntityUtils.consume(response.getEntity());
+ }
+
+ private void testGetIdToken(OIDCApplication application) throws Exception {
+
+ ClientID clientID = new ClientID(application.getClientId());
+ Secret clientSecret = new Secret(application.getClientSecret());
+ ClientSecretBasic clientSecretBasic = new ClientSecretBasic(clientID, clientSecret);
+
+ URI callbackURI = new URI(application.getCallBackURL());
+ AuthorizationCodeGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, callbackURI);
+
+ TokenRequest tokenReq = new TokenRequest(new URI(OAuth2Constant.ACCESS_TOKEN_ENDPOINT), clientSecretBasic,
+ authorizationCodeGrant);
+
+ HTTPResponse tokenHTTPResp = tokenReq.toHTTPRequest().send();
+ Assert.assertNotNull(tokenHTTPResp, "Access token http response is null.");
+
+ TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenHTTPResp);
+ Assert.assertNotNull(tokenResponse, "Access token response is null.");
+
+ Assert.assertFalse(tokenResponse instanceof TokenErrorResponse,
+ "Access token response contains errors.");
+
+ OIDCTokenResponse oidcTokenResponse = (OIDCTokenResponse) tokenResponse;
+ OIDCTokens oidcTokens = oidcTokenResponse.getOIDCTokens();
+ Assert.assertNotNull(oidcTokens, "OIDC Tokens object is null.");
+
+ idToken = oidcTokens.getIDTokenString();
+ Assert.assertNotNull(idToken, "ID token is null");
+ }
+
+ private void testOIDCLogout(boolean checkSuccess, OIDCApplication application, BasicNameValuePair... parameters) {
+
+ try {
+ StringBuilder oidcLogoutUrl =
+ new StringBuilder(identityContextUrls.getWebAppURLHttps() + "/oidc/logout?post_logout_redirect_uri="
+ + application.getCallBackURL());
+
+ for (BasicNameValuePair parameter: parameters) {
+ oidcLogoutUrl.append("&").append(parameter.getName()).append("=").append(parameter.getValue());
+ }
+ HttpResponse response = sendGetRequest(client, oidcLogoutUrl.toString());
+ EntityUtils.consume(response.getEntity());
+
+ List urlParameters = new ArrayList<>();
+ urlParameters.add(new BasicNameValuePair("consent", "approve"));
+ response = sendPostRequestWithParameters(client, urlParameters, oidcLogoutUrl.toString());
+ Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION);
+ EntityUtils.consume(response.getEntity());
+
+ String redirectUrl = locationHeader.getValue();
+ if (checkSuccess) {
+ /*
+ since client_id and id_token_hint are optional parameters, logout will be successful even if they are
+ not present in the request. However, if either of these and the correct post_logout_redirect_uri is
+ sent in the request, OP should validate the client based on these values and redirect to the given
+ redirect url.
+ */
+ Assert.assertTrue(redirectUrl.contains(application.getCallBackURL()), "Not redirected to the"
+ + "post logout redirect url");
+ response = sendGetRequest(client, redirectUrl);
+ Assert.assertNotNull(response, "OIDC Logout failed.");
+ String result = DataExtractUtil.getContentData(response);
+ Assert.assertTrue(result.contains("WSO2 OAuth2 Playground"), "OIDC logout failed.");
+ EntityUtils.consume(response.getEntity());
+ } else {
+ Assert.assertTrue(redirectUrl.contains("oauth2_error.do"));
+ }
+ } catch (Exception e) {
+ Assert.fail("OIDC Logout failed.", e);
+ }
+ }
+
+ protected void initUser() {
+
+ user = new UserObject();
+ user.setUserName(OIDCUtilTest.username);
+ user.setPassword(OIDCUtilTest.password);
+ user.setName(new Name().givenName(OIDCUtilTest.firstName).familyName(OIDCUtilTest.lastName));
+ user.addEmail(new Email().value(OIDCUtilTest.email));
+ }
+
+ protected OIDCApplication initApplicationOne() {
+
+ playgroundAppOne = new OIDCApplication(OIDCUtilTest.playgroundAppOneAppName,
+ OIDCUtilTest.playgroundAppOneAppContext,
+ OIDCUtilTest.playgroundAppOneAppCallBackUri);
+ playgroundAppOne.addRequiredClaim(OIDCUtilTest.emailClaimUri);
+ playgroundAppOne.addRequiredClaim(OIDCUtilTest.firstNameClaimUri);
+ return playgroundAppOne;
+ }
+
+ protected OIDCApplication initApplicationTwo() {
+
+ playgroundAppTwo = new OIDCApplication(OIDCUtilTest.playgroundAppTwoAppName,
+ OIDCUtilTest.playgroundAppTwoAppContext,
+ OIDCUtilTest.playgroundAppTwoAppCallBackUri);
+ playgroundAppOne.addRequiredClaim(OIDCUtilTest.emailClaimUri);
+ playgroundAppOne.addRequiredClaim(OIDCUtilTest.firstNameClaimUri);
+ return playgroundAppTwo;
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/common/B2BRESTTestBase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/common/B2BRESTTestBase.java
new file mode 100644
index 00000000000..fbc37f5c2b6
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/common/B2BRESTTestBase.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.common;
+
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpHeaders;
+import org.hamcrest.Matcher;
+import org.wso2.carbon.automation.engine.context.AutomationContext;
+import org.wso2.identity.integration.common.clients.usermgt.remote.RemoteUserStoreManagerServiceClient;
+
+import java.rmi.RemoteException;
+import java.util.ResourceBundle;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringContains.containsString;
+
+/**
+ * Base test class for B2B organization management REST API tests.
+ */
+public class B2BRESTTestBase extends RESTTestBase {
+
+ protected static final String ORGANIZATION_CONTEXT_IN_URL = "/o/%s";
+ protected static final String SERVICES = "/services";
+ private static ResourceBundle errorProperties = ResourceBundle.getBundle("RESTAPIErrors");
+
+ protected String authenticatingUserName;
+ protected String authenticatingCredential;
+ protected String tenant;
+ protected AutomationContext context;
+
+ protected RemoteUserStoreManagerServiceClient remoteUSMServiceClient;
+ protected String swaggerDefinition;
+
+ protected String basePath = StringUtils.EMPTY;
+
+ /**
+ * Initialize the REST API validation requirements configuring the OpenApiValidationFilter
+ *
+ * @param swaggerDefinition Swagger definition name.
+ * @param basePathInSwagger Basepath that is defined in the swagger definition (ex: /api/users/v1).
+ * @param basePath Basepath of the current test run (ex: /o/{organization-domain}/api/users/v1).
+ * @throws RemoteException Throws this exception.
+ */
+ protected void init(String swaggerDefinition, String basePathInSwagger, String basePath) throws RemoteException {
+
+ super.init(swaggerDefinition, basePathInSwagger, basePath);
+ this.basePath = basePath;
+ }
+
+ /**
+ * Invoke given endpointUri for GET with Basic authentication, authentication credential being the
+ * authenticatingUserName and authenticatingCredential.
+ *
+ * @param endpointUri Endpoint to be invoked.
+ * @return response of get request.
+ */
+ protected Response getResponseOfGet(String endpointUri) {
+
+ return given().auth().preemptive().basic(authenticatingUserName, authenticatingCredential)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.ACCEPT, ContentType.JSON)
+ .log().ifValidationFails()
+ .when()
+ .get(endpointUri);
+ }
+
+ /**
+ * Invoke given endpointUri for POST with given body and Basic authentication, authentication credential being the
+ * authenticatingUserName and authenticatingCredential.
+ *
+ * @param endpointUri Endpoint to be invoked.
+ * @param body Payload.
+ * @return Response of post request.
+ */
+ protected Response getResponseOfPost(String endpointUri, String body) {
+
+ return given().auth().preemptive().basic(authenticatingUserName, authenticatingCredential)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.ACCEPT, ContentType.JSON)
+ .body(body)
+ .log().ifValidationFails()
+ .log().ifValidationFails()
+ .when()
+ .log().ifValidationFails()
+ .post(endpointUri);
+ }
+
+ /**
+ * Invoke given endpointUri for DELETE with given body and Basic authentication, authentication credential being
+ * the authenticatingUserName and authenticatingCredential.
+ *
+ * @param endpointURI Endpoint of the request.
+ * @return Response of delete request.
+ */
+ protected Response getResponseOfDelete(String endpointURI) {
+
+ return given().auth().preemptive().basic(authenticatingUserName, authenticatingCredential)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.ACCEPT, ContentType.JSON)
+ .log().ifValidationFails()
+ .log().ifValidationFails()
+ .when()
+ .log().ifValidationFails()
+ .delete(endpointURI);
+ }
+
+ /**
+ * Validate the error response of a request.
+ *
+ * @param response Response of the request.
+ * @param httpStatusCode Status code of the response.
+ * @param errorCode Error code of the response.
+ * @param errorDescriptionArgs Error msg and the description of the response.
+ */
+ protected void validateErrorResponse(Response response, int httpStatusCode, String errorCode, String...
+ errorDescriptionArgs) {
+
+ validateHttpStatusCode(response, httpStatusCode);
+ validateResponseElement(response, "code", is(errorCode));
+ validateErrorMessage(response, errorCode);
+ validateErrorDescription(response, errorCode, errorDescriptionArgs);
+ }
+
+ /**
+ * Validate http status code of the response.
+ *
+ * @param response Response.
+ * @param httpStatusCode Expected status code.
+ */
+ protected void validateHttpStatusCode(Response response, int httpStatusCode) {
+
+ response
+ .then()
+ .assertThat()
+ .log().ifValidationFails()
+ .statusCode(httpStatusCode);
+ }
+
+ /**
+ * Validate error description of the response, if an entry is available in RESTAPIErrors.properties.
+ *
+ * @param response Response.
+ * @param errorCode Error code.
+ * @param placeHolders Values to be replaced in the error description in the corresponding entry in
+ * RESTAPIError.properties.
+ */
+ private void validateErrorDescription(Response response, String errorCode, String... placeHolders) {
+
+ validateElementAgainstErrorProperties(response, errorCode, "description", placeHolders);
+ }
+
+ /**
+ * Validate error message of the response, if an entry is available in RESTAPIErrors.properties.
+ *
+ * @param response Response.
+ * @param errorCode Error code.
+ */
+ private void validateErrorMessage(Response response, String errorCode) {
+
+ validateElementAgainstErrorProperties(response, errorCode, "message");
+ }
+
+ /**
+ * Validate elements in error response against entries in RESTAPIErrors.properties.
+ *
+ * @param response Response.
+ * @param errorCode API error code.
+ * @param element Element.
+ * @param placeHolderValues Placeholder values.
+ * arg[0], key element in the RESTAPIErrors.properties (error-code.arg[0]).
+ * arg[1-n] place holder values to replace in value in the RESTAPIErrors.properties.
+ */
+ private void validateElementAgainstErrorProperties(Response response, String errorCode, String element, String...
+ placeHolderValues) {
+
+ String expected = StringUtils.EMPTY;
+ try {
+ expected = errorProperties.getString(String.format("%s.%s", errorCode, element));
+ } catch (Throwable e) {
+ //Ignore if error properties are not defined as keys in RESTAPIErrors.properties
+ }
+ if (StringUtils.isNotEmpty(expected)) {
+ expected = String.format(expected, placeHolderValues);
+ validateResponseElement(response, element, containsString(expected));
+ }
+ }
+
+ /**
+ * Validate a response element against a matcher.
+ *
+ * @param response Response.
+ * @param element JSON path element to match.
+ * @param responseAwareMatcher Expected matcher.
+ */
+ protected void validateResponseElement(Response response, String element, Matcher
+ responseAwareMatcher) {
+
+ response
+ .then()
+ .assertThat()
+ .log().ifValidationFails()
+ .body(element, responseAwareMatcher);
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/common/B2BRESTAPIServerTestBase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/common/B2BRESTAPIServerTestBase.java
new file mode 100644
index 00000000000..9bc6073b328
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/common/B2BRESTAPIServerTestBase.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.common;
+
+import org.wso2.identity.integration.test.rest.api.common.B2BRESTTestBase;
+
+import java.rmi.RemoteException;
+
+/**
+ * Base Test Class for server based B2B REST API test cases.
+ * ex: /o/{organization-domain}/api/server/{version}
+ */
+public class B2BRESTAPIServerTestBase extends B2BRESTTestBase {
+
+ protected static final String API_SERVER_BASE_PATH = "/api/server/%s";
+ protected static final String API_SERVER_BASE_PATH_IN_SWAGGER = "/o/\\{organization-domain\\}" +
+ API_SERVER_BASE_PATH;
+ protected static final String API_SERVER_BASE_PATH_WITH_ORGANIZATION_CONTEXT =
+ ORGANIZATION_CONTEXT_IN_URL + API_SERVER_BASE_PATH;
+
+ protected void testInit(String apiVersion, String apiDefinition, String organizationID) throws RemoteException {
+
+ String basePathInSwagger = String.format(API_SERVER_BASE_PATH_IN_SWAGGER, apiVersion);
+ String basePath = String.format(API_SERVER_BASE_PATH_WITH_ORGANIZATION_CONTEXT,
+ organizationID, apiVersion);
+ super.init(apiDefinition, basePathInSwagger, basePath);
+ }
+
+ protected void testInitWithoutTenantQualifiedPath(String apiVersion, String apiDefinition) throws RemoteException {
+
+ String basePathInSwagger = String.format(API_SERVER_BASE_PATH, apiVersion);
+ super.init(apiDefinition, basePathInSwagger, basePathInSwagger);
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementBaseTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementBaseTest.java
new file mode 100644
index 00000000000..09027596e68
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementBaseTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1;
+
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.testng.Assert;
+import org.testng.ISuite;
+import org.testng.ITestContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.wso2.carbon.automation.engine.context.TestUserMode;
+import org.wso2.identity.integration.test.rest.api.server.common.B2BRESTAPIServerTestBase;
+import org.wso2.identity.integration.test.rest.api.server.organization.management.v1.model.OrganizationLevel;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.wso2.identity.integration.test.rest.api.server.organization.management.v1.Utils.assertNotBlank;
+import static org.wso2.identity.integration.test.rest.api.server.organization.management.v1.Utils.extractOrganizationIdFromLocationHeader;
+
+/**
+ * Base test class for Organization Management REST APIs.
+ */
+public class OrganizationManagementBaseTest extends B2BRESTAPIServerTestBase {
+
+ private static final String API_DEFINITION_NAME = "org.wso2.carbon.identity.organization.management.yaml";
+ static final String API_VERSION = "v1";
+ public final OrganizationLevel organizationLevel;
+ static final String SUPER_ORGANIZATION_NAME = "Super";
+ static final String ORGANIZATION_NAME = "name";
+ static final String ORGANIZATION_PARENT_ID = "parentId";
+ static final String SUPER_ORGANIZATION_ID = "10084a8d-113f-4211-a0d5-efe36b082211";
+ protected String subOrganizationId;
+ protected static final String ORGANIZATION_MANAGEMENT_API_BASE_PATH = "/organizations";
+ protected static String swaggerDefinition;
+
+ static {
+ String apiPackageName = "org.wso2.carbon.identity.api.server.organization.management.v1";
+ try {
+ swaggerDefinition = getAPISwaggerDefinition(apiPackageName, API_DEFINITION_NAME);
+ } catch (IOException e) {
+ Assert.fail(String.format("Unable to read the swagger definition %s from %s", API_DEFINITION_NAME,
+ apiPackageName), e);
+ }
+ }
+
+ public OrganizationManagementBaseTest(TestUserMode userMode, OrganizationLevel organizationLevel) throws Exception {
+
+ this.organizationLevel = organizationLevel;
+ super.init(userMode);
+ this.context = isServer;
+ this.authenticatingUserName = context.getContextTenant().getTenantAdmin().getUserName();
+ this.authenticatingCredential = context.getContextTenant().getTenantAdmin().getPassword();
+ }
+
+ @BeforeClass(alwaysRun = true)
+ public void init(ITestContext context) throws Exception {
+
+ ISuite suite = context.getSuite();
+ String orgId = (String) suite.getAttribute("createdOrgId");
+ if (orgId == null) {
+ orgId = SUPER_ORGANIZATION_ID;
+ }
+ this.subOrganizationId = orgId;
+ if (OrganizationLevel.SUPER_ORGANIZATION.equals(this.organizationLevel)) {
+ super.testInitWithoutTenantQualifiedPath(API_VERSION, swaggerDefinition);
+ } else {
+ this.tenant = subOrganizationId;
+ this.authenticatingUserName = "admin@" + SUPER_ORGANIZATION_ID;
+ super.testInit(API_VERSION, swaggerDefinition, tenant);
+ }
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void testConclude() throws Exception {
+
+ super.conclude();
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void testInit(ITestContext context) throws Exception {
+
+ RestAssured.basePath = basePath;
+ ISuite suite = context.getSuite();
+ String orgId = (String) suite.getAttribute("createdOrgId");
+
+ if (orgId == null) {
+ orgId = createBaseOrg();
+ suite.setAttribute("createdOrgId", orgId);
+ }
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void testFinish() {
+
+ RestAssured.basePath = StringUtils.EMPTY;
+ }
+
+ @DataProvider(name = "restAPIUserConfigProvider")
+ public static Object[][] restAPIUserConfigProvider() {
+
+ return new Object[][]{
+ {TestUserMode.SUPER_TENANT_ADMIN, OrganizationLevel.SUPER_ORGANIZATION},
+ {TestUserMode.SUPER_TENANT_ADMIN, OrganizationLevel.SUB_ORGANIZATION}
+ };
+ }
+
+ @DataProvider(name = "initRESTAPIUserConfigProvider")
+ public static Object[][] initRESTAPIUserConfigProvider() {
+
+ return new Object[][]{
+ {TestUserMode.SUPER_TENANT_ADMIN, OrganizationLevel.SUPER_ORGANIZATION},
+ };
+ }
+
+ protected void cleanUpOrganizations(Set orgsToCleanUp) {
+
+ orgsToCleanUp.forEach(orgId -> {
+ String organizationPath = ORGANIZATION_MANAGEMENT_API_BASE_PATH + "/" + orgId;
+ Response responseOfDelete = getResponseOfDelete(organizationPath);
+ responseOfDelete.then()
+ .log()
+ .ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_NO_CONTENT);
+ });
+ }
+
+ private String createBaseOrg() {
+
+ String body = "{\n" +
+ " \"name\": \"ABC Builders\",\n" +
+ " \"description\": \"Building constructions\",\n" +
+ " \"type\": \"TENANT\",\n" +
+ " \"parentId\": \"Super\",\n" +
+ " \"attributes\": [\n" +
+ " {\n" +
+ " \"key\": \"Country\",\n" +
+ " \"value\": \"Sri Lanka\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+ Response responseOfPost = getResponseOfPost(ORGANIZATION_MANAGEMENT_API_BASE_PATH, body);
+ responseOfPost.then()
+ .log().ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_CREATED)
+ .header(HttpHeaders.LOCATION, notNullValue());
+
+ String location = responseOfPost.getHeader(HttpHeaders.LOCATION);
+ String createdOrgId = extractOrganizationIdFromLocationHeader(location);
+ assertNotBlank(createdOrgId);
+ return createdOrgId;
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementFailureTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementFailureTest.java
new file mode 100644
index 00000000000..3a6b62fee16
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementFailureTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1;
+
+import io.restassured.response.Response;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpStatus;
+import org.json.JSONObject;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+import org.wso2.carbon.automation.engine.context.TestUserMode;
+import org.wso2.identity.integration.test.rest.api.server.organization.management.v1.model.OrganizationLevel;
+
+/**
+ * Tests for negative paths of the Organization Management REST API.
+ */
+public class OrganizationManagementFailureTest extends OrganizationManagementBaseTest {
+
+ @Factory(dataProvider = "restAPIUserConfigProvider")
+ public OrganizationManagementFailureTest(TestUserMode userMode, OrganizationLevel organizationLevel)
+ throws Exception {
+
+ super(userMode, organizationLevel);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ @Override
+ public void testFinish() {
+
+ super.testFinish();
+ }
+
+ @Test
+ public void testCreateOrganizationWithoutRequiredField() throws Exception {
+
+ JSONObject organizationObject = new JSONObject();
+ String parentId;
+
+ if (OrganizationLevel.SUPER_ORGANIZATION.equals(this.organizationLevel)) {
+ parentId = SUPER_ORGANIZATION_NAME;
+ } else {
+ parentId = subOrganizationId;
+ }
+ organizationObject.put("parentId", parentId);
+ String payload = organizationObject.toString();
+
+ Response response = getResponseOfPost(ORGANIZATION_MANAGEMENT_API_BASE_PATH, payload);
+ validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "UE-10000");
+ }
+
+ @Test
+ public void testCreateOrganizationWithABlankName() throws Exception {
+
+ JSONObject organizationObject = new JSONObject();
+ String parentId;
+
+ if (OrganizationLevel.SUPER_ORGANIZATION.equals(this.organizationLevel)) {
+ parentId = SUPER_ORGANIZATION_NAME;
+ } else {
+ parentId = subOrganizationId;
+ }
+ organizationObject.put(ORGANIZATION_NAME, StringUtils.EMPTY);
+ organizationObject.put(ORGANIZATION_PARENT_ID, parentId);
+ String payload = organizationObject.toString();
+
+ Response response = getResponseOfPost(ORGANIZATION_MANAGEMENT_API_BASE_PATH, payload);
+ validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "ORG-60002", ORGANIZATION_NAME);
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementSuccessTest.java
new file mode 100644
index 00000000000..df59a76377a
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementSuccessTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1;
+
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.json.JSONObject;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+import org.wso2.carbon.automation.engine.context.TestUserMode;
+import org.wso2.identity.integration.test.rest.api.server.organization.management.v1.model.OrganizationLevel;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.wso2.identity.integration.test.rest.api.server.organization.management.v1.OrganizationManagementTestData.APPLICATION_PAYLOAD;
+import static org.wso2.identity.integration.test.rest.api.server.organization.management.v1.Utils.assertNotBlank;
+import static org.wso2.identity.integration.test.rest.api.server.organization.management.v1.Utils.extractOrganizationIdFromLocationHeader;
+
+/**
+ * Tests for happy paths of the Organization Management REST API.
+ */
+public class OrganizationManagementSuccessTest extends OrganizationManagementBaseTest {
+
+ private Set createdOrgs = new HashSet<>();
+ private String createdOrganizationId;
+ private String createdOrganizationName;
+
+ @Factory(dataProvider = "restAPIUserConfigProvider")
+ public OrganizationManagementSuccessTest(TestUserMode userMode, OrganizationLevel organizationLevel)
+ throws Exception {
+
+ super(userMode, organizationLevel);
+ }
+
+ @AfterClass(alwaysRun = true)
+ @Override
+ public void testFinish() {
+
+ cleanUpOrganizations(createdOrgs);
+ super.testFinish();
+ }
+
+ @Test
+ public void createOrganization() throws Exception {
+
+ JSONObject organizationObject = new JSONObject();
+ String org;
+ String parentId;
+
+ if (OrganizationLevel.SUPER_ORGANIZATION.equals(this.organizationLevel)) {
+ org = "Level1Org";
+ parentId = SUPER_ORGANIZATION_NAME;
+ } else {
+ org = "Level2Org";
+ parentId = subOrganizationId;
+ }
+ organizationObject.put(ORGANIZATION_NAME, org);
+ organizationObject.put(ORGANIZATION_PARENT_ID, parentId);
+
+ Response responseOfPost = getResponseOfPost(ORGANIZATION_MANAGEMENT_API_BASE_PATH,
+ organizationObject.toString());
+ responseOfPost.then()
+ .log().ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_CREATED)
+ .header(HttpHeaders.LOCATION, notNullValue());
+
+ String location = responseOfPost.getHeader(HttpHeaders.LOCATION);
+ String createdOrgId = extractOrganizationIdFromLocationHeader(location);
+ createdOrgs.add(createdOrgId);
+ createdOrganizationId = createdOrgId;
+ createdOrganizationName = org;
+
+ assertNotBlank(createdOrgId);
+ if (organizationLevel == OrganizationLevel.SUB_ORGANIZATION) {
+ // Check whether password recovery is enabled in the created sub-organization.
+ String governanceURL = "/o/" + createdOrganizationId +
+ "/api/server/v1/identity-governance/QWNjb3VudCBNYW5hZ2VtZW50/connectors/YWNjb3VudC1yZWNvdmVyeQ";
+ given()
+ .auth().preemptive().basic(authenticatingUserName, authenticatingCredential)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.ACCEPT, ContentType.JSON)
+ .log().ifValidationFails()
+ .when()
+ .get(backendURL.replace(SERVICES, governanceURL))
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_OK)
+ .body("properties.find { it.name == 'Recovery.Notification.Password.Enable' }.value",
+ equalTo("true"))
+ .body("properties.find { it.name == 'Recovery.NotifySuccess' }.value", equalTo("true"));
+
+ // Check whether application creation is disabled in the sub-organization.
+ String appCreationURL = "/o/" + createdOrganizationId + "/api/server/v1/applications";
+ Response response = given()
+ .auth().preemptive().basic(authenticatingUserName, authenticatingCredential)
+ .contentType(ContentType.JSON)
+ .header(HttpHeaders.ACCEPT, ContentType.JSON)
+ .body(APPLICATION_PAYLOAD)
+ .log().ifValidationFails()
+ .when()
+ .post(backendURL.replace(SERVICES, appCreationURL));
+ response.then()
+ .log().ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_BAD_REQUEST)
+ .body("code", equalTo("ORG-60078"))
+ .body("message", equalTo("Error creating application."))
+ .body("description", equalTo("Applications cannot be created for sub-organizations."));
+ }
+ }
+
+ @Test(dependsOnMethods = {"createOrganization"})
+ public void testGetOrganizationById() throws Exception {
+
+ getResponseOfGet(ORGANIZATION_MANAGEMENT_API_BASE_PATH + "/" + createdOrganizationId)
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .statusCode(HttpStatus.SC_OK)
+ .body(ORGANIZATION_NAME, equalTo(createdOrganizationName));
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementTestData.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementTestData.java
new file mode 100644
index 00000000000..1ae0212a2be
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/OrganizationManagementTestData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1;
+
+/**
+ * Contains test data for organization management REST API tests.
+ */
+public class OrganizationManagementTestData {
+
+ public static final String APPLICATION_PAYLOAD = "{" +
+ "\"name\": \"pickup-dispatch\"," +
+ "\"description\": \"This is the configuration for Pickup-dispatch application.\"," +
+ "\"imageUrl\": \"https://example.com/logo/my-logo.png\"," +
+ "\"accessUrl\": \"https://example.com/login\"," +
+ "\"templateId\": \"b9c5e11e-fc78-484b-9bec-015d247561b8\"," +
+ "\"isManagementApp\": false," +
+ "\"claimConfiguration\": {" +
+ " \"dialect\": \"LOCAL\"," +
+ " \"claimMappings\": [" +
+ " {" +
+ " \"applicationClaim\": \"firstname\"," +
+ " \"localClaim\": {" +
+ " \"uri\": \"http://wso2.org/claims/username\"" +
+ " }" +
+ " }" +
+ " ]," +
+ " \"requestedClaims\": [" +
+ " {" +
+ " \"claim\": {" +
+ " \"uri\": \"http://wso2.org/claims/username\"" +
+ " }," +
+ " \"mandatory\": false" +
+ " }" +
+ " ]," +
+ " \"subject\": {" +
+ " \"claim\": {" +
+ " \"uri\": \"http://wso2.org/claims/username\"" +
+ " }," +
+ " \"includeUserDomain\": false," +
+ " \"includeTenantDomain\": false," +
+ " \"useMappedLocalSubject\": false" +
+ " }," +
+ " \"role\": {" +
+ " \"mappings\": [" +
+ " {" +
+ " \"localRole\": \"admin\"," +
+ " \"applicationRole\": \"Administrator\"" +
+ " }" +
+ " ]," +
+ " \"includeUserDomain\": true," +
+ " \"claim\": {" +
+ " \"uri\": \"http://wso2.org/claims/username\"" +
+ " }" +
+ " }" +
+ "}," +
+ "\"inboundProtocolConfiguration\": {" +
+ " \"oidc\": {" +
+ " \"clientId\": \"rMfbPgCi5oWljNhv8c4Pugfuo8Aa\"," +
+ " \"clientSecret\": \"MkHGGiTdAPfTyUKfXLdyOwelMywt\"," +
+ " \"grantTypes\": [" +
+ " \"authorization_code\"," +
+ " \"password\"" +
+ " ]," +
+ " \"callbackURLs\": [" +
+ " \"regexp=(https://app.example.com/callback1|https://app.example.com/callback2)\"" +
+ " ]," +
+ " \"allowedOrigins\": [" +
+ " \"https://app.example.com\"" +
+ " ]," +
+ " \"publicClient\": false," +
+ " \"pkce\": {" +
+ " \"mandatory\": false," +
+ " \"supportPlainTransformAlgorithm\": true" +
+ " }," +
+ " \"accessToken\": {" +
+ " \"type\": \"JWT\"," +
+ " \"userAccessTokenExpiryInSeconds\": 3600," +
+ " \"applicationAccessTokenExpiryInSeconds\": 3600," +
+ " \"bindingType\": \"cookie\"," +
+ " \"revokeTokensWhenIDPSessionTerminated\": true," +
+ " \"validateTokenBinding\": true" +
+ " }," +
+ " \"refreshToken\": {" +
+ " \"expiryInSeconds\": 86400," +
+ " \"renewRefreshToken\": true" +
+ " }," +
+ " \"idToken\": {" +
+ " \"expiryInSeconds\": 3600," +
+ " \"audience\": [" +
+ " \"http://idp.xyz.com\"," +
+ " \"http://idp.abc.com\"" +
+ " ]," +
+ " \"encryption\": {" +
+ " \"enabled\": false," +
+ " \"algorithm\": \"RSA-OAEP\"," +
+ " \"method\": \"A128CBC+HS256\"" +
+ " }" +
+ " }," +
+ " \"logout\": {" +
+ " \"backChannelLogoutUrl\": \"https://app.example.com/backchannel/callback\"," +
+ " \"frontChannelLogoutUrl\": \"https://app.example.com/frontchannel/callback\"" +
+ " }," +
+ " \"validateRequestObjectSignature\": false," +
+ " \"scopeValidators\": [" +
+ " \"Role based scope validator\"" +
+ " ]" +
+ " }" +
+ "}," +
+ "\"authenticationSequence\": {" +
+ " \"type\": \"DEFAULT\"," +
+ " \"steps\": [" +
+ " {" +
+ " \"id\": 1," +
+ " \"options\": [" +
+ " {" +
+ " \"idp\": \"LOCAL\"," +
+ " \"authenticator\": \"basic\"" +
+ " }" +
+ " ]" +
+ " }" +
+ " ]," +
+ " \"script\": \"string\"," +
+ " \"subjectStepId\": 1," +
+ " \"attributeStepId\": 1" +
+ "}," +
+ "\"advancedConfigurations\": {" +
+ " \"saas\": false," +
+ " \"discoverableByEndUsers\": false," +
+ " \"certificate\": {" +
+ " \"type\": \"string\"," +
+ " \"value\": \"string\"" +
+ " }," +
+ " \"skipLoginConsent\": false," +
+ " \"skipLogoutConsent\": false," +
+ " \"useExternalConsentPage\": false," +
+ " \"returnAuthenticatedIdpList\": false," +
+ " \"enableAuthorization\": true" +
+ "}" +
+ "}";
+}
+
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/Utils.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/Utils.java
new file mode 100644
index 00000000000..21cf0f76900
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/Utils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1;
+
+import org.apache.commons.lang.StringUtils;
+import org.testng.Assert;
+
+/**
+ * Common utility functions used in organization management REST API tests.
+ */
+public class Utils {
+
+ public static String extractOrganizationIdFromLocationHeader(String locationHeaderValue) {
+
+ return StringUtils.substringAfterLast(locationHeaderValue, "/");
+ }
+
+ public static void assertNotBlank(String value) {
+
+ Assert.assertTrue(StringUtils.isNotBlank(value));
+ }
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/model/OrganizationLevel.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/model/OrganizationLevel.java
new file mode 100644
index 00000000000..52fa3423c04
--- /dev/null
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/organization/management/v1/model/OrganizationLevel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.identity.integration.test.rest.api.server.organization.management.v1.model;
+
+public enum OrganizationLevel {
+ SUPER_ORGANIZATION,
+ SUB_ORGANIZATION
+}
diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/utils/OAuth2Constant.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/utils/OAuth2Constant.java
index db72305c51f..67434f4c365 100644
--- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/utils/OAuth2Constant.java
+++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/utils/OAuth2Constant.java
@@ -111,6 +111,14 @@ public final class OAuth2Constant {
public static final String SCOPE_ENDPOINT = "https://localhost:9853/api/server/v1/oidc/scopes";
public static final String TENANT_SCOPE_ENDPOINT = "https://localhost:9853/t/wso2.com/api/server/v1/oidc/scopes";
+ public static final String OAUTH2_RESPONSE_MODE = "response_mode";
+ public static final String RESPONSE_MODE_QUERY = "query";
+ public static final String RESPONSE_MODE_FRAGMENT = "fragment";
+ public static final String RESPONSE_MODE_JWT = "jwt";
+ public static final String RESPONSE_MODE_QUERY_JWT = "query.jwt";
+ public static final String RESPONSE_MODE_FRAGMENT_JWT = "fragment.jwt";
+
+ public static final String RESPONSE_TYPE_CODE_ID_TOKEN = "code id_token";
// Tenanted urls.
public final static String TENANT_PLACEHOLDER = "";
diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/testng.xml b/modules/integration/tests-integration/tests-backend/src/test/resources/testng.xml
index e09c8704bfd..e165c051740 100644
--- a/modules/integration/tests-integration/tests-backend/src/test/resources/testng.xml
+++ b/modules/integration/tests-integration/tests-backend/src/test/resources/testng.xml
@@ -31,6 +31,7 @@
+
@@ -105,6 +106,7 @@
+
@@ -359,4 +361,10 @@
+
+
+
+
+
+
diff --git a/modules/local-authenticators/pom.xml b/modules/local-authenticators/pom.xml
index c439696d903..cdf9819982a 100644
--- a/modules/local-authenticators/pom.xml
+++ b/modules/local-authenticators/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/oauth2-grant-types/pom.xml b/modules/oauth2-grant-types/pom.xml
index 828be835db1..9ef314ad2af 100644
--- a/modules/oauth2-grant-types/pom.xml
+++ b/modules/oauth2-grant-types/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/p2-profile-gen/pom.xml b/modules/p2-profile-gen/pom.xml
index 18147a7f967..1cc6b1b06c0 100644
--- a/modules/p2-profile-gen/pom.xml
+++ b/modules/p2-profile-gen/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
@@ -339,6 +339,11 @@
org.wso2.carbon.identity.framework:org.wso2.carbon.identity.provisioning.server.feature:${carbon.identity.framework.version}
+
+
+ org.wso2.carbon.identity.framework:org.wso2.carbon.identity.api.resource.mgt.server.feature:${carbon.identity.framework.version}
+
+
org.wso2.carbon.identity.tool.validator.sso.saml2:org.wso2.carbon.identity.tools.saml.validator.feature:${identity.tool.samlsso.validator.version}
@@ -825,6 +830,10 @@
${carbon.identity.framework.version}
+
+ org.wso2.carbon.identity.api.resource.mgt.server.feature.group
+ ${carbon.identity.framework.version}
+
org.wso2.carbon.identity.unique.claim.mgt.server.feature.group
@@ -927,7 +936,7 @@
org.wso2.carbon.identity.provisioning.server.feature.group
${carbon.identity.framework.version}
-
+
org.wso2.carbon.identity.tools.saml.validator.feature.group
${identity.tool.samlsso.validator.version}
@@ -1441,6 +1450,10 @@
${carbon.identity.framework.version}
+
+ org.wso2.carbon.identity.api.resource.mgt.server.feature.group
+ ${carbon.identity.framework.version}
+
org.wso2.carbon.identity.unique.claim.mgt.server.feature.group
@@ -1919,6 +1932,10 @@
${carbon.identity.framework.version}
+
+ org.wso2.carbon.identity.api.resource.mgt.server.feature.group
+ ${carbon.identity.framework.version}
+
org.wso2.carbon.identity.unique.claim.mgt.server.feature.group
diff --git a/modules/provisioning-connectors/pom.xml b/modules/provisioning-connectors/pom.xml
index 39894f69f4c..3e270853ad4 100644
--- a/modules/provisioning-connectors/pom.xml
+++ b/modules/provisioning-connectors/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/social-authenticators/pom.xml b/modules/social-authenticators/pom.xml
index 9451c31ea6b..2b25c10ccac 100644
--- a/modules/social-authenticators/pom.xml
+++ b/modules/social-authenticators/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/modules/styles/pom.xml b/modules/styles/pom.xml
index cb1bd32e828..20b6a7e1086 100644
--- a/modules/styles/pom.xml
+++ b/modules/styles/pom.xml
@@ -20,7 +20,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/modules/styles/product/pom.xml b/modules/styles/product/pom.xml
index e60394697a2..d90dae35ed4 100644
--- a/modules/styles/product/pom.xml
+++ b/modules/styles/product/pom.xml
@@ -20,7 +20,7 @@
org.wso2.is
identity-server-styles-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/tests-utils/admin-services/pom.xml b/modules/tests-utils/admin-services/pom.xml
index 74fdfcc36f1..2899b6ffabc 100644
--- a/modules/tests-utils/admin-services/pom.xml
+++ b/modules/tests-utils/admin-services/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-integration-tests-utils
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/tests-utils/admin-stubs/pom.xml b/modules/tests-utils/admin-stubs/pom.xml
index 0ad598e5d37..8fa75534440 100644
--- a/modules/tests-utils/admin-stubs/pom.xml
+++ b/modules/tests-utils/admin-stubs/pom.xml
@@ -21,7 +21,7 @@
org.wso2.is
identity-integration-tests-utils
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../pom.xml
diff --git a/modules/tests-utils/pom.xml b/modules/tests-utils/pom.xml
index 15128b36bd9..be631d45714 100644
--- a/modules/tests-utils/pom.xml
+++ b/modules/tests-utils/pom.xml
@@ -19,7 +19,7 @@
org.wso2.is
identity-server-parent
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
../../pom.xml
diff --git a/pom.xml b/pom.xml
index 63195c824bd..85d3c2cb193 100755
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
identity-server-parent
pom
WSO2 Identity Server
- 7.0.0-alpha-SNAPSHOT
+ 7.0.0-m3-SNAPSHOT
WSO2 Identity Server
http://wso2.org/projects/identity
@@ -1441,6 +1441,11 @@
org.wso2.carbon.identity.secret.mgt.core
${carbon.identity.framework.version}
+
+ org.wso2.carbon.identity.framework
+ org.wso2.carbon.identity.api.resource.mgt
+ ${carbon.identity.framework.version}
+
org.wso2.carbon.identity.saml.common
org.wso2.carbon.identity.saml.common.util
@@ -1964,6 +1969,11 @@
org.wso2.carbon.identity.userstore.configuration.server.feature
${carbon.identity.framework.version}
+
+ org.wso2.carbon.identity.framework
+ org.wso2.carbon.identity.api.resource.mgt.server.feature
+ ${carbon.identity.framework.version}
+
org.wso2.carbon.identity.governance
org.wso2.carbon.identity.multi.attribute.login.service.server.feature
@@ -2293,7 +2303,7 @@
2.5.2
- 1.8.69
+ 1.8.73
5.8.5
@@ -2307,7 +2317,7 @@
6.11.122-SNAPSHOT
5.9.5
5.10.16
- 5.7.3
+ 5.7.4
3.4.26
@@ -2389,8 +2399,8 @@
1.0.14
1.0.0
- 1.3.66
- 1.0.61
+ 1.3.72
+ 1.0.63
1.1.21
1.1.9
@@ -2402,20 +2412,20 @@
2.0.13
- 1.3.19
- 1.2.82
+ 1.3.22
+ 1.2.84
5.5.9
5.5.7
2.3.1
- 2.4.25
+ 2.4.26
1.1.3
1.2.36
- 2.0.4
- 2.0.2
- 2.0.3
+ 2.0.12
+ 2.0.6
+ 2.0.5
1.6.373