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