diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 73dc75db4f2..5c37ef659a8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: ❗️ Issue/Bug report about: Report issue or bug related to the project title: '' -labels: 'bug' +labels: 'Type/Bug' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/doc_issues.md b/.github/ISSUE_TEMPLATE/doc_issues.md index d20a232cf74..76753cbc4f3 100644 --- a/.github/ISSUE_TEMPLATE/doc_issues.md +++ b/.github/ISSUE_TEMPLATE/doc_issues.md @@ -2,7 +2,7 @@ name: 📕 Doc issues about: Please report documentation issues here title: '' -labels: 'docs' +labels: 'Type/Docs' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a2679026f0a..57bdb424dbf 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: ➕ Feature request about: Suggest an idea for this project title: '' -labels: 'feature' +labels: 'Type/NewFeature' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/improvement.md b/.github/ISSUE_TEMPLATE/improvement.md index fa3be360593..31f29c5a91a 100644 --- a/.github/ISSUE_TEMPLATE/improvement.md +++ b/.github/ISSUE_TEMPLATE/improvement.md @@ -2,7 +2,7 @@ name: ✅ Improvement suggestion about: Suggest an improvement for the project title: '' -labels: 'improvement' +labels: 'Type/Improvement' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/test_request.md b/.github/ISSUE_TEMPLATE/test_request.md index 896d1e0e2a3..3b8c0e53d73 100644 --- a/.github/ISSUE_TEMPLATE/test_request.md +++ b/.github/ISSUE_TEMPLATE/test_request.md @@ -2,7 +2,7 @@ name: "✅ Testing Task" about: Create a testing task to improve the quality of the product. title: '' -labels: 'test' +labels: 'Type/Test' assignees: '' --- diff --git a/.github/workflows/product-is-builder-jdk17.yml b/.github/workflows/product-is-builder-jdk17.yml index 3d0c722a641..c94a139c99d 100644 --- a/.github/workflows/product-is-builder-jdk17.yml +++ b/.github/workflows/product-is-builder-jdk17.yml @@ -3,8 +3,8 @@ name: product-is-builder-jdk17 on: workflow_dispatch: schedule: -# At 00:00 on day-of-month 1 - - cron: '0 0 1 * *' +# Daily at 17:00 UTC (10.30 PM SL time) + - cron: '0 17 * * *' env: @@ -39,8 +39,10 @@ jobs: ls cd modules/integration/ mvn clean install --batch-mode | tee mvn-build.log + PR_BUILD_STATUS=$(cat mvn-build.log | grep "\[INFO\] BUILD" | grep -oE '[^ ]+$') PR_TEST_RESULT=$(sed -n -e '/\[INFO\] Results:/,/\[INFO\] Tests run:/ p' mvn-build.log) + PR_BUILD_FINAL_RESULT=$( echo "===========================================================" echo "product-is BUILD $PR_BUILD_STATUS" @@ -48,12 +50,21 @@ jobs: echo "" echo "$PR_TEST_RESULT" ) + PR_BUILD_RESULT_LOG_TEMP=$(echo "$PR_BUILD_FINAL_RESULT" | sed 's/$/%0A/') PR_BUILD_RESULT_LOG=$(echo $PR_BUILD_RESULT_LOG_TEMP) echo "::warning::$PR_BUILD_RESULT_LOG" + PR_BUILD_SUCCESS_COUNT=$(grep -o -i "\[INFO\] BUILD SUCCESS" mvn-build.log | wc -l) - if [ "$PR_BUILD_SUCCESS_COUNT" != "3" ]; then + if [ "$PR_BUILD_SUCCESS_COUNT" != "11" ]; then + echo "Success Count $PR_BUILD_SUCCESS_COUNT" echo "PR BUILD not successfull. Aborting." echo "::error::PR BUILD not successfull. Check artifacts for logs." exit 1 fi + + echo "" + echo "==========================================================" + echo "Build completed" + echo "==========================================================" + echo "" diff --git a/.github/workflows/product-is-builder-jdk21.yml b/.github/workflows/product-is-builder-jdk21.yml new file mode 100644 index 00000000000..632f0b71100 --- /dev/null +++ b/.github/workflows/product-is-builder-jdk21.yml @@ -0,0 +1,70 @@ +name: product-is-builder-jdk21 + +on: + workflow_dispatch: + schedule: +# Daily at 23:00 UTC (04.30 AM SL time) + - cron: '0 23 * * *' + + +env: + MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3 + + +jobs: + build: + runs-on: ubuntu-latest + + env: + JAVA_TOOL_OPTIONS: "-Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true" + + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Set up Adopt JDK 11 + uses: actions/setup-java@v2 + with: + java-version: "11" + distribution: "adopt" + - name: Product-IS build with JDK 11 without Integration Tests + run: | + mvn clean install --batch-mode -Dmaven.test.skip=true | tee mvn-build.log + - name: Set up Adopt JDK 21 + uses: actions/setup-java@v2 + with: + java-version: "21" + distribution: "adopt" + - name: Product-IS build with JDK 21 with Integration Tests + run: | + ls + cd modules/integration/ + mvn clean install --batch-mode | tee mvn-build.log + + PR_BUILD_STATUS=$(cat mvn-build.log | grep "\[INFO\] BUILD" | grep -oE '[^ ]+$') + PR_TEST_RESULT=$(sed -n -e '/\[INFO\] Results:/,/\[INFO\] Tests run:/ p' mvn-build.log) + + PR_BUILD_FINAL_RESULT=$( + echo "===========================================================" + echo "product-is BUILD $PR_BUILD_STATUS" + echo "==========================================================" + echo "" + echo "$PR_TEST_RESULT" + ) + + PR_BUILD_RESULT_LOG_TEMP=$(echo "$PR_BUILD_FINAL_RESULT" | sed 's/$/%0A/') + PR_BUILD_RESULT_LOG=$(echo $PR_BUILD_RESULT_LOG_TEMP) + echo "::warning::$PR_BUILD_RESULT_LOG" + + PR_BUILD_SUCCESS_COUNT=$(grep -o -i "\[INFO\] BUILD SUCCESS" mvn-build.log | wc -l) + if [ "$PR_BUILD_SUCCESS_COUNT" != "11" ]; then + echo "Success Count $PR_BUILD_SUCCESS_COUNT" + echo "PR BUILD not successfull. Aborting." + echo "::error::PR BUILD not successfull. Check artifacts for logs." + exit 1 + fi + + echo "" + echo "==========================================================" + echo "Build completed" + echo "==========================================================" + echo "" diff --git a/modules/api-resources/api-resources-full/pom.xml b/modules/api-resources/api-resources-full/pom.xml index 6db52472deb..00458995709 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml api-resources-full - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT war WSO2 Identity Server - All Rest API diff --git a/modules/api-resources/pom.xml b/modules/api-resources/pom.xml index 55e35f96cbc..e8d6c5950b3 100644 --- a/modules/api-resources/pom.xml +++ b/modules/api-resources/pom.xml @@ -23,12 +23,12 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml api-resources - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT pom WSO2 Identity Server - Rest API diff --git a/modules/authenticators/pom.xml b/modules/authenticators/pom.xml index f9f8a4a988a..a62e8cd241f 100644 --- a/modules/authenticators/pom.xml +++ b/modules/authenticators/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/modules/connectors/pom.xml b/modules/connectors/pom.xml index 95a838e3656..ecf453da1e9 100644 --- a/modules/connectors/pom.xml +++ b/modules/connectors/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/modules/distribution/pom.xml b/modules/distribution/pom.xml index d0a9d350ec0..10f231e8f86 100755 --- a/modules/distribution/pom.xml +++ b/modules/distribution/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/features/org.wso2.identity.styles.feature/pom.xml b/modules/features/org.wso2.identity.styles.feature/pom.xml index dbb2eb7e0c4..effcc8fc8bf 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.1.0-m2-SNAPSHOT + 7.1.0-m4-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 5634b9f61b4..ab4a8bf4c95 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.1.0-m2-SNAPSHOT + 7.1.0-m4-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 1ab4b0eced1..78bb94dee1c 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/features/pom.xml b/modules/features/pom.xml index 138eb89e021..84eae0f1027 100644 --- a/modules/features/pom.xml +++ b/modules/features/pom.xml @@ -17,7 +17,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration-ui-templates/pom.xml b/modules/integration-ui-templates/pom.xml index fc15bcbac78..c06645ec101 100644 --- a/modules/integration-ui-templates/pom.xml +++ b/modules/integration-ui-templates/pom.xml @@ -22,7 +22,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/pom.xml b/modules/integration/pom.xml index f086e42834c..a37194ceee4 100644 --- a/modules/integration/pom.xml +++ b/modules/integration/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/tests-common/admin-clients/pom.xml b/modules/integration/tests-common/admin-clients/pom.xml index 58c4a93f285..890ad2e95c4 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/tests-common/extensions/pom.xml b/modules/integration/tests-common/extensions/pom.xml index d3c6b8688c2..3925de11624 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.1.0-m2-SNAPSHOT + 7.1.0-m4-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 748d9a90c3f..f307eb70e99 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/tests-common/jacoco-report-generator/pom.xml b/modules/integration/tests-common/jacoco-report-generator/pom.xml index b562b93be9a..93f365fdfa2 100644 --- a/modules/integration/tests-common/jacoco-report-generator/pom.xml +++ b/modules/integration/tests-common/jacoco-report-generator/pom.xml @@ -22,7 +22,7 @@ org.wso2.is identity-integration-tests - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/tests-common/pom.xml b/modules/integration/tests-common/pom.xml index b5b09423f58..efb4802f2b9 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/integration/tests-common/ui-pages/pom.xml b/modules/integration/tests-common/ui-pages/pom.xml index 2671a4382e9..dff6e7703cd 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/integration/tests-integration/pom.xml b/modules/integration/tests-integration/pom.xml index 98510b0f1b1..6eb24e057ca 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/integration/tests-integration/tests-backend/pom.xml b/modules/integration/tests-integration/tests-backend/pom.xml index ad39b54dcb5..ef9e6f50d8a 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml @@ -1098,5 +1098,10 @@ jsoup test + + com.nimbusds + nimbus-jose-jwt + test + diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/ApplicationNativeAuthentication2FATestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/ApplicationNativeAuthentication2FATestCase.java new file mode 100644 index 00000000000..e3b6100e892 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/ApplicationNativeAuthentication2FATestCase.java @@ -0,0 +1,758 @@ +/* + * Copyright (c) 2024, 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.applicationNativeAuthentication; + +import com.icegreen.greenmail.util.GreenMailUtil; +import io.restassured.http.ContentType; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import jakarta.mail.Message; +import org.apache.commons.lang.ArrayUtils; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +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.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.automation.engine.context.TestUserMode; +import org.wso2.carbon.identity.application.common.model.idp.xsd.FederatedAuthenticatorConfig; +import org.wso2.carbon.identity.application.common.model.idp.xsd.IdentityProvider; +import org.wso2.identity.integration.common.clients.Idp.IdentityProviderMgtServiceClient; +import org.wso2.identity.integration.test.oauth2.OAuth2ServiceAbstractIntegrationTest; +import org.wso2.identity.integration.test.oidc.OIDCUtilTest; +import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.AdvancedApplicationConfiguration; +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.AuthenticationSequence; +import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.Authenticator; +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.rest.api.user.common.model.Email; +import org.wso2.identity.integration.test.rest.api.user.common.model.ListObject; +import org.wso2.identity.integration.test.rest.api.user.common.model.Name; +import org.wso2.identity.integration.test.rest.api.user.common.model.PatchOperationRequestObject; +import org.wso2.identity.integration.test.rest.api.user.common.model.RoleItemAddGroupobj; +import org.wso2.identity.integration.test.rest.api.user.common.model.UserObject; +import org.wso2.identity.integration.test.restclients.SCIM2RestClient; +import org.wso2.identity.integration.test.util.Utils; +import org.wso2.identity.integration.test.utils.OAuth2Constant; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static io.restassured.RestAssured.given; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.AUTHENTICATOR; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.AUTHENTICATORS; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.AUTHENTICATOR_ID; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.AUTH_DATA_CODE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.AUTH_DATA_SESSION_STATE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.CODE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.CONFIDENTIAL; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.CONTENT_TYPE_APPLICATION_JSON; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.DESCRIPTION; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.DISPLAY_NAME; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.FAIL_INCOMPLETE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.FLOW_ID; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.FLOW_STATUS; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.FLOW_TYPE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.HREF; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.I18N_KEY; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.IDP; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.LINKS; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.MESSAGE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.MESSAGES; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.MESSAGE_ID; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.METADATA; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.NEXT_STEP; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.ORDER; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.PARAM; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.PARAMS; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.PROMPT_TYPE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.REQUIRED_PARAMS; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.RESPONSE_MODE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.STEP_TYPE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.SUCCESS_COMPLETED; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.TEST_APP_NAME; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.TEST_PASSWORD; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.TEST_USER_NAME; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.TRACE_ID; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.TYPE; +import static org.wso2.identity.integration.test.applicationNativeAuthentication.Constants.UTF_8; + +/** + * Integration test class for testing the native authentication flow in an OAuth 2.0-enabled application. + * This test case extends {@link OAuth2ServiceAbstractIntegrationTest} and focuses on scenarios related + * to native authentication, covering the interaction between the application, authorization server, and user. + * The app contains basic as first authentication matrix and email otp as second authentication step. + */ +public class ApplicationNativeAuthentication2FATestCase extends OAuth2ServiceAbstractIntegrationTest { + + private String appId; + private String flowId; + private String flowStatus; + private String authenticatorId; + private String href; + private JSONArray paramsArray; + private CloseableHttpClient client; + private String code; + protected SCIM2RestClient scim2RestClient; + private UserObject userObject; + private String userId; + + + @BeforeClass(alwaysRun = true) + public void testInit() throws Exception { + + Utils.getMailServer().purgeEmailFromAllMailboxes(); + super.init(TestUserMode.SUPER_TENANT_USER); + + Lookup cookieSpecRegistry = RegistryBuilder.create() + .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider()) + .build(); + RequestConfig requestConfig = RequestConfig.custom() + .setCookieSpec(CookieSpecs.DEFAULT) + .build(); + client = HttpClientBuilder.create() + .setDefaultCookieSpecRegistry(cookieSpecRegistry) + .setDefaultRequestConfig(requestConfig) + .build(); + + setSystemproperties(); + scim2RestClient = new SCIM2RestClient(serverURL, tenantInfo); + userObject = initUser(); + createUser(userObject); + // Reset the idp cache object to remove effects from previous test cases. + resetResidentIDPCache(); + } + + @AfterClass(alwaysRun = true) + public void atEnd() throws Exception { + + deleteApp(appId); + deleteUser(userObject); + scim2RestClient = null; + + // Nullifying attributes. + consumerKey = null; + consumerSecret = null; + appId = null; + flowId = null; + flowStatus = null; + code = null; + authenticatorId = null; + href = null; + paramsArray = null; + client.close(); + restClient.closeHttpClient(); + Utils.getMailServer().purgeEmailFromAllMailboxes(); + } + + private UserObject initUser() { + + UserObject user = new UserObject(); + user.setUserName(TEST_USER_NAME); + user.setPassword(TEST_PASSWORD); + user.setName(new Name().givenName(OIDCUtilTest.firstName).familyName(OIDCUtilTest.lastName)); + user.addEmail(new Email().value(OIDCUtilTest.email)); + return user; + } + + /** + * Creates a user. + * + * @param user user instance. + * @throws Exception If an error occurred while creating a user. + */ + private void createUser(UserObject user) throws Exception { + + scim2RestClient = new SCIM2RestClient(serverURL, tenantInfo); + userId = scim2RestClient.createUser(user); + + RoleItemAddGroupobj rolePatchReqObject = new RoleItemAddGroupobj(); + rolePatchReqObject.setOp(RoleItemAddGroupobj.OpEnum.ADD); + rolePatchReqObject.setPath("users"); + rolePatchReqObject.addValue(new ListObject().value(userId)); + + String roleId = scim2RestClient.getRoleIdByName("everyone"); + scim2RestClient.updateUserRole(new PatchOperationRequestObject().addOperations(rolePatchReqObject), roleId); + } + + /** + * Deletes a user. + * + * @param user user instance. + * @throws Exception If an error occurred while deleting a user. + */ + private void deleteUser(UserObject user) throws Exception { + + log.info("Deleting User " + user.getUserName()); + scim2RestClient.deleteUser(userId); + } + + @Test(groups = "wso2.is", description = "Check Oauth2 application registration for default configurations.") + 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."); + + appId = application.getId(); + Assert.assertTrue(application.getAdvancedConfigurations().getEnableAPIBasedAuthentication(), + "API Base Authentication expected to false by default but set as true."); + + } + + @Test(groups = "wso2.is", description = "Send init authorize POST request.", + dependsOnMethods = "testRegisterApplication") + public void testSendInitAuthRequestPost() throws Exception { + + HttpResponse response = sendPostRequestWithParameters(client, buildOAuth2Parameters(consumerKey), + OAuth2Constant.AUTHORIZE_ENDPOINT_URL); + Assert.assertNotNull(response, "Authorization request failed. Authorized response is null."); + + String responseString = EntityUtils.toString(response.getEntity(), UTF_8); + EntityUtils.consume(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(responseString); + Assert.assertNotNull(json, "Client Native Authentication Init response is null."); + validInitClientNativeAuthnResponse(json); + } + + @Test(groups = "wso2.is", description = "Send Basic authentication POST request.", + dependsOnMethods = "testSendInitAuthRequestPost") + public void testSendBasicAuthRequestWithFalseAuthenticator() throws Exception { + + String body = "{\n" + + " \"flowId\": \"" + flowId + "\",\n" + + " \"selectedAuthenticator\": {\n" + + " \"authenticatorId\": \"" + "falseAuthenticatorId" + "\",\n" + + " \"params\": {\n" + + " \"username\": \"" + TEST_USER_NAME + "\",\n" + + " \"password\": \"" + TEST_PASSWORD + "\"\n" + + " }\n" + + " }\n" + + "}"; + + Response authnResponse = getResponseOfJSONPost( href, body, new HashMap<>()); + ExtractableResponse extractableResponse = authnResponse.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_BAD_REQUEST) + .and() + .assertThat() + .header(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .extract(); + Assert.assertNotNull(extractableResponse, "Basic Authentication request failed. Authentication response is null."); + + validateFailedBasicAuthenticationResponseBody(extractableResponse); + } + + @Test(groups = "wso2.is", description = "Send Basic authentication POST request.", + dependsOnMethods = "testSendBasicAuthRequestWithFalseAuthenticator") + public void testSendBasicAuthRequestPostWithFalsePassword() throws Exception { + + testSendInitAuthRequestPost(); + String body = "{\n" + + " \"flowId\": \"" + flowId + "\",\n" + + " \"selectedAuthenticator\": {\n" + + " \"authenticatorId\": \"" + authenticatorId + "\",\n" + + " \"params\": {\n" + + " \"username\": \"" + TEST_USER_NAME + "\",\n" + + " \"password\": \"" + "FALSE_TEST_PASSWORD" + "\"\n" + + " }\n" + + " }\n" + + "}"; + + Response authnResponse = getResponseOfJSONPost( href, body, new HashMap<>()); + ExtractableResponse extractableResponse = authnResponse.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .and() + .assertThat() + .header(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .extract(); + Assert.assertNotNull(extractableResponse, "Basic Authentication request failed. Authentication response is null."); + + validateBasicFailedAuthenticationResponseBody(extractableResponse); + } + + @Test(groups = "wso2.is", description = "Send Basic authentication POST request.", + dependsOnMethods = "testSendBasicAuthRequestPostWithFalsePassword") + public void testSendBasicAuthRequestPost() throws Exception { + + String body = "{\n" + + " \"flowId\": \"" + flowId + "\",\n" + + " \"selectedAuthenticator\": {\n" + + " \"authenticatorId\": \"" + authenticatorId + "\",\n" + + " \"params\": {\n" + + " \"username\": \"" + TEST_USER_NAME + "\",\n" + + " \"password\": \"" + TEST_PASSWORD + "\"\n" + + " }\n" + + " }\n" + + "}"; + + Response authnResponse = getResponseOfJSONPost( href, body, new HashMap<>()); + ExtractableResponse extractableResponse = authnResponse.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .and() + .assertThat() + .header(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .extract(); + Assert.assertNotNull(extractableResponse, "Basic Authentication request failed. Authentication response is null."); + + validateBasicAuthenticationResponseBody(extractableResponse); + } + + @Test(groups = "wso2.is", description = "Send Email OTP POST request.", + dependsOnMethods = "testSendBasicAuthRequestPost") + public void testSendEmailOTPRequestPost() { + + String emailOTP = getOTPFromEmail(); + + if (emailOTP == null) { + Assert.fail("Unable to retrieve email otp from the email otp body"); + } + String body = "{\n" + + " \"flowId\": \"" + flowId + "\",\n" + + " \"selectedAuthenticator\": {\n" + + " \"authenticatorId\": \"" + authenticatorId + "\",\n" + + " \"params\": {\n" + + " \"OTPCode\": \"" + emailOTP + "\"\n" + + " }\n" + + " }\n" + + "}"; + + Response authnResponse = getResponseOfJSONPost( href, body, new HashMap<>()); + ExtractableResponse extractableResponse = authnResponse.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .and() + .assertThat() + .header(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .extract(); + Assert.assertNotNull(extractableResponse, "Email OTP Authentication request failed. " + + "Authentication response is null."); + + validateEmailOTPAuthenticationResponseBody(extractableResponse); + } + + /** + * Validates specific fields in the JSON response of a basic authentication response. + * + * @param extractableResponse The ExtractableResponse containing the JSON response + */ + private void validateEmailOTPAuthenticationResponseBody(ExtractableResponse extractableResponse) { + + // Validate specific fields in the JSON response + flowStatus = extractableResponse + .jsonPath() + .getString(FLOW_STATUS); + Assert.assertEquals(flowStatus, SUCCESS_COMPLETED); + + code = extractableResponse + .jsonPath() + .getString(AUTH_DATA_CODE); + Assert.assertNotNull(code, "Authorization Code is null in the authData"); + + Assert.assertNotNull(extractableResponse + .jsonPath() + .getString(AUTH_DATA_SESSION_STATE), "Session state is null in the authData"); + } + + /** + * 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, OAuth2Constant.OAUTH2_GRANT_TYPE_AUTHORIZATION_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(TEST_APP_NAME); + application.advancedConfigurations(new AdvancedApplicationConfiguration()); + application.getAdvancedConfigurations().setEnableAPIBasedAuthentication(true); + application.setAuthenticationSequence(new AuthenticationSequence() + .type(AuthenticationSequence.TypeEnum.USER_DEFINED) + .addStepsItem(new org.wso2.identity.integration.test.rest.api.server.application.management. + v1.model.AuthenticationStep() + .id(1) + .addOptionsItem(new Authenticator() + .idp("LOCAL") + .authenticator("BasicAuthenticator")))); + application.getAuthenticationSequence() + .addStepsItem(new org.wso2.identity.integration.test.rest.api.server.application.management. + v1.model.AuthenticationStep() + .id(2) + .addOptionsItem(new Authenticator() + .idp("LOCAL") + .authenticator("email-otp-authenticator"))); + String appId = addApplication(application); + return getApplication(appId); + } + + /** + * Builds a list of OAuth 2.0 parameters required for initiating the authorization process. + * The method constructs and returns a list of parameters necessary for initiating the OAuth 2.0 authorization process. + * + * @param consumerKey The client's unique identifier in the OAuth 2.0 system + * @return A list of NameValuePair representing the OAuth 2.0 parameters + */ + private List buildOAuth2Parameters(String consumerKey) { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, + OAuth2Constant.AUTHORIZATION_CODE_NAME)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_MODE, RESPONSE_MODE)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_CLIENT_ID, consumerKey)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.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())); + + return urlParameters; + } + + /** + * Validates the structure and content of a Client Native Authentication JSON response. + * The method checks for the presence of required keys and their expected types in the provided JSON. + * It verifies the format of the authentication flow, authenticators, metadata, and required parameters. + * If the JSON response is not in the expected format, the method asserts failures using JUnit's Assert.fail(). + * + * @param json The JSON object representing the Client Native Authentication response + */ + private void validInitClientNativeAuthnResponse(JSONObject json) { + + // Check for the presence of required keys and their expected types + if (json.containsKey(FLOW_ID) && json.containsKey(FLOW_STATUS) && json.containsKey(FLOW_TYPE) && + json.containsKey(NEXT_STEP) && json.containsKey(LINKS)) { + + flowId = (String) json.get(FLOW_ID); + flowStatus = (String) json.get(FLOW_STATUS); + + JSONObject nextStepNode = (JSONObject) json.get(NEXT_STEP); + if (nextStepNode.containsKey(STEP_TYPE) && nextStepNode.containsKey(AUTHENTICATORS)) { + JSONArray authenticatorsArray = (JSONArray) nextStepNode.get(AUTHENTICATORS); + if (!authenticatorsArray.isEmpty()) { + JSONObject authenticator = (JSONObject) authenticatorsArray.get(0); + if (authenticator.containsKey(AUTHENTICATOR_ID) && authenticator.containsKey(AUTHENTICATOR) && + authenticator.containsKey(IDP) && authenticator.containsKey(METADATA) && + authenticator.containsKey(REQUIRED_PARAMS)) { + + authenticatorId = (String) authenticator.get(AUTHENTICATOR_ID); + JSONObject metadataNode = (JSONObject) authenticator.get(METADATA); + if (metadataNode.containsKey(PROMPT_TYPE) && metadataNode.containsKey(PARAMS)) { + paramsArray = (JSONArray) metadataNode.get(PARAMS); + if (paramsArray.isEmpty()) { + Assert.fail("Content of param for the authenticator is null in " + + "Client native authentication JSON Response."); + } + } else { + Assert.fail("Params for the authenticator is null in " + + "Client native authentication JSON Response."); + } + } + } else { + Assert.fail("Authenticator is not expected format in Client native authentication"); + } + } else { + Assert.fail("Authenticators in Client native authentication JSON Response is null, " + + "expecting list of Authentication."); + } + JSONArray links = (JSONArray) json.get(LINKS); + JSONObject link = (JSONObject) links.get(0); + if (link.containsKey(HREF)) { + href = link.get(HREF).toString(); + } else { + Assert.fail("Link is not available for next step in Client native authentication JSON Response."); + } + } else { + Assert.fail("Client native authentication JSON Response is not in expected format."); + } + } + + /** + * Invoke given endpointUri for JSON POST request with given body, headers and Basic. + * + * @param endpointUri endpoint to be invoked + * @param body payload + * @param headers list of headers to be added to the request + * @return response + */ + protected Response getResponseOfJSONPost(String endpointUri, String body, Map headers) { + + return given() + .contentType(ContentType.JSON) + .headers(headers) + .body(body) + .when() + .post(endpointUri); + } + + private String getOTPFromEmail() { + + Assert.assertTrue(Utils.getMailServer().waitForIncomingEmail(10000, 1)); + Message[] messages = Utils.getMailServer().getReceivedMessages(); + String body = GreenMailUtil.getBody(messages[0]).replaceAll("=\r?\n", ""); + + String otpPattern = "One-Time Passcode:\\s*(\\d+)"; + Pattern pattern = Pattern.compile(otpPattern); + Matcher matcher = pattern.matcher(body); + + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + private void resetResidentIDPCache() throws Exception { + + IdentityProviderMgtServiceClient superTenantIDPMgtClient = + new IdentityProviderMgtServiceClient(sessionCookie, backendURL); + IdentityProvider residentIdp = superTenantIDPMgtClient.getResidentIdP(); + + FederatedAuthenticatorConfig[] federatedAuthenticatorConfigs = + residentIdp.getFederatedAuthenticatorConfigs(); + for (FederatedAuthenticatorConfig authenticatorConfig : federatedAuthenticatorConfigs) { + if (!authenticatorConfig.getName().equalsIgnoreCase("samlsso")) { + federatedAuthenticatorConfigs = (FederatedAuthenticatorConfig[]) + ArrayUtils.removeElement(federatedAuthenticatorConfigs, + authenticatorConfig); + } + } + residentIdp.setFederatedAuthenticatorConfigs(federatedAuthenticatorConfigs); + superTenantIDPMgtClient.updateResidentIdP(residentIdp); + } + + private void validateFailedBasicAuthenticationResponseBody(ExtractableResponse extractableResponse) + throws ParseException { + + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(extractableResponse.body().asString()); + + + // Check if the required keys are present + if (json.containsKey(CODE) && json.containsKey(MESSAGE) && + json.containsKey(DESCRIPTION) && json.containsKey(TRACE_ID)) { + + // Extract and validate the values (optional) + String code = (String) json.get(CODE); + String message = (String) json.get(MESSAGE); + String description = (String) json.get(DESCRIPTION); + String traceId = (String) json.get(TRACE_ID); + + // Example validation: ensure no fields are null or empty + if (code == null || code.isEmpty()) { + Assert.fail("Code is missing or empty in the JSON response."); + } + + if (message == null || message.isEmpty()) { + Assert.fail("Message is missing or empty in the JSON response."); + } + + if (description == null || description.isEmpty()) { + Assert.fail("Description is missing or empty in the JSON response."); + } + + if (traceId == null || traceId.isEmpty()) { + Assert.fail("TraceId is missing or empty in the JSON response."); + } + + } else { + Assert.fail("JSON response is missing one or more required fields."); + } + } + + private void validateBasicFailedAuthenticationResponseBody(ExtractableResponse extractableResponse) + throws ParseException { + + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(extractableResponse.body().asString()); + + // Check for the presence of required keys and their expected types + if (json.containsKey(FLOW_ID) && json.containsKey(FLOW_STATUS) && json.containsKey(FLOW_TYPE) && + json.containsKey(NEXT_STEP) && json.containsKey(LINKS)) { + + Assert.assertEquals(flowId, (String) json.get(FLOW_ID), "Basic authentication " + + "JSON Response flow id is not same as init response."); + flowId = (String) json.get(FLOW_ID); + flowStatus = (String) json.get(FLOW_STATUS); + Assert.assertEquals(flowStatus, FAIL_INCOMPLETE); + + JSONObject nextStepNode = (JSONObject) json.get(NEXT_STEP); + if (nextStepNode.containsKey(STEP_TYPE) && nextStepNode.containsKey(AUTHENTICATORS) + && nextStepNode.containsKey(MESSAGES)) { + JSONArray messagesArray = (JSONArray) nextStepNode.get(MESSAGES); + // Ensure the array is not empty + if (!messagesArray.isEmpty()) { + JSONObject messageObject = (JSONObject) messagesArray.get(0); + + // Check for required fields within each message object + if (messageObject.containsKey(TYPE) && messageObject.containsKey(MESSAGE_ID) && + messageObject.containsKey(MESSAGE) && messageObject.containsKey(I18N_KEY)) { + + // Extract and validate values (optional) + String type = (String) messageObject.get(TYPE); + String messageId = (String) messageObject.get(MESSAGE_ID); + String message = (String) messageObject.get(MESSAGE); + String i18nKey = (String) messageObject.get(I18N_KEY); + + // Example validation: Ensure none of the values are null or empty + if (type == null || type.isEmpty()) { + Assert.fail("Type is missing or empty in the messages array."); + } + + if (messageId == null || messageId.isEmpty()) { + Assert.fail("Message ID is missing or empty in the messages array."); + } + + if (message == null || message.isEmpty()) { + Assert.fail("Message is missing or empty in the messages array."); + } + + if (i18nKey == null || i18nKey.isEmpty()) { + Assert.fail("i18nKey is missing or empty in the messages array."); + } + + } else { + Assert.fail("A required field is missing in the message object."); + } + } else { + Assert.fail("Messages array is empty."); + } + } else { + Assert.fail("NextStep is missing required fields in Basic authentication JSON Response."); + } + } else { + Assert.fail("Basic authentication JSON Response is missing required fields."); + } + } + + private void validateBasicAuthenticationResponseBody(ExtractableResponse extractableResponse) + throws ParseException { + + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(extractableResponse.body().asString()); + + // Check for the presence of required keys and their expected types + if (json.containsKey(FLOW_ID) && json.containsKey(FLOW_STATUS) && json.containsKey(FLOW_TYPE) && + json.containsKey(NEXT_STEP) && json.containsKey(LINKS)) { + + Assert.assertEquals(flowId, (String) json.get(FLOW_ID), "Basic authentication " + + "JSON Response flow id is not same as init response."); + flowId = (String) json.get(FLOW_ID); + flowStatus = (String) json.get(FLOW_STATUS); + + JSONObject nextStepNode = (JSONObject) json.get(NEXT_STEP); + if (nextStepNode.containsKey(STEP_TYPE) && nextStepNode.containsKey(AUTHENTICATORS)) { + JSONArray authenticatorsArray = (JSONArray) nextStepNode.get(AUTHENTICATORS); + if (!authenticatorsArray.isEmpty()) { + JSONObject authenticator = (JSONObject) authenticatorsArray.get(0); + if (authenticator.containsKey(AUTHENTICATOR_ID) && authenticator.containsKey(AUTHENTICATOR) && + authenticator.containsKey(IDP) && authenticator.containsKey(METADATA) && + authenticator.containsKey(REQUIRED_PARAMS)) { + + authenticatorId = (String) authenticator.get(AUTHENTICATOR_ID); + JSONObject metadataNode = (JSONObject) authenticator.get(METADATA); + if (metadataNode.containsKey(PROMPT_TYPE) && metadataNode.containsKey(PARAMS)) { + JSONArray paramsArray = (JSONArray) metadataNode.get(PARAMS); + if (!paramsArray.isEmpty()) { + JSONObject param = (JSONObject) paramsArray.get(0); + if (!param.containsKey(PARAM) || !param.containsKey(TYPE) || + !param.containsKey(ORDER) || !param.containsKey(I18N_KEY) || + !param.containsKey(DISPLAY_NAME) || !param.containsKey(CONFIDENTIAL)) { + Assert.fail("Param for the authenticator is not in the expected format."); + } + + } else { + Assert.fail("Params for the authenticator is empty in Basic authentication " + + "JSON Response."); + } + } else { + Assert.fail("Metadata for the authenticator is missing required fields in Basic " + + "authentication JSON Response."); + } + } else { + Assert.fail("Authenticator is missing required fields in Basic authentication JSON Response."); + } + } else { + Assert.fail("Authenticators list is empty in Basic authentication JSON Response."); + } + } else { + Assert.fail("NextStep is missing required fields in Basic authentication JSON Response."); + } + + JSONArray linksArray = (JSONArray) json.get(LINKS); + if (!linksArray.isEmpty()) { + JSONObject link = (JSONObject) linksArray.get(0); + if (!link.containsKey(HREF)) { + Assert.fail("Href for the link is missing in Client native authentication JSON Response."); + } + } else { + Assert.fail("Links array is empty in Client native authentication JSON Response."); + } + } else { + Assert.fail("Basic authentication JSON Response is missing required fields."); + } + } +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/Constants.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/Constants.java index 63ce5c8214a..a524936a27f 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/Constants.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/applicationNativeAuthentication/Constants.java @@ -60,4 +60,15 @@ public class Constants { public static final String SUCCESS_COMPLETED = "SUCCESS_COMPLETED"; public static final String AUTH_DATA_CODE = "authData.code"; public static final String AUTH_DATA_SESSION_STATE = "authData.session_state"; + public static final String PARAM = "param"; + public static final String TYPE = "type"; + public static final String ORDER = "order"; + public static final String I18N_KEY = "i18nKey"; + public static final String DISPLAY_NAME = "displayName"; + public static final String CONFIDENTIAL = "confidential"; + public static final String MESSAGE = "message"; + public static final String DESCRIPTION = "description"; + public static final String MESSAGE_ID = "messageId"; + public static final String MESSAGES = "messages"; + public static final String FAIL_INCOMPLETE = "FAIL_INCOMPLETE"; } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/AdaptiveScriptInitializerTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/AdaptiveScriptInitializerTestCase.java deleted file mode 100644 index fbb62b99981..00000000000 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/AdaptiveScriptInitializerTestCase.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. 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.auth; - -import java.io.File; - -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.wso2.carbon.automation.extensions.servers.utils.ServerLogReader; -import org.wso2.carbon.integration.common.utils.exceptions.AutomationUtilException; -import org.wso2.carbon.integration.common.utils.mgt.ServerConfigurationManager; - -/** - * Initiation Test for adaptive authentication. - */ -public class AdaptiveScriptInitializerTestCase extends AbstractAdaptiveAuthenticationTestCase { - - private ServerConfigurationManager serverConfigurationManager; - - private int javaVersion; - private static String enableInfo = "Adaptive authentication successfully enabled."; - private static String disableInfo = "Adaptive authentication successfully disabled."; - - @BeforeTest(alwaysRun = true) - public void testInit() throws Exception { - - super.init(); - serverConfigurationManager = new ServerConfigurationManager(isServer); - javaVersion = getJavaVersion(); - // Download OpenJDK Nashorn only if the JDK version is Higher or Equal to 15. - if (javaVersion >= 15) { - runAdaptiveAuthenticationDependencyScript(false); - } - } - - /** - * Get Java Major Version from System Property. - * - * @return Java Major Version - */ - private int getJavaVersion() { - - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { - version = version.substring(2, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - return Integer.parseInt(version); - } - - private void runAdaptiveAuthenticationDependencyScript(boolean disable) { - - ServerLogReader inputStreamHandler; - ServerLogReader errorStreamHandler; - String targetFolder = System.getProperty("carbon.home"); - String scriptFolder = getTestArtifactLocation() + File.separator; - Process tempProcess = null; - File scriptFile = new File(scriptFolder); - Runtime runtime = Runtime.getRuntime(); - - try { - if (System.getProperty("os.name").toLowerCase().contains("windows")) { - log.info("Operating System is Windows. Executing batch script"); - if (disable) { - // TODO https://github.com/wso2/product-is/issues/14301 - restartServer(); - tempProcess = runtime.exec( - new String[] { "cmd", "/c", "adaptive.bat", targetFolder, "DISABLE" }, null, scriptFile); - } else { - tempProcess = runtime.exec( - new String[] { "cmd", "/c", "adaptive.bat", targetFolder }, null, scriptFile); - } - errorStreamHandler = new ServerLogReader("errorStream", - tempProcess.getErrorStream()); - inputStreamHandler = new ServerLogReader("inputStream", - tempProcess.getInputStream()); - inputStreamHandler.start(); - errorStreamHandler.start(); - boolean runStatus = waitForMessage(inputStreamHandler, disable); - log.info("Status Message : " + runStatus); - restartServer(); - } else { - log.info("Operating system is not windows. Executing shell script"); - if (disable) { - tempProcess = runtime.getRuntime().exec( - new String[] { "/bin/bash", "adaptive.sh", targetFolder, "DISABLE" }, null, scriptFile); - } else { - tempProcess = runtime.getRuntime().exec( - new String[] { "/bin/bash", "adaptive.sh", targetFolder }, null, scriptFile); - } - errorStreamHandler = new ServerLogReader("errorStream", - tempProcess.getErrorStream()); - inputStreamHandler = new ServerLogReader("inputStream", - tempProcess.getInputStream()); - inputStreamHandler.start(); - errorStreamHandler.start(); - boolean runStatus = waitForMessage(inputStreamHandler, disable); - log.info("Status Message : " + runStatus); - restartServer(); - } - } catch (Exception e) { - log.error("Failed to execute adaptive authentication dependency script", e); - } finally { - if (tempProcess != null) { - tempProcess.destroy(); - } - } - } - - private void restartServer() throws AutomationUtilException { - - serverConfigurationManager.restartGracefully(); - } - - private boolean waitForMessage(ServerLogReader inputStreamHandler, boolean disable) { - long time = System.currentTimeMillis() + 60 * 1000; - String message = enableInfo; - if (disable) { - message = disableInfo; - } - while (System.currentTimeMillis() < time) { - if (inputStreamHandler.getOutput().contains(message)) { - return true; - } - } - return false; - } - - @AfterTest(alwaysRun = true) - public void resetUserstoreConfig() throws Exception { - - super.init(); - javaVersion = (javaVersion == 0) ? getJavaVersion() : javaVersion; - if (javaVersion >= 15) { - runAdaptiveAuthenticationDependencyScript(false); - } - serverConfigurationManager.restoreToLastConfiguration(false); - } -} diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/NashornAdaptiveScriptInitializerTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/NashornAdaptiveScriptInitializerTestCase.java index 3d9804e9328..5788b86bd02 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/NashornAdaptiveScriptInitializerTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/NashornAdaptiveScriptInitializerTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2022 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 @@ -18,38 +18,138 @@ package org.wso2.identity.integration.test.auth; +import java.io.File; + +import org.apache.commons.logging.Log; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; +import org.wso2.carbon.automation.engine.frameworkutils.FrameworkPathUtil; +import org.wso2.carbon.automation.extensions.servers.utils.ServerLogReader; +import org.wso2.carbon.integration.common.utils.exceptions.AutomationUtilException; import org.wso2.carbon.integration.common.utils.mgt.ServerConfigurationManager; import org.wso2.carbon.utils.CarbonUtils; -import org.wso2.identity.integration.common.utils.ISIntegrationTest; +import org.wso2.identity.integration.test.util.Utils; -import java.io.File; +/** + * Initiation Test for adaptive authentication. + */ +public class NashornAdaptiveScriptInitializerTestCase extends AbstractAdaptiveAuthenticationTestCase { -public class NashornAdaptiveScriptInitializerTestCase extends ISIntegrationTest { + private ServerConfigurationManager serverConfigurationManager; - private ServerConfigurationManager scm; - private File defaultConfigFile; + private int javaVersion; @BeforeTest(alwaysRun = true) - public void initScriptEngineConfig() throws Exception { + public void testInit() throws Exception { super.init(); + serverConfigurationManager = new ServerConfigurationManager(isServer); String carbonHome = CarbonUtils.getCarbonHome(); - defaultConfigFile = getDeploymentTomlFile(carbonHome); + File defaultConfigFile = getDeploymentTomlFile(carbonHome); + + javaVersion = Utils.getJavaVersion(); + String identityNewResourceFileName = "nashorn_script_engine_config.toml"; + + if (javaVersion >= 15) { + // Download OpenJDK Nashorn only if the JDK version is Higher or Equal to 15. + runAdaptiveAuthenticationDependencyScript(false, serverConfigurationManager, log); + identityNewResourceFileName = "openjdknashorn_script_engine_config.toml"; + } + File scriptEngineConfigFile = new File( getISResourceLocation() + File.separator + "scriptEngine" + File.separator + - "nashorn_script_engine_config.toml"); - scm = new ServerConfigurationManager(isServer); - scm.applyConfiguration(scriptEngineConfigFile, defaultConfigFile, true, true); + identityNewResourceFileName); + serverConfigurationManager.applyConfigurationWithoutRestart(scriptEngineConfigFile, defaultConfigFile, true); + serverConfigurationManager.restartGracefully(); + } + + protected static void runAdaptiveAuthenticationDependencyScript(boolean disable, ServerConfigurationManager scm, Log logger) { + + ServerLogReader inputStreamHandler; + ServerLogReader errorStreamHandler; + String targetFolder = System.getProperty("carbon.home"); + String scriptFolder = FrameworkPathUtil.getSystemResourceLocation() + File.separator; + Process tempProcess = null; + File scriptFile = new File(scriptFolder); + Runtime runtime = Runtime.getRuntime(); + + try { + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + logger.info("Operating System is Windows. Executing batch script"); + if (disable) { + /* + Restarting before the excution to release the locks on nashorn + and asm-util jars in the dropins directory. + */ + scm.restartGracefully(); + tempProcess = runtime.exec( + new String[]{"cmd", "/c", "adaptive.bat", targetFolder, "DISABLE"}, null, scriptFile); + } else { + tempProcess = runtime.exec( + new String[]{"cmd", "/c", "adaptive.bat", targetFolder}, null, scriptFile); + } + errorStreamHandler = new ServerLogReader("errorStream", tempProcess.getErrorStream()); + inputStreamHandler = new ServerLogReader("inputStream", tempProcess.getInputStream()); + inputStreamHandler.start(); + errorStreamHandler.start(); + boolean runStatus = waitForMessage(inputStreamHandler, disable); + logger.info("Status Message : " + runStatus); + scm.restartGracefully(); + } else { + logger.info("Operating system is not windows. Executing shell script"); + if (disable) { + tempProcess = Runtime.getRuntime().exec( + new String[]{"/bin/bash", "adaptive.sh", targetFolder, "DISABLE"}, null, scriptFile); + } else { + tempProcess = Runtime.getRuntime().exec( + new String[]{"/bin/bash", "adaptive.sh", targetFolder}, null, scriptFile); + } + errorStreamHandler = new ServerLogReader("errorStream", tempProcess.getErrorStream()); + inputStreamHandler = new ServerLogReader("inputStream", tempProcess.getInputStream()); + inputStreamHandler.start(); + errorStreamHandler.start(); + boolean runStatus = waitForMessage(inputStreamHandler, disable); + logger.info("Status Message : " + runStatus); + scm.restartGracefully(); + } + } catch (Exception e) { + logger.error("Failed to execute adaptive authentication dependency script", e); + } finally { + if (tempProcess != null) { + tempProcess.destroy(); + } + } + } + + private void restartServer() throws AutomationUtilException { + + serverConfigurationManager.restartGracefully(); + } + + private static boolean waitForMessage(ServerLogReader inputStreamHandler, boolean disable) { + + long time = System.currentTimeMillis() + 60 * 1000; + String message = "Adaptive authentication successfully enabled."; + if (disable) { + message = "Adaptive authentication successfully disabled."; + } + while (System.currentTimeMillis() < time) { + if (inputStreamHandler.getOutput().contains(message)) { + return true; + } + } + return false; } @AfterTest(alwaysRun = true) public void resetScriptEngineConfig() throws Exception { super.init(); - scm.restoreToLastConfiguration(false); - scm.restartGracefully(); + serverConfigurationManager.restoreToLastConfiguration(false); + javaVersion = (javaVersion == 0) ? Utils.getJavaVersion() : javaVersion; + if (javaVersion >= 15) { + runAdaptiveAuthenticationDependencyScript(true, serverConfigurationManager, log); + } + restartServer(); } - } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/RiskBasedLoginTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/RiskBasedLoginTestCase.java index 860a8421dd3..78793e9945f 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/RiskBasedLoginTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/auth/RiskBasedLoginTestCase.java @@ -94,13 +94,10 @@ public class RiskBasedLoginTestCase extends AbstractAdaptiveAuthenticationTestCa private ApplicationManagementServiceClient applicationManagementServiceClient; private WebAppAdminClient webAppAdminClient; private CookieStore cookieStore = new BasicCookieStore(); - private Lookup cookieSpecRegistry; - private RequestConfig requestConfig; private HttpClient client; private HttpResponse response; - private List consentParameters = new ArrayList<>(); private ServerConfigurationManager serverConfigurationManager; - private IdentityProvider superTenantResidentIDP; + private boolean openJDKNashornEnabled = false; private Map userRiskScores = new HashMap<>(); @@ -110,7 +107,7 @@ public class RiskBasedLoginTestCase extends AbstractAdaptiveAuthenticationTestCa @BeforeClass(alwaysRun = true) @Parameters({"scriptEngine"}) - public void testInit(@Optional("nashorn") String scriptEngine) throws Exception { + public void testInit(@Optional("graaljs") String scriptEngine) throws Exception { super.init(); @@ -145,7 +142,7 @@ public void testInit(@Optional("nashorn") String scriptEngine) throws Exception String authenticatorWebappPathString = Utils.getResidentCarbonHome() + File.separator + "repository" + File.separator + "deployment" + File.separator + "server" + File.separator + "webapps" + File.separator + "sample-auth"; - waitForWebappToDeploy(authenticatorWebappPathString, 120000L); + waitForWebappToDeploy(authenticatorWebappPathString); log.info("Copied the demo authenticator war file to " + authenticatorWarPathString); Assert.assertTrue(Files.exists(Paths.get(authenticatorWarPathString)), "Demo Authenticator war is not copied " + @@ -168,10 +165,10 @@ public void testInit(@Optional("nashorn") String scriptEngine) throws Exception configContext); webAppAdminClient = new WebAppAdminClient(backendURL, sessionCookie); - cookieSpecRegistry = RegistryBuilder.create() + Lookup cookieSpecRegistry = RegistryBuilder.create() .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider()) .build(); - requestConfig = RequestConfig.custom() + RequestConfig requestConfig = RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT) .build(); client = HttpClientBuilder.create() @@ -191,16 +188,16 @@ public void testInit(@Optional("nashorn") String scriptEngine) throws Exception microserviceServer = MicroserviceUtil.initMicroserviceServer(); MicroserviceUtil.deployService(microserviceServer, this); - superTenantResidentIDP = superTenantIDPMgtClient.getResidentIdP(); + IdentityProvider superTenantResidentIDP = superTenantIDPMgtClient.getResidentIdP(); updateResidentIDPProperty(superTenantResidentIDP, "adaptive_authentication.analytics.receiver", "http://localhost:" + microserviceServer.getPort()); userRiskScores.put(userInfo.getUserName(), 0); } - private void changeAdaptiveAuthenticationScript(String scriptFileName) throws Exception { + private void changeAdaptiveAuthenticationScript() throws Exception { - String script = getConditionalAuthScript(scriptFileName); + String script = getConditionalAuthScript("RiskBasedLoginScriptPayload.js"); serviceProvider.getLocalAndOutBoundAuthenticationConfig().getAuthenticationScriptConfig().setContent(script); applicationManagementServiceClient.updateApplicationData(serviceProvider); } @@ -209,7 +206,14 @@ private void changeISConfiguration(String scriptEngine) throws Exception { String identityNewResourceFileName = "identity_new_resource.toml"; if (scriptEngine.equalsIgnoreCase("nashorn")) { - identityNewResourceFileName = "identity_new_resource_nashorn.toml"; + if (Utils.getJavaVersion() >= 15) { + identityNewResourceFileName = "identity_new_resource_openjdknashorn.toml"; + NashornAdaptiveScriptInitializerTestCase.runAdaptiveAuthenticationDependencyScript(false, + serverConfigurationManager, log); + openJDKNashornEnabled = true; + } else { + identityNewResourceFileName = "identity_new_resource_nashorn.toml"; + } } String carbonHome = Utils.getResidentCarbonHome(); @@ -224,13 +228,17 @@ private void changeISConfiguration(String scriptEngine) throws Exception { private void resetISConfiguration() throws Exception { serverConfigurationManager.restoreToLastConfiguration(false); + if (openJDKNashornEnabled) { + NashornAdaptiveScriptInitializerTestCase.runAdaptiveAuthenticationDependencyScript(true, + serverConfigurationManager, log); + } } - private void waitForWebappToDeploy(String authenticatorWebappPathString, long timeout) { + private void waitForWebappToDeploy(String authenticatorWebappPathString) { long startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < timeout) { + while (System.currentTimeMillis() - startTime < 120000L) { if (Files.exists(Paths.get(authenticatorWebappPathString))) { log.info(authenticatorWebappPathString + " deployed successfully."); break; @@ -376,7 +384,7 @@ public void testAuthenticationForRisk() throws Exception { @Test(groups = "wso2.is", description = "Check conditional authentication flow.") public void testAuthenticationForRiskWithComplexPayload() throws Exception { - changeAdaptiveAuthenticationScript("RiskBasedLoginScriptPayload.js"); + changeAdaptiveAuthenticationScript(); cookieStore.clear(); response = loginWithOIDC(PRIMARY_IS_APPLICATION_NAME, consumerKey, client); diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/base/MockOIDCIdentityProvider.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/base/MockOIDCIdentityProvider.java new file mode 100644 index 00000000000..59533900bd1 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/base/MockOIDCIdentityProvider.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2024, 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.base; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.http.Response; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.wso2.identity.integration.test.util.Utils; + +import java.io.FileInputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.interfaces.RSAPrivateKey; +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.notContaining; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; + +/** + * Mock OIDC Identity Provider for testing OIDC flows. + */ +public class MockOIDCIdentityProvider { + + public static final String MOCK_IDP_AUTHORIZE_ENDPOINT = "https://localhost:8089/authorize"; + public static final String MOCK_IDP_TOKEN_ENDPOINT = "https://localhost:8089/token"; + public static final String MOCK_IDP_LOGOUT_ENDPOINT = "https://localhost:8089/oidc/logout"; + public static final String MOCK_IDP_CLIENT_ID = "mockIdPClientID"; + public static final String MOCK_IDP_CLIENT_SECRET = "mockIdPClientSecret"; + + private WireMockServer wireMockServer; + private final AtomicReference authorizationCode = new AtomicReference<>(); + + public void start() { + + wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig() + .httpsPort(8089) + .keystorePath(Paths.get(Utils.getResidentCarbonHome(), "repository", "resources", "security", + "wso2carbon.p12").toAbsolutePath().toString()) + .keystorePassword("wso2carbon") + .keyManagerPassword("wso2carbon") + .extensions( + new ResponseTemplateTransformer(null, true, null, null), + new ResponseTransformerV2() { + @Override + public Response transform(Response response, ServeEvent serveEvent) { + // Extract the code parameter from the redirect URL + String locationHeader = response.getHeaders().getHeader("Location").firstValue(); + String codeParam = locationHeader.split("code=")[1].split("&")[0]; + + // Store the authorization code + authorizationCode.set(codeParam); + return response; + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public String getName() { + return "authz-code-transformer"; + } + })); + + wireMockServer.start(); + + // Configure the mock OIDC endpoints + configureMockEndpoints(); + } + + public void stop() { + + if (wireMockServer != null) { + wireMockServer.stop(); + } + } + + private void configureMockEndpoints() { + + wireMockServer.stubFor(post(urlEqualTo("/token")) + .withRequestBody(notContaining("grant_type=") + .or(notContaining("code=")) + .or(notContaining("redirect_uri="))) + .willReturn(aResponse() + .withStatus(400) + .withHeader("Content-Type", "application/json") + .withBody("{ \"error\": \"invalid_request\", \"error_description\": " + + "\"Missing required parameter\" }"))); + + try { + wireMockServer.stubFor(post(urlEqualTo("/token")) + .withRequestBody(containing("grant_type=authorization_code")) + .withRequestBody(containing("code=")) + .withRequestBody(containing("redirect_uri=")) + .withRequestBody(containing("client_secret="+ MOCK_IDP_CLIENT_SECRET)) + .withRequestBody(containing("client_id=" + MOCK_IDP_CLIENT_ID)) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody("{\"access_token\": \"mock_access_token\", \"token_type\": \"Bearer\", " + + "\"expires_in\": 3600, \"id_token\": \"" + buildIdToken() + "\" }"))); + } catch (Exception e) { + throw new RuntimeException(e); + } + + wireMockServer.stubFor(get(urlPathEqualTo("/authorize")) + .withQueryParam("response_type", matching(".*")) + .withQueryParam("redirect_uri", matching(".*")) + .withQueryParam("state", matching(".*")) + .withQueryParam("nonce", matching(".*")) + .withQueryParam("client_id", matching(MOCK_IDP_CLIENT_ID)) + .withQueryParam("scope", matching(".*")) + .willReturn(aResponse() + .withTransformers("response-template", "authz-code-transformer") + .withStatus(302) + .withHeader("Location", + "{{request.query.redirect_uri}}?session_state=mockid&code=" + + java.util.UUID.randomUUID() + "&state={{request.query.state}}"))); + + wireMockServer.stubFor(get(urlPathEqualTo("/oidc/logout")) + .withQueryParam("state", matching(".*")) + .withQueryParam("post_logout_redirect_uri", matching(".*")) + .withQueryParam("id_token_hint", matching(".*")) + .willReturn(aResponse() + .withTransformers("response-template") + .withStatus(302) + .withHeader("Location", + "{{request.query.post_logout_redirect_uri}}?state={{request.query.state}}"))); + } + + public void verifyForAuthzCodeFlow() { + + wireMockServer.verify(postRequestedFor(urlPathEqualTo("/token")) + .withRequestBody(containing("grant_type=authorization_code")) + .withRequestBody(containing("code=" + authorizationCode.get()))); + wireMockServer.verify(getRequestedFor(urlPathEqualTo("/authorize"))); + } + + public void verifyForLogoutFlow() { + + wireMockServer.verify(getRequestedFor(urlPathEqualTo("/oidc/logout"))); + } + + private String buildIdToken() throws Exception { + + KeyStore wso2KeyStore = getKeyStoreFromFile("wso2carbon.p12", "wso2carbon", + Utils.getResidentCarbonHome()); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) wso2KeyStore.getKey("wso2carbon", "wso2carbon".toCharArray()); + + JWSSigner signer = new RSASSASigner(rsaPrivateKey); + + // Prepare JWT with claims set + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .issuer("https://localhost:8089/token") + .subject("61b935a1-1915-4792-8916-99c59d03c54a") + .audience("LzWfxDK_7LSGxfuL3BlRdXUGEJYa") + .claim("azp", "LzWfxDK_7LSGxfuL3BlRdXUGEJYa") + .claim("org_id", "10084a8d-113f-4211-a0d5-efe36b082211") + .claim("org_name", "Super") + .claim("amr", new String[]{"BasicAuthenticator"}) + .claim("c_hash", "3eh6RwdVWxGQEljI7l9K3g") + .claim("at_hash", "zZ5nLASTkVRWrcCelPOHw") + .claim("sid", "05759c14-d0bc-414a-931c-b7ffba55b2c3") + .claim("jti", "37803fb8-f1f1-4eac-8ed2-5067349664fc") + .claim("isk", "9ab97ab343161334c9432d117e8da73211949aacce8c5d1c0ba8d6c75e0782c4") + .issueTime(new Date()) + .notBeforeTime(new Date()) + .build(); + + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(getKeyId(rsaPrivateKey)).build(), claimsSet); + signedJWT.sign(signer); + return signedJWT.serialize(); + } + + private KeyStore getKeyStoreFromFile(String keystoreName, String password, String home) throws Exception { + + Path tenantKeystorePath = Paths.get(home, "repository", "resources", "security", keystoreName); + FileInputStream file = new FileInputStream(tenantKeystorePath.toString()); + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(file, password.toCharArray()); + return keystore; + } + + private String getKeyId(RSAPrivateKey privateKey) throws Exception { + + java.security.MessageDigest sha256 = java.security.MessageDigest.getInstance("SHA-256"); + byte[] keyBytes = privateKey.getEncoded(); + byte[] hash = sha256.digest(keyBytes); + return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(hash); + } +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/idp/mgt/IdentityProviderMgtServiceTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/idp/mgt/IdentityProviderMgtServiceTestCase.java index fd106d5b634..0176d8e8551 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/idp/mgt/IdentityProviderMgtServiceTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/idp/mgt/IdentityProviderMgtServiceTestCase.java @@ -47,7 +47,7 @@ public class IdentityProviderMgtServiceTestCase extends ISIntegrationTest { private String testIdpName = "TestIDPProvider"; private String testIdpNameSearch = "SearchTestIDPProviderTest"; private String updatedTestIdpName = "UpdatedTestIDPProvider"; - private String testFedAuthName = "OpenIDAuthenticator"; + private String testFedAuthName = "OpenIDConnectAuthenticator"; //Resident idp default values private boolean residentIdpEnable; @@ -159,7 +159,7 @@ public void testGetResidentIdP() throws Exception { public void testAddIdp() throws Exception { String testIdpDescription = "This is test identity provider"; String testIdpRealmId = "localhost"; - String testFedAuthDispName = "openid"; + String testFedAuthDispName = "openidConnect"; String testFedAuthPropName = "OpenIdUrl"; String testFedAuthPropValue = "https://testDomain:9853/openid"; diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2PushedAuthRequestTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2PushedAuthRequestTestCase.java index 75e6306fcef..5317405e78b 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2PushedAuthRequestTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2PushedAuthRequestTestCase.java @@ -17,6 +17,7 @@ */ package org.wso2.identity.integration.test.oauth2; +import org.apache.commons.lang.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.config.CookieSpecs; @@ -44,6 +45,7 @@ import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.OpenIDConnectConfiguration; import org.wso2.identity.integration.test.utils.OAuth2Constant; +import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -121,9 +123,11 @@ public void testSendPar() throws Exception { urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); - String response = responsePost(OAuth2Constant.PAR_ENDPOINT, urlParameters); + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); JSONParser parser = new JSONParser(); - JSONObject jsonResponse = (JSONObject) parser.parse(response); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); if (jsonResponse == null) { throw new Exception("Error occurred while getting the response."); } @@ -139,8 +143,10 @@ public void testSendAuthorize() throws Exception { List urlParameters = new ArrayList<>(); urlParameters.add(new BasicNameValuePair(REQUEST_URI, requestUri)); urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); - String response = responsePost(OAuth2Constant.AUTHORIZE_ENDPOINT_URL, urlParameters); - Assert.assertNotNull(response, "Authorized response is null"); + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + Assert.assertNotNull(responseString, "Authorized response is null"); } @Test(groups = "wso2.is", description = "Send PAR with openid request object", dependsOnMethods = @@ -154,9 +160,11 @@ public void testSendParWithRequestObject() throws Exception { OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH_OIDC_REQUEST, REQUEST)); urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_SCOPE, OAuth2Constant.OAUTH2_SCOPE_OPENID)); - String response = responsePost(OAuth2Constant.PAR_ENDPOINT, urlParameters); + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); JSONParser parser = new JSONParser(); - JSONObject jsonResponse = (JSONObject) parser.parse(response); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); if (jsonResponse == null) { throw new Exception("Error occurred while getting the response."); } @@ -166,16 +174,148 @@ public void testSendParWithRequestObject() throws Exception { Assert.assertNotNull(expiryTime, "expiry_time is null"); } - private String responsePost(String endpoint, List postParameters) - throws Exception { + @Test(groups = "wso2.is", description = "Send authorize user request with invalid client id", + dependsOnMethods = "testSendPar") + public void testSendAuthorizeWithInvalidClient() throws Exception { - HttpPost httpPost = new HttpPost(endpoint); - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); - httpPost.setEntity(new UrlEncodedFormEntity(postParameters)); - HttpResponse response = client.execute(httpPost); - String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); - EntityUtils.consume(response.getEntity()); - return responseString; + testSendPar(); + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(REQUEST_URI, requestUri)); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, "invalid_client_id")); + + HttpResponse response = sendPostRequest(OAuth2Constant.AUTHORIZE_ENDPOINT_URL, urlParameters); + String locationHeader = response.getFirstHeader("Location") != null ? + response.getFirstHeader("Location").getValue() : null; + + Assert.assertNotNull(locationHeader, "Location header from the authz response is null"); + Assert.assertTrue(StringUtils.contains(locationHeader, "oauthErrorMsg=par.client.id.not.match")); + } + + @Test(groups = "wso2.is", description = "Send authorize user request with invalid request uri", + dependsOnMethods = "testSendAuthorizeWithInvalidClient") + public void testSendAuthorizeWithInvalidRequestURI() throws Exception { + + testSendPar(); + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(REQUEST_URI, + "urn:ietf:params:oauth:par:request_uri:invalid_request_uri")); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + + HttpResponse response = sendPostRequest(OAuth2Constant.AUTHORIZE_ENDPOINT_URL, urlParameters); + String locationHeader = response.getFirstHeader("Location") != null ? + response.getFirstHeader("Location").getValue() : null; + + Assert.assertNotNull(locationHeader, "Location header from the authz response is null"); + Assert.assertTrue(StringUtils.contains(locationHeader, "oauthErrorMsg=par.invalid.request.uri")); + } + + @Test(groups = "wso2.is", description = "Send authorize user request with expired request uri", + dependsOnMethods = "testSendAuthorizeWithInvalidRequestURI") + public void testSendAuthorizeWithExpiredRequestURI() throws Exception { + + testSendPar(); + // Sleep for 1 min for request uri timeout + Thread.sleep(60 * 1000); + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(REQUEST_URI, requestUri)); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + + HttpResponse response = sendPostRequest(OAuth2Constant.AUTHORIZE_ENDPOINT_URL, urlParameters); + String locationHeader = response.getFirstHeader("Location") != null ? + response.getFirstHeader("Location").getValue() : null; + + Assert.assertNotNull(locationHeader, "Location header from the authz response is null"); + Assert.assertTrue(StringUtils.contains(locationHeader, "oauthErrorMsg=par.request.uri.expired")); + } + + @Test(groups = "wso2.is", description = "Send PAR with repeated param", + dependsOnMethods = "testSendAuthorizeWithExpiredRequestURI") + public void testSendParWithRepeatedParam() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, "repeated_redirect_uri")); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, + OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.BAD_REQUEST.getStatusCode(), + "Invalid request with repeated parameters.", "invalid_request"); + } + + @Test(groups = "wso2.is", description = "Send PAR with invalid client id", + dependsOnMethods = "testSendParWithRepeatedParam") + public void testSendParWithInvalidClient() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, "invalid_consumerKey")); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.UNAUTHORIZED.getStatusCode(), + "A valid OAuth client could not be found for client_id: invalid_consumerKey", + "invalid_client"); + } + + @Test(groups = "wso2.is", description = "Send PAR without client id", + dependsOnMethods = "testSendParWithInvalidClient") + public void testSendParWithoutClient() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.UNAUTHORIZED.getStatusCode(), + "Client ID not found in the request.", "invalid_client"); + } + + @Test(groups = "wso2.is", description = "Send PAR with invalid response type", + dependsOnMethods = "testSendParWithoutClient") + public void testSendParWithInvalidResponseType() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, "invalid_responseType")); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.BAD_REQUEST.getStatusCode(), + "Invalid response_type parameter value", "invalid_request"); + } + + @Test(groups = "wso2.is", description = "Send PAR with invalid redirect uri", + dependsOnMethods = "testSendParWithInvalidResponseType") + public void testSendParWithInvalidRedirectURI() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, "invalid_redirect_URI")); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.BAD_REQUEST.getStatusCode(), + "callback.not.match", "invalid_request"); + } + + @Test(groups = "wso2.is", description = "Send PAR with request uri", + dependsOnMethods = "testSendParWithInvalidRedirectURI") + public void testSendParWithRequestURI() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair(CLIENT_ID_PARAM, consumerKey)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_REDIRECT_URI, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(OAuth2Constant.OAUTH2_RESPONSE_TYPE, + OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + urlParameters.add(new BasicNameValuePair(REQUEST_URI, + "urn:ietf:params:oauth:par:request_uri:75fb6713-62fa-4d2f-9f72-0e05eab0d331")); + + HttpResponse response = sendPostRequest(OAuth2Constant.PAR_ENDPOINT, urlParameters); + assertResponse(response, Response.Status.BAD_REQUEST.getStatusCode(), + "Request with request_uri not allowed.", "invalid_request"); } /** @@ -211,4 +351,32 @@ private ApplicationResponseModel createApp() throws Exception { return getApplication(appId); } + + private HttpResponse sendPostRequest(String endpoint, List parameters) throws Exception { + + HttpPost httpPost = new HttpPost(endpoint); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); + httpPost.setEntity(new UrlEncodedFormEntity(parameters)); + return client.execute(httpPost); + } + + private void assertResponse(HttpResponse response, int expectedStatusCode, + String expectedErrorDescription, String expectedError) throws Exception { + + int responseCode = response.getStatusLine().getStatusCode(); + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + + JSONParser parser = new JSONParser(); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); + if (jsonResponse == null) { + throw new Exception("Error occurred while getting the response."); + } + + Assert.assertEquals(responseCode, expectedStatusCode, "Response status code does not match."); + Assert.assertEquals(jsonResponse.get("error_description").toString(), expectedErrorDescription, + "Error description is missing or invalid value"); + Assert.assertEquals(jsonResponse.get("error").toString(), expectedError, + "Error is missing or invalid value"); + } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OIDCDiscoveryTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OIDCDiscoveryTestCase.java index b41f4e769d4..84a178f8beb 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OIDCDiscoveryTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OIDCDiscoveryTestCase.java @@ -18,7 +18,10 @@ package org.wso2.identity.integration.test.oauth2; -import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.wink.client.ClientConfig; import org.apache.wink.client.Resource; import org.apache.wink.client.RestClient; @@ -30,39 +33,37 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; -import org.testng.annotations.Factory; import org.testng.annotations.Test; import org.wso2.charon.core.schema.SCIMConstants; import org.wso2.identity.integration.common.utils.ISIntegrationTest; import java.io.IOException; +import static org.testng.Assert.assertEquals; + public class OIDCDiscoveryTestCase extends ISIntegrationTest { public static final String WEBFINGER_ENDPOINT_SUFFIX = "/.well-known/webfinger"; public static final String RESOURCE = "resource"; public static final String REL = "rel"; private String isServerBackendUrl; - private String webfingerEndpoint; - private String relUri = "http://openid.net/specs/connect/1.0/issuer"; - private String discoveryBasePath; - private DiscoveryConfig config; - - @Factory(dataProvider = "webfingerConfigProvider") - public OIDCDiscoveryTestCase(DiscoveryConfig config) { - if (log.isDebugEnabled()){ - log.info("SAML SSO Test initialized for " + config); - } - this.config = config; - } + private static final String[] expectedResponseModes = {"fragment", "jwt", "fragment.jwt", "query", "form_post", + "query.jwt", "form_post.jwt"}; + private static final String[] expectedAuthModes = {"private_key_jwt", "client_secret_post", "tls_client_auth", + "client_secret_basic"}; + private static final String[] expectedResponseTypes = {"id_token token", "code", "code id_token token", + "code id_token", "id_token", "code token", "none", "device", "subject_token", "id_token subject_token", + "token"}; + private static final String[] expectedGrantTypes = {"refresh_token", "password", "client_credentials", "iwa:ntlm", + "urn:ietf:params:oauth:grant-type:saml2-bearer", "urn:ietf:params:oauth:grant-type:device_code", + "authorization_code", "account_switch", "urn:ietf:params:oauth:grant-type:token-exchange", + "organization_switch", "urn:ietf:params:oauth:grant-type:jwt-bearer"}; @BeforeClass(alwaysRun = true) public void testInit() throws Exception { super.init(); isServerBackendUrl = isServer.getContextUrls().getWebAppURLHttps(); - webfingerEndpoint = isServerBackendUrl + WEBFINGER_ENDPOINT_SUFFIX + "?" + RESOURCE + "=" + config - .getResource() + "&" + REL + "=" + relUri; } @AfterClass(alwaysRun = true) @@ -70,9 +71,13 @@ public void atEnd() { } - @Test(alwaysRun = true, groups = "wso2.is", description = "webfinger test") - public void testWebFinger() throws IOException { + @Test(alwaysRun = true, groups = "wso2.is", description = "webfinger test", + dataProvider = "webFingerConfigProvider") + public void testWebFinger(DiscoveryConfig config) { + String relUri = "http://openid.net/specs/connect/1.0/issuer"; + String webFingerEndpoint = isServerBackendUrl + WEBFINGER_ENDPOINT_SUFFIX + "?" + RESOURCE + "=" + config + .getResource() + "&" + REL + "=" + relUri; ClientConfig clientConfig = new ClientConfig(); BasicAuthSecurityHandler basicAuth = new BasicAuthSecurityHandler(); basicAuth.setUserName(userInfo.getUserName()); @@ -80,7 +85,7 @@ public void testWebFinger() throws IOException { clientConfig.handlers(basicAuth); RestClient restClient = new RestClient(clientConfig); - Resource userResource = restClient.resource(webfingerEndpoint); + Resource userResource = restClient.resource(webFingerEndpoint); String response = userResource.accept(SCIMConstants.APPLICATION_JSON).get(String.class); Object obj= JSONValue.parse(response); Object links = ((JSONObject)obj).get("links"); @@ -90,8 +95,9 @@ public void testWebFinger() throws IOException { Assert.assertEquals(openIdProviderIssuerLocation, urlExpected); } - @Test(alwaysRun = true, groups = "wso2.is", description = "Discovery test", dependsOnMethods = { "testWebFinger" }) - public void testDiscovery() throws IOException { + @Test(alwaysRun = true, groups = "wso2.is", description = "Discovery test", dependsOnMethods = { "testWebFinger" }, + dataProvider = "oidcDiscoveryConfigProvider") + public void testDiscovery(String tenantDomain, String issuer) { ClientConfig clientConfig = new ClientConfig(); BasicAuthSecurityHandler basicAuth = new BasicAuthSecurityHandler(); @@ -100,21 +106,47 @@ public void testDiscovery() throws IOException { clientConfig.handlers(basicAuth); RestClient restClient = new RestClient(clientConfig); - String discoveryUrl = isServerBackendUrl + "/oauth2/oidcdiscovery/.well-known/openid-configuration"; + String discoveryUrl = getTenantQualifiedURL(isServerBackendUrl + + "/oauth2/" + issuer + "/.well-known/openid-configuration", tenantDomain); + Resource userResource = restClient.resource(discoveryUrl); String response = userResource.accept(SCIMConstants.APPLICATION_JSON).get(String.class); - Object obj= JSONValue.parse(response); - String authorization_endpoint = ((JSONObject)obj).get("authorization_endpoint").toString(); - Assert.assertEquals(authorization_endpoint, isServerBackendUrl + "/oauth2/authorize"); - String token_endpoint = ((JSONObject)obj).get("token_endpoint").toString(); - Assert.assertEquals(token_endpoint, isServerBackendUrl + "/oauth2/token"); - String userinfo_endpoint = ((JSONObject)obj).get("userinfo_endpoint").toString(); - Assert.assertEquals(userinfo_endpoint, isServerBackendUrl + "/oauth2/userinfo"); + JSONObject jsonResponse = (JSONObject) JSONValue.parse(response); + + // Extract and validate the endpoints + validateEndpoint(jsonResponse, "authorization_endpoint", "/oauth2/authorize", tenantDomain); + validateEndpoint(jsonResponse, "token_endpoint", "/oauth2/token", tenantDomain); + validateEndpoint(jsonResponse, "userinfo_endpoint", "/oauth2/userinfo", tenantDomain); + validateEndpoint(jsonResponse, "pushed_authorization_request_endpoint", "/oauth2/par", tenantDomain); + validateEndpoint(jsonResponse, "introspection_endpoint", "/oauth2/introspect", tenantDomain); + validateEndpoint(jsonResponse, "device_authorization_endpoint", "/oauth2/device_authorize", tenantDomain); + validateEndpoint(jsonResponse, "end_session_endpoint", "/oidc/logout", tenantDomain); + validateEndpoint(jsonResponse, "revocation_endpoint", "/oauth2/revoke", tenantDomain); + validateEndpoint(jsonResponse, "jwks_uri", "/oauth2/jwks", tenantDomain); + validateEndpoint(jsonResponse, "registration_endpoint", + "/api/identity/oauth2/dcr/v1.1/register", tenantDomain); + validateArrayElements(jsonResponse, "response_modes_supported", expectedResponseModes); + validateArrayElements(jsonResponse, "token_endpoint_auth_methods_supported", expectedAuthModes); + validateArrayElements(jsonResponse, "response_types_supported", expectedResponseTypes); + validateArrayElements(jsonResponse, "grant_types_supported", expectedGrantTypes); + } + + @DataProvider(name = "oidcDiscoveryConfigProvider") + public static Object[][] configProvider() { + + return new Object[][]{ + {"", "oidcdiscovery"}, + {"carbon.super", "oidcdiscovery"}, + {"wso2.com", "oidcdiscovery"}, + {"", "token"}, + {"carbon.super", "token"}, + {"wso2.com", "token"}, + }; } + @DataProvider(name = "webFingerConfigProvider") + public static Object[][] webFingerConfigProvider(){ - @DataProvider(name = "webfingerConfigProvider") - public static Object[][] webfingerConfigProvider(){ return new DiscoveryConfig[][]{ {new DiscoveryConfig("acct:admin@localhost", "")}, {new DiscoveryConfig("acct:admin%40wso2.com@localhost", "wso2.com")}, @@ -149,4 +181,63 @@ public void setTenant(String tenant) { } } + /** + * Validates the specific endpoint from the OIDC discovery response. + * + * @param jsonResponse The parsed JSON object from the response. + * @param endpointKey The key of the endpoint in the JSON response. + * @param expectedEndpoint The expected suffix for the endpoint URL. + * @param tenantDomain Tenant domain intend for testing. + */ + private void validateEndpoint(JSONObject jsonResponse, String endpointKey, String expectedEndpoint, + String tenantDomain) { + + String endpointUrl = jsonResponse.get(endpointKey).toString(); + String expectedUrl = getTenantQualifiedURL(isServerBackendUrl + expectedEndpoint, tenantDomain); + Assert.assertEquals(endpointUrl, expectedUrl, + String.format("Expected %s to be %s, but found %s", endpointKey, expectedUrl, endpointUrl)); + } + + private void validateArrayElements(JSONObject jsonResponse, String key, String[] expectedElements) { + + JSONArray elementsArray = (JSONArray) jsonResponse.get(key); + String[] actualElements = new String[elementsArray.size()]; + for (int i = 0; i < elementsArray.size(); i++) { + actualElements[i] = (String) elementsArray.get(i); + } + + Assert.assertTrue(containsAll(actualElements, expectedElements), + String.format("Expected elements to include %s, but found %s", + String.join(", ", expectedElements), + String.join(", ", actualElements))); + } + + private boolean containsAll(String[] actualElements, String[] expectedElements) { + + for (String expectedElement : expectedElements) { + boolean found = false; + for (String actualElement : actualElements) { + if (actualElement.equals(expectedElement)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + + @Test(alwaysRun = true, groups = "wso2.is", description = "Discovery test", dependsOnMethods = { "testDiscovery" }) + public void testDiscoveryForInvalidIssuer() throws IOException { + + try (CloseableHttpClient client = HttpClients.createDefault()) { + String discoveryUrl = isServerBackendUrl + "/oauth2/invalidIssuer/.well-known/openid-configuration"; + HttpGet request = new HttpGet(discoveryUrl); + HttpResponse response = client.execute(request); + assertEquals(response.getStatusLine().getStatusCode(), 400, "Expected a Bad Request " + + "(HTTP 400) response"); + } + } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ServiceAuthCodeGrantOpenIdTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OpenIdUserInfoTestCase.java similarity index 70% rename from modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ServiceAuthCodeGrantOpenIdTestCase.java rename to modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OpenIdUserInfoTestCase.java index 8a0b4ebbde2..b55747360b8 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OAuth2ServiceAuthCodeGrantOpenIdTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oauth2/OpenIdUserInfoTestCase.java @@ -25,7 +25,9 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.config.Lookup; import org.apache.http.config.RegistryBuilder; import org.apache.http.cookie.CookieSpecProvider; @@ -33,10 +35,12 @@ 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.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.json.simple.JSONObject; import org.json.simple.JSONValue; +import org.json.simple.parser.JSONParser; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -48,9 +52,6 @@ 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.OpenIDConnectConfiguration; import org.wso2.identity.integration.test.rest.api.user.common.model.Email; -import org.wso2.identity.integration.test.rest.api.user.common.model.ListObject; -import org.wso2.identity.integration.test.rest.api.user.common.model.PatchOperationRequestObject; -import org.wso2.identity.integration.test.rest.api.user.common.model.RoleItemAddGroupobj; import org.wso2.identity.integration.test.rest.api.user.common.model.UserObject; import org.wso2.identity.integration.test.restclients.SCIM2RestClient; import org.wso2.identity.integration.test.utils.DataExtractUtil; @@ -60,33 +61,30 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.testng.Assert.assertNotNull; import static org.wso2.identity.integration.test.utils.DataExtractUtil.KeyValue; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.ACCESS_TOKEN_ENDPOINT; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.AUTHORIZATION_HEADER; import static org.wso2.identity.integration.test.utils.OAuth2Constant.COMMON_AUTH_URL; -public class OAuth2ServiceAuthCodeGrantOpenIdTestCase extends OAuth2ServiceAbstractIntegrationTest { +public class OpenIdUserInfoTestCase extends OAuth2ServiceAbstractIntegrationTest { private String accessToken; private String sessionDataKeyConsent; private String sessionDataKey; private String authorizationCode; AutomationContext context; - private String consumerKey; private String consumerSecret; - - private Lookup cookieSpecRegistry; - private RequestConfig requestConfig; private CloseableHttpClient client; - - private static final String USERS_PATH = "users"; private static final String USER_EMAIL = "abc@wso2.com"; private static final String USERNAME = "authcodegrantuser"; private static final String PASSWORD = "Pass@123"; - private final List consentParameters = new ArrayList<>(); private final CookieStore cookieStore = new BasicCookieStore(); private final String username; @@ -102,7 +100,7 @@ public static Object[][] configProvider() { } @Factory(dataProvider = "configProvider") - public OAuth2ServiceAuthCodeGrantOpenIdTestCase(TestUserMode userMode) throws Exception { + public OpenIdUserInfoTestCase(TestUserMode userMode) throws Exception { super.init(userMode); context = new AutomationContext("IDENTITY", userMode); @@ -116,10 +114,10 @@ public void testInit() throws Exception { tenantInfo = context.getContextTenant(); scim2RestClient = new SCIM2RestClient(serverURL, tenantInfo); - cookieSpecRegistry = RegistryBuilder.create() + Lookup cookieSpecRegistry = RegistryBuilder.create() .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider()) .build(); - requestConfig = RequestConfig.custom() + RequestConfig requestConfig = RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT) .build(); client = HttpClientBuilder.create() @@ -336,6 +334,120 @@ public void testValidateTokenScope() throws Exception { Assert.assertTrue(scopes.contains("openid"), "Invalid JWT Token scope Value"); } + @Test(groups = "wso2.is", description = "request user info using POST", dependsOnMethods = "testValidateTokenScope") + public void testUserInfoPostRequest() throws Exception { + + String userInfoUrl = tenantInfo.getDomain().equalsIgnoreCase("carbon.super") ? + OAuth2Constant.USER_INFO_ENDPOINT : OAuth2Constant.TENANT_USER_INFO_ENDPOINT; + HttpPost request = new HttpPost(userInfoUrl); + + List urlParameters = Collections.singletonList( + new BasicNameValuePair("access_token", accessToken) + ); + request.setHeader("User-Agent", OAuth2Constant.USER_AGENT); + request.setHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setEntity(new UrlEncodedFormEntity(urlParameters)); + + HttpResponse response = client.execute(request); + + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); + if (jsonResponse == null) { + throw new Exception("Error occurred while getting the response."); + } + Assert.assertNotNull(jsonResponse.get("sub"), "sub from introspection endpoint response is null."); + Assert.assertNotNull(jsonResponse.get("email"), "sub from introspection endpoint response is null."); + } + + @Test(groups = "wso2.is", description = "request user info using POST with invalid token", + dependsOnMethods = "testUserInfoPostRequest") + public void testUserInfoPostWithInvalidToken() throws Exception { + + String userInfoUrl = tenantInfo.getDomain().equalsIgnoreCase("carbon.super") ? + OAuth2Constant.USER_INFO_ENDPOINT : OAuth2Constant.TENANT_USER_INFO_ENDPOINT; + HttpPost request = new HttpPost(userInfoUrl); + + List urlParameters = Collections.singletonList( + new BasicNameValuePair("access_token", "invalid_access_token") + ); + request.setHeader("User-Agent", OAuth2Constant.USER_AGENT); + request.setHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setEntity(new UrlEncodedFormEntity(urlParameters)); + + HttpResponse response = client.execute(request); + + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); + if (jsonResponse == null) { + throw new Exception("Error occurred while getting the response."); + } + Assert.assertEquals(jsonResponse.get("error_description"), + "Access token validation failed", "Unexpected error description"); + Assert.assertEquals(jsonResponse.get("error"), "invalid_token", + "Unexpected error message"); + } + + @Test(groups = "wso2.is", description = "Send user info request using m2m token", + dependsOnMethods = "testUserInfoPostWithInvalidToken") + public void testSendAuthorizedPostWithM2MToken() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair("grant_type", + OAuth2Constant.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS)); + urlParameters.add(new BasicNameValuePair("scope", OAuth2Constant.OAUTH2_SCOPE_OPENID+ " " + + OAuth2Constant.OAUTH2_SCOPE_EMAIL)); + + List
headers = new ArrayList<>(); + headers.add(new BasicHeader(AUTHORIZATION_HEADER, OAuth2Constant.BASIC_HEADER + " " + + getBase64EncodedString(consumerKey, consumerSecret))); + headers.add(new BasicHeader("Content-Type", "application/x-www-form-urlencoded")); + headers.add(new BasicHeader("User-Agent", OAuth2Constant.USER_AGENT)); + + HttpResponse response = sendPostRequest(client, headers, urlParameters, + getTenantQualifiedURL(ACCESS_TOKEN_ENDPOINT, tenantInfo.getDomain())); + + Assert.assertNotNull(response, "Authorization request failed. Authorized response is null"); + + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + JSONParser parser = new JSONParser(); + JSONObject jsonResponse = (JSONObject) parser.parse(responseString); + if (jsonResponse == null) { + throw new Exception("Error occurred while getting the m2m token response."); + } + String m2mAccessToken = (String) jsonResponse.get("access_token"); + assertNotNull(m2mAccessToken, "M2M Access token is null."); + + String userInfoUrl = tenantInfo.getDomain().equalsIgnoreCase("carbon.super") ? + OAuth2Constant.USER_INFO_ENDPOINT : OAuth2Constant.TENANT_USER_INFO_ENDPOINT; + HttpPost request = new HttpPost(userInfoUrl); + + urlParameters = Collections.singletonList( + new BasicNameValuePair("access_token", m2mAccessToken) + ); + request.setHeader("User-Agent", OAuth2Constant.USER_AGENT); + request.setHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setEntity(new UrlEncodedFormEntity(urlParameters)); + + response = client.execute(request); + + responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + EntityUtils.consume(response.getEntity()); + jsonResponse = (JSONObject) parser.parse(responseString); + if (jsonResponse == null) { + throw new Exception("Error occurred while getting the response."); + } + Assert.assertEquals(jsonResponse.get("error_description"), + "Access token does not have the openid scope", "Unexpected error description"); + Assert.assertEquals(jsonResponse.get("error"), "insufficient_scope", + "Unexpected error message"); + } + + public HttpResponse sendLoginPost(HttpClient client, String sessionDataKey) throws IOException { List urlParameters = new ArrayList<>(); @@ -361,15 +473,6 @@ private void addAdminUser() throws Exception { userInfo.setUserName(USERNAME); userInfo.setPassword(PASSWORD); userInfo.addEmail(new Email().value(USER_EMAIL)); - userId = scim2RestClient.createUser(userInfo); - String roleId = scim2RestClient.getRoleIdByName("admin"); - - RoleItemAddGroupobj patchRoleItem = new RoleItemAddGroupobj(); - patchRoleItem.setOp(RoleItemAddGroupobj.OpEnum.ADD); - patchRoleItem.setPath(USERS_PATH); - patchRoleItem.addValue(new ListObject().value(userId)); - - scim2RestClient.updateUserRole(new PatchOperationRequestObject().addOperations(patchRoleItem), roleId); } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCIdentityFederationTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCIdentityFederationTestCase.java index 9a9ead40a1e..f2663b12a42 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCIdentityFederationTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/oidc/OIDCIdentityFederationTestCase.java @@ -18,7 +18,6 @@ package org.wso2.identity.integration.test.oidc; -import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; @@ -37,7 +36,6 @@ 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.opensaml.xml.util.Base64; import org.testng.Assert; import org.testng.annotations.AfterClass; @@ -48,13 +46,13 @@ import org.wso2.carbon.automation.engine.context.AutomationContext; import org.wso2.carbon.automation.engine.context.TestUserMode; import org.wso2.identity.integration.test.application.mgt.AbstractIdentityFederationTestCase; +import org.wso2.identity.integration.test.base.MockOIDCIdentityProvider; 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.AuthenticationSequence; import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.AuthenticationSequence.TypeEnum; import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.Authenticator; 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.rest.api.server.application.management.v1.model.SAML2Configuration; import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.SAML2ServiceProvider; import org.wso2.identity.integration.test.rest.api.server.application.management.v1.model.SAMLAssertionConfiguration; @@ -67,13 +65,6 @@ import org.wso2.identity.integration.test.rest.api.server.idp.v1.model.IdentityProviderPOSTRequest; import org.wso2.identity.integration.test.rest.api.server.idp.v1.model.ProvisioningRequest; import org.wso2.identity.integration.test.rest.api.server.idp.v1.model.ProvisioningRequest.JustInTimeProvisioning; -import org.wso2.identity.integration.test.rest.api.user.common.model.ListObject; -import org.wso2.identity.integration.test.rest.api.user.common.model.PatchOperationRequestObject; -import org.wso2.identity.integration.test.rest.api.user.common.model.RoleItemAddGroupobj; -import org.wso2.identity.integration.test.rest.api.user.common.model.UserObject; -import org.wso2.identity.integration.test.restclients.SCIM2RestClient; -import org.wso2.identity.integration.test.util.Utils; -import org.wso2.identity.integration.test.utils.DataExtractUtil; import org.wso2.identity.integration.test.utils.IdentityConstants; import org.wso2.identity.integration.test.utils.OAuth2Constant; @@ -81,10 +72,13 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; + +import static org.wso2.identity.integration.test.base.MockOIDCIdentityProvider.MOCK_IDP_AUTHORIZE_ENDPOINT; +import static org.wso2.identity.integration.test.base.MockOIDCIdentityProvider.MOCK_IDP_CLIENT_ID; +import static org.wso2.identity.integration.test.base.MockOIDCIdentityProvider.MOCK_IDP_CLIENT_SECRET; +import static org.wso2.identity.integration.test.base.MockOIDCIdentityProvider.MOCK_IDP_LOGOUT_ENDPOINT; +import static org.wso2.identity.integration.test.base.MockOIDCIdentityProvider.MOCK_IDP_TOKEN_ENDPOINT; /** * Integration test cases for SAML-OIDC federation scenarios. @@ -108,34 +102,15 @@ public class OIDCIdentityFederationTestCase extends AbstractIdentityFederationTe private static final String ENCODED_PRIMARY_IS_IDP_AUTHENTICATOR_ID_OIDC = "T3BlbklEQ29ubmVjdEF1dGhlbnRpY2F0b3I"; private static final String PRIMARY_IS_IDP_CALLBACK_URL = "https://localhost:9853/commonauth"; - private static final String SECONDARY_IS_TEST_USERNAME = "testFederatedUser"; - private static final String SECONDARY_IS_TEST_PASSWORD = "TestFederatePassword@123"; - private static final String SECONDARY_IS_TEST_USER_ROLES = "admin"; - - private static final String SECONDARY_IS_SP_NAME = "secondarySP"; - private static final String SECONDARY_IS_IDP_CALLBACK_URL = "https://localhost:9854/commonauth"; - private static final String SECONDARY_IS_TOKEN_ENDPOINT = "https://localhost:9854/oauth2/token"; - private static final String SECONDARY_IS_LOGOUT_ENDPOINT = "https://localhost:9854/oidc/logout"; - private static final String SECONDARY_IS_AUTHORIZE_ENDPOINT = "https://localhost:9854/oauth2/authorize"; - private static final String HTTPS_LOCALHOST_SERVICES = "https://localhost:%s/"; - private String secondaryISClientID; - private String secondaryISClientSecret; - private final String username; - private final String userPassword; private final AutomationContext context; private static final int PORT_OFFSET_0 = 0; - private static final int PORT_OFFSET_1 = 1; CookieStore cookieStore; - private Lookup cookieSpecRegistry; - private RequestConfig requestConfig; private CloseableHttpClient client; - private String secondaryISAppId; private String primaryISIdpId; private String primaryISAppId; - private SCIM2RestClient scim2RestClient; - private String secondaryISUserId; + private MockOIDCIdentityProvider mockIdP; @DataProvider(name = "configProvider") public static Object[][] configProvider() { @@ -146,31 +121,27 @@ public static Object[][] configProvider() { public OIDCIdentityFederationTestCase(TestUserMode userMode) throws Exception { context = new AutomationContext("IDENTITY", userMode); - this.username = context.getContextTenant().getTenantAdmin().getUserName(); - this.userPassword = context.getContextTenant().getTenantAdmin().getPassword(); } @BeforeClass(alwaysRun = true) public void initTest() throws Exception { + mockIdP = new MockOIDCIdentityProvider(); + mockIdP.start(); super.initTest(); createServiceClients(PORT_OFFSET_0, new IdentityConstants.ServiceClientType[]{ IdentityConstants.ServiceClientType.APPLICATION_MANAGEMENT, IdentityConstants.ServiceClientType.IDENTITY_PROVIDER_MGT}); - createServiceClients(PORT_OFFSET_1, new IdentityConstants.ServiceClientType[]{ - IdentityConstants.ServiceClientType.APPLICATION_MANAGEMENT}); - - createApplicationInSecondaryIS(); createIDPInPrimaryIS(); createApplicationInPrimaryIS(); cookieStore = new BasicCookieStore(); - cookieSpecRegistry = RegistryBuilder.create() + Lookup cookieSpecRegistry = RegistryBuilder.create() .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider()) .build(); - requestConfig = RequestConfig.custom() + RequestConfig requestConfig = RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT) .build(); client = HttpClientBuilder.create() @@ -178,9 +149,6 @@ public void initTest() throws Exception { .setDefaultRequestConfig(requestConfig) .setDefaultCookieStore(cookieStore) .build(); - - scim2RestClient = new SCIM2RestClient(getSecondaryISURI(), tenantInfo); - addUserToSecondaryIS(); } @AfterClass(alwaysRun = true) @@ -189,14 +157,11 @@ public void endTest() throws Exception { try { deleteApplication(PORT_OFFSET_0, primaryISAppId); deleteIdp(PORT_OFFSET_0, primaryISIdpId); - deleteApplication(PORT_OFFSET_1, secondaryISAppId); - - deleteAddedUsersInSecondaryIS(); client.close(); - scim2RestClient.closeHttpClient(); + mockIdP.stop(); } catch (Exception e) { - log.error("Failure occured due to :" + e.getMessage(), e); + log.error("Failure occurred due to :" + e.getMessage(), e); throw e; } } @@ -204,17 +169,14 @@ public void endTest() throws Exception { @Test(groups = "wso2.is", description = "Check SAML-to-OIDC federated login") public void testFederatedLogin() throws Exception { - String sessionDataKeyOfSecondaryISLogin = sendSAMLRequestToPrimaryIS(); - Assert.assertNotNull(sessionDataKeyOfSecondaryISLogin, - "Unable to acquire 'sessionDataKey' value in secondary IS"); - - String sessionDataKeyConsentOfSecondaryIS = doAuthenticationInSecondaryIS(sessionDataKeyOfSecondaryISLogin); - Assert.assertNotNull(sessionDataKeyConsentOfSecondaryIS, "Invalid sessionDataKeyConsent."); - - String callbackURLOfPrimaryIS = doConsentApprovalInSecondaryIS(sessionDataKeyConsentOfSecondaryIS); - Assert.assertNotNull(callbackURLOfPrimaryIS, "Unable to acquire authorizeCallbackURL in primary IS"); + // Sending the SAML request to the primary IS + // Client will handle all the redirections and will return the final response of the flow which contains the + // SAMLResponse. This is because the mock server is not prompting anything to the user. + HttpGet request = new HttpGet(SAML_SSO_URL); + request.setHeader("User-Agent", USER_AGENT); + HttpResponse response = client.execute(request); - String samlResponse = getSAMLResponseFromPrimaryIS(callbackURLOfPrimaryIS); + String samlResponse = extractValueFromResponse(response, "SAMLResponse", 5); Assert.assertNotNull(samlResponse, "Unable to acquire SAML response from primary IS"); String decodedSAMLResponse = new String(Base64.decode(samlResponse)); @@ -224,15 +186,17 @@ public void testFederatedLogin() throws Exception { String homepageContent = sendSAMLResponseToWebApp(samlResponse); boolean isValidLogin = validateLoginHomePageContent(homepageContent); Assert.assertTrue(isValidLogin, "Invalid SAML login response received by travelocity app"); + mockIdP.verifyForAuthzCodeFlow(); } @Test(groups = "wso2.is", description = "Check SAML-to-OIDC federated logout", dependsOnMethods = { "testFederatedLogin"}) public void testLogout() throws Exception { - sendLogoutRequestToPrimaryIS(); + HttpResponse response = sendGetRequest(client, SAML_LOGOUT_URL); + Assert.assertNotNull(response); - String samlLogoutResponseToWebapp = doLogoutConsentApprovalInSecondaryIS(); + String samlLogoutResponseToWebapp = extractValueFromResponse(response, "SAMLResponse", 5); Assert.assertNotNull(samlLogoutResponseToWebapp, "Unable to acquire SAML Logout response from travelocity app"); @@ -242,6 +206,7 @@ public void testLogout() throws Exception { String logoutPageContent = sendSAMLResponseToWebApp(samlLogoutResponseToWebapp); boolean isValidLogout = validateLogoutPageContent(logoutPageContent); Assert.assertTrue(isValidLogout, "Invalid SAML Logout response received by travelocity app"); + mockIdP.verifyForLogoutFlow(); } /**TODO Test case for consent denial from the federated IdP during the logout. Implement after resolving @@ -259,34 +224,6 @@ public void testLogout() throws Exception { // Assert.assertTrue(consentDeniedResponseToWebapp.contains("access_denied")); // } - private void addUserToSecondaryIS() throws Exception { - - UserObject user = new UserObject() - .userName(SECONDARY_IS_TEST_USERNAME) - .password(SECONDARY_IS_TEST_PASSWORD); - - secondaryISUserId = scim2RestClient.createUser(user); - Assert.assertNotNull(secondaryISUserId, "User creation failed in secondary IS."); - - RoleItemAddGroupobj rolePatchReqObject = new RoleItemAddGroupobj(); - rolePatchReqObject.setOp(RoleItemAddGroupobj.OpEnum.ADD); - rolePatchReqObject.setPath("users"); - rolePatchReqObject.addValue(new ListObject().value(secondaryISUserId)); - - String adminRoleId = scim2RestClient.getRoleIdByName(SECONDARY_IS_TEST_USER_ROLES); - scim2RestClient.updateUserRole(new PatchOperationRequestObject().addOperations(rolePatchReqObject), adminRoleId); - } - - private void deleteAddedUsersInSecondaryIS() throws IOException { - - scim2RestClient.deleteUser(secondaryISUserId); - } - - protected String getSecondaryISURI() { - - return String.format(HTTPS_LOCALHOST_SERVICES, DEFAULT_PORT + PORT_OFFSET_1); - } - private void createApplicationInPrimaryIS() throws Exception { ApplicationModel applicationCreationModel = new ApplicationModel() @@ -313,26 +250,6 @@ private void createApplicationInPrimaryIS() throws Exception { "Failed to update local and outbound configs in primary IS"); } - private void createApplicationInSecondaryIS() throws Exception { - - ApplicationModel applicationCreationModel = new ApplicationModel() - .name(SECONDARY_IS_SP_NAME) - .description("This is a test Service Provider") - .isManagementApp(true) - .inboundProtocolConfiguration(new InboundProtocols().oidc(getOIDCConfigurations())); - - secondaryISAppId = addApplication(PORT_OFFSET_1, applicationCreationModel); - Assert.assertNotNull(secondaryISAppId, "Failed to create service provider 'secondarySP' in secondary IS"); - - OpenIDConnectConfiguration oidcConfig = getOIDCInboundDetailsOfApplication(PORT_OFFSET_1, secondaryISAppId); - secondaryISClientID = oidcConfig.getClientId(); - Assert.assertNotNull(secondaryISClientID, - "Failed to update service provider with inbound OIDC configs in secondary IS"); - secondaryISClientSecret = oidcConfig.getClientSecret(); - Assert.assertNotNull(secondaryISClientSecret, - "Failed to update service provider with inbound OIDC configs in secondary IS"); - } - private void createIDPInPrimaryIS() throws Exception { FederatedAuthenticator authenticator = new FederatedAuthenticator() @@ -344,22 +261,22 @@ private void createIDPInPrimaryIS() throws Exception { .value("oidcFedIdP")) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.CLIENT_ID) - .value(secondaryISClientID)) + .value(MOCK_IDP_CLIENT_ID)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.CLIENT_SECRET) - .value(secondaryISClientSecret)) + .value(MOCK_IDP_CLIENT_SECRET)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.OAUTH2_AUTHZ_URL) - .value(SECONDARY_IS_AUTHORIZE_ENDPOINT)) + .value(MOCK_IDP_AUTHORIZE_ENDPOINT)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.OAUTH2_TOKEN_URL) - .value(SECONDARY_IS_TOKEN_ENDPOINT)) + .value(MOCK_IDP_TOKEN_ENDPOINT)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.CALLBACK_URL) .value(PRIMARY_IS_IDP_CALLBACK_URL)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key(IdentityConstants.Authenticator.OIDC.OIDC_LOGOUT_URL) - .value(SECONDARY_IS_LOGOUT_ENDPOINT)) + .value(MOCK_IDP_LOGOUT_ENDPOINT)) .addProperty(new org.wso2.identity.integration.test.rest.api.server.idp.v1.model.Property() .key("commonAuthQueryParams") .value("scope=" + OAuth2Constant.OAUTH2_SCOPE_OPENID_WITH_INTERNAL_LOGIN)); @@ -382,19 +299,6 @@ private void createIDPInPrimaryIS() throws Exception { Assert.assertNotNull(primaryISIdpId, "Failed to create Identity Provider 'trustedIdP' in primary IS"); } - private OpenIDConnectConfiguration getOIDCConfigurations() { - - 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"); - - OpenIDConnectConfiguration oidcConfig = new OpenIDConnectConfiguration(); - oidcConfig.setGrantTypes(grantTypes); - oidcConfig.addCallbackURLsItem(PRIMARY_IS_IDP_CALLBACK_URL); - - return oidcConfig; - } - private SAML2Configuration getSAMLConfigurations() { SAML2ServiceProvider serviceProvider = new SAML2ServiceProvider() @@ -415,127 +319,6 @@ private SAML2Configuration getSAMLConfigurations() { return new SAML2Configuration().manualConfiguration(serviceProvider); } - private String sendSAMLRequestToPrimaryIS() throws Exception { - - HttpGet request = new HttpGet(SAML_SSO_URL); - request.setHeader("User-Agent", USER_AGENT); - HttpResponse response = client.execute(request); - return extractValueFromResponse(response, "name=\"sessionDataKey\"", 1); - } - - private String doAuthenticationInSecondaryIS(String sessionDataKey) throws Exception { - - HttpResponse response = sendLoginPost(client, sessionDataKey); - Assert.assertNotNull(response, "Login request failed. response is null."); - - Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION); - Assert.assertNotNull(locationHeader, "Login response header is null."); - EntityUtils.consume(response.getEntity()); - - response = sendGetRequest(client, locationHeader.getValue()); - Map keyPositionMap = new HashMap<>(1); - keyPositionMap.put("name=\"sessionDataKeyConsent\"", 1); - List keyValues = DataExtractUtil.extractSessionConsentDataFromResponse(response, - keyPositionMap); - Assert.assertNotNull(keyValues, "SessionDataKeyConsent key value is null."); - - String sessionDataKeyConsent = keyValues.get(0).getValue(); - EntityUtils.consume(response.getEntity()); - - return sessionDataKeyConsent; - } - - private HttpResponse sendLoginPost(HttpClient client, String sessionDataKey) throws IOException { - - List urlParameters = new ArrayList<>(); - urlParameters.add(new BasicNameValuePair("username", SECONDARY_IS_TEST_USERNAME + "@" + tenantInfo.getDomain())); - urlParameters.add(new BasicNameValuePair("password", SECONDARY_IS_TEST_PASSWORD)); - urlParameters.add(new BasicNameValuePair("sessionDataKey", sessionDataKey)); - - HttpResponse response = sendPostRequestWithParameters(client, urlParameters, SECONDARY_IS_IDP_CALLBACK_URL); - - return response; - } - - private String doConsentApprovalInSecondaryIS(String sessionDataKeyConsent) throws Exception { - - List consentParameters = new ArrayList<>(); - - HttpResponse response = sendApprovalPostWithConsent(client, sessionDataKeyConsent, consentParameters); - Assert.assertNotNull(response, "Approval request failed."); - - Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION); - EntityUtils.consume(response.getEntity()); - - String authzResponseURL = locationHeader.getValue(); - Assert.assertNotNull(authzResponseURL, "Approval request failed for."); - - String authorizeURL = testAuthzCode(authzResponseURL); - return authorizeURL; - } - - private HttpResponse sendApprovalPostWithConsent(HttpClient client, String sessionDataKeyConsent, - List consentClaims) throws IOException { - - List urlParameters = new ArrayList<>(); - urlParameters.add(new BasicNameValuePair("sessionDataKeyConsent", sessionDataKeyConsent)); - urlParameters.add(new BasicNameValuePair("scope-approval", "approve")); - urlParameters.add(new BasicNameValuePair("user_claims_consent", "true")); - urlParameters.add(new BasicNameValuePair("consent_select_all", "on")); - urlParameters.add(new BasicNameValuePair("consent_0", "on")); - urlParameters.add(new BasicNameValuePair("consent", "approve")); - - if (consentClaims != null) { - urlParameters.addAll(consentClaims); - } - - HttpResponse response = sendPostRequestWithParameters(client, urlParameters, SECONDARY_IS_AUTHORIZE_ENDPOINT); - return response; - } - - private String testAuthzCode(String authzResponseURL) throws Exception { - - HttpClient httpClientWithoutAutoRedirections = HttpClientBuilder.create() - .disableRedirectHandling() - .setDefaultCookieSpecRegistry(cookieSpecRegistry) - .setDefaultRequestConfig(requestConfig) - .setDefaultCookieStore(cookieStore).build(); - - HttpResponse response = sendGetRequest(httpClientWithoutAutoRedirections, authzResponseURL); - Assert.assertNotNull(response, "Authorization code response to primary IS is invalid."); - - String locationHeader = getHeaderValue(response, "Location"); - Assert.assertNotNull(locationHeader, "locationHeader not found in response."); - - String pastrCookie = Utils.getPastreCookie(response); - Assert.assertNotNull(pastrCookie, "pastr cookie not found in response."); - - if (Utils.requestMissingClaims(response)) { - locationHeader = handleMissingClaims(response, locationHeader, client, pastrCookie); - Assert.assertNotNull(locationHeader, "locationHeader not found in response."); - } - - return locationHeader; - } - - private String handleMissingClaims(HttpResponse response, String locationHeader, HttpClient client, String - pastrCookie) throws Exception { - - EntityUtils.consume(response.getEntity()); - - response = Utils.sendPOSTConsentMessage(response, PRIMARY_IS_IDP_CALLBACK_URL, USER_AGENT, locationHeader, - client, pastrCookie); - EntityUtils.consume(response.getEntity()); - - return getHeaderValue(response, "Location"); - } - - private String getSAMLResponseFromPrimaryIS(String callbackURL) throws IOException { - - HttpResponse response = sendGetRequest(client, callbackURL); - return extractValueFromResponse(response, "SAMLResponse", 5); - } - private String sendSAMLResponseToWebApp(String samlResponse) throws Exception { @@ -556,7 +339,7 @@ private HttpResponse getHttpResponseWebApp(String samlResponse) throws IOExcepti HttpPost request = new HttpPost(PRIMARY_IS_SAML_ACS_URL); request.setHeader("User-Agent", USER_AGENT); - List urlParameters = new ArrayList(); + List urlParameters = new ArrayList<>(); urlParameters.add(new BasicNameValuePair("SAMLResponse", samlResponse)); request.setEntity(new UrlEncodedFormEntity(urlParameters)); return client.execute(request); @@ -567,60 +350,11 @@ private boolean validateLoginHomePageContent(String homepageContent) { return homepageContent.contains("You are logged in as "); } - private HttpResponse sendLogoutRequestToPrimaryIS() throws IOException { - - HttpResponse response = sendGetRequest(client, SAML_LOGOUT_URL); - EntityUtils.consume(response.getEntity()); - Assert.assertNotNull(response); - return response; - } - - private String doLogoutConsentApprovalInSecondaryIS() throws Exception { - - HttpResponse response = sendLogoutApprovalPostWithConsent(client); - Assert.assertNotNull(response, "Approval request failed."); - - Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION); - Assert.assertNotNull(locationHeader, "Approval request failed for."); - EntityUtils.consume(response.getEntity()); - - String logoutResponseToPrimaryIS = locationHeader.getValue(); - - response = sendGetRequest(client, logoutResponseToPrimaryIS); - return extractValueFromResponse(response, "SAMLResponse", 5); - } - - private String doLogoutConsentDenyInSecondaryIS() throws Exception { - - HttpResponse response = sendLogoutDenyPostWithConsent(client); - Assert.assertNotNull(response, "Approval request failed."); - - Header locationHeader = response.getFirstHeader(OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION); - Assert.assertNotNull(locationHeader, "Approval request failed for."); - EntityUtils.consume(response.getEntity()); - - String logoutResponseToPrimaryIS = locationHeader.getValue(); - - response = sendGetRequest(client, logoutResponseToPrimaryIS); - return extractValueFromResponse(response, "error", 3); - } - private boolean validateLogoutPageContent(String logoutPageContent) { return logoutPageContent.contains("location.href = \"index.jsp\""); } - private HttpResponse sendPostRequestWithParameters(HttpClient client, List urlParameters, String url) - throws ClientProtocolException, IOException { - - HttpPost request = new HttpPost(url); - request.setHeader("User-Agent", OAuth2Constant.USER_AGENT); - request.setEntity(new UrlEncodedFormEntity(urlParameters)); - - HttpResponse response = client.execute(request); - return response; - } - private HttpResponse sendGetRequest(HttpClient client, String locationURL) throws ClientProtocolException, IOException { @@ -630,32 +364,4 @@ private HttpResponse sendGetRequest(HttpClient client, String locationURL) throw return response; } - - private HttpResponse sendLogoutApprovalPostWithConsent(HttpClient client) throws IOException { - - List urlParameters = new ArrayList<>(); - urlParameters.add(new BasicNameValuePair("consent", "approve")); - - HttpResponse response = sendPostRequestWithParameters(client, urlParameters, SECONDARY_IS_LOGOUT_ENDPOINT); - return response; - } - - private HttpResponse sendLogoutDenyPostWithConsent(HttpClient client) throws IOException { - - List urlParameters = new ArrayList<>(); - urlParameters.add(new BasicNameValuePair("consent", "deny")); - - HttpResponse response = sendPostRequestWithParameters(client, urlParameters, SECONDARY_IS_LOGOUT_ENDPOINT); - return response; - } - - private HttpResponse sendPostRequest(HttpClient client, String locationURL) throws ClientProtocolException, - IOException { - - HttpPost postRequest = new HttpPost(locationURL); - postRequest.setHeader("User-Agent", OAuth2Constant.USER_AGENT); - HttpResponse response = client.execute(postRequest); - - return response; - } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsSuccessTest.java index 1e51aa627aa..7e7494bb729 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsSuccessTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsSuccessTest.java @@ -132,7 +132,7 @@ public void testGetActionByActionType() { } @Test(dependsOnMethods = {"testGetActionByActionType"}) - public void testGetActions() { + public void testGetActionTypes() { Response responseOfGet = getResponseOfGet(ACTION_MANAGEMENT_API_BASE_PATH + TYPES_API_PATH); responseOfGet.then() @@ -148,7 +148,7 @@ public void testGetActions() { .body( "find { it.type == '" + PRE_ISSUE_ACCESS_TOKEN_ACTION_TYPE + "' }.self", notNullValue()); } - @Test(dependsOnMethods = {"testGetActions"}) + @Test(dependsOnMethods = {"testGetActionTypes"}) public void testUpdateAction() { // Update all the attributes of the action. diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsTestBase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsTestBase.java index f68c245a4fb..0770b8ae09f 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsTestBase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/action/management/v1/ActionsTestBase.java @@ -80,7 +80,6 @@ public class ActionsTestBase extends RESTAPIServerTestBase { NOT_IMPLEMENTED_ACTION_TYPE_PATHS.add("/preUpdatePassword"); NOT_IMPLEMENTED_ACTION_TYPE_PATHS.add("/preUpdateProfile"); NOT_IMPLEMENTED_ACTION_TYPE_PATHS.add("/preRegistration"); - NOT_IMPLEMENTED_ACTION_TYPE_PATHS.add("/authentication"); String API_PACKAGE_NAME = "org.wso2.carbon.identity.api.server.action.management.v1"; try { diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/application/management/v1/ApplicationManagementSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/application/management/v1/ApplicationManagementSuccessTest.java index 66aee6d25db..fef332c2ce5 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/application/management/v1/ApplicationManagementSuccessTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/application/management/v1/ApplicationManagementSuccessTest.java @@ -90,6 +90,28 @@ public void testGetAllApplications() throws IOException { } } + @Test + public void testGetAllApplicationsExcludingSystemApps() throws IOException { + + Map params = new HashMap<>(); + params.put("excludeSystemPortals", true); + Response response = getResponseOfGet(APPLICATION_MANAGEMENT_API_BASE_PATH, params); + response.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK); + ObjectMapper jsonWriter = new ObjectMapper(new JsonFactory()); + ApplicationListResponse listResponse = jsonWriter.readValue(response.asString(), ApplicationListResponse.class); + Assert.assertFalse(listResponse.getApplications() + .stream() + .anyMatch(appBasicInfo -> appBasicInfo.getName().equals(ApplicationConstants.CONSOLE_APPLICATION_NAME)), + "Default resident service provider '" + ApplicationConstants.CONSOLE_APPLICATION_NAME + "' is listed by the API"); + Assert.assertFalse(listResponse.getApplications() + .stream() + .anyMatch(appBasicInfo -> appBasicInfo.getName().equals(ApplicationConstants.MY_ACCOUNT_APPLICATION_NAME)), + "Default resident service provider '" + ApplicationConstants.MY_ACCOUNT_APPLICATION_NAME + "' is listed by the API"); + } + @Test public void testGetResidentApplication() throws IOException { diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorSuccessTest.java new file mode 100644 index 00000000000..b2eeb6f7e31 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorSuccessTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, 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.authenticator.management.v1; + +import io.restassured.RestAssured; +import io.restassured.response.Response; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpStatus; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; +import org.testng.Assert; +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.testng.annotations.Factory; +import org.testng.annotations.Test; +import org.wso2.carbon.automation.engine.context.TestUserMode; + +import java.io.IOException; + +/** + * Tests for happy paths of the authentication Management REST API. + */ +public class AuthenticatorSuccessTest extends AuthenticatorTestBase{ + + + @Factory(dataProvider = "restAPIUserConfigProvider") + public AuthenticatorSuccessTest(TestUserMode userMode) throws Exception { + + super.init(userMode); + this.context = isServer; + this.authenticatingUserName = context.getContextTenant().getTenantAdmin().getUserName(); + this.authenticatingCredential = context.getContextTenant().getTenantAdmin().getPassword(); + this.tenant = context.getContextTenant().getDomain(); + } + + @BeforeClass(alwaysRun = true) + public void init() throws IOException { + + super.testInit(API_VERSION, swaggerDefinition, tenant); + } + + @AfterClass(alwaysRun = true) + public void testConclude() { + + super.conclude(); + } + + @BeforeMethod(alwaysRun = true) + public void testInit() { + + RestAssured.basePath = basePath; + } + + @AfterMethod(alwaysRun = true) + public void testFinish() { + + RestAssured.basePath = StringUtils.EMPTY; + } + + @DataProvider(name = "restAPIUserConfigProvider") + public static Object[][] restAPIUserConfigProvider() { + + return new Object[][]{ + {TestUserMode.SUPER_TENANT_ADMIN}, + {TestUserMode.TENANT_ADMIN} + }; + } + + @Test + public void getAuthenticators() throws JSONException { + + Response response = getResponseOfGet(AUTHENTICATOR_API_BASE_PATH); + response.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK); + + JSONArray jsonArray = new JSONArray(response.body().asString()); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject authenticator = jsonArray.getJSONObject(i); + Assert.assertTrue(authenticator.has("id")); + Assert.assertTrue(authenticator.has("name")); + Assert.assertTrue(authenticator.has("displayName")); + Assert.assertTrue(authenticator.has("type")); + Assert.assertEquals(authenticator.getString("definedBy"), "SYSTEM"); + } + } + +} + diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorTestBase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorTestBase.java new file mode 100644 index 00000000000..677455d1ed0 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/authenticator/management/v1/AuthenticatorTestBase.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 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.authenticator.management.v1; + +import io.restassured.RestAssured; +import org.apache.commons.lang.StringUtils; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.wso2.identity.integration.test.rest.api.server.common.RESTAPIServerTestBase; + +import java.io.IOException; + +public class AuthenticatorTestBase extends RESTAPIServerTestBase { + + private static final String API_DEFINITION_NAME = "authenticators.yaml"; + protected static final String API_VERSION = "v1"; + protected static final String API_PACKAGE_NAME = "org.wso2.carbon.identity.api.server.authenticators.v1"; + + protected static final String AUTHENTICATOR_API_BASE_PATH = "/authenticators"; + + protected static String swaggerDefinition; + + static { + try { + swaggerDefinition = getAPISwaggerDefinition(API_PACKAGE_NAME, API_DEFINITION_NAME); + } catch (IOException e) { + Assert.fail(String.format("Unable to read the swagger definition %s from %s", API_DEFINITION_NAME, + API_PACKAGE_NAME), e); + } + } + + @AfterClass(alwaysRun = true) + public void testConclude() throws Exception { + + super.conclude(); + } + + @BeforeMethod(alwaysRun = true) + public void testInit() { + + RestAssured.basePath = basePath; + } + + @AfterMethod(alwaysRun = true) + public void testFinish() { + + RestAssured.basePath = StringUtils.EMPTY; + } +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/configs/v1/ConfigSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/configs/v1/ConfigSuccessTest.java index 46acc836533..68f39b7b802 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/configs/v1/ConfigSuccessTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/configs/v1/ConfigSuccessTest.java @@ -104,7 +104,8 @@ public void testGetAuthenticator() throws Exception { .body("name", equalTo("BasicAuthenticator")) .body("displayName", equalTo("Username & Password")) .body("isEnabled", equalTo(true)) - .body("properties", notNullValue()); + .body("properties", notNullValue()) + .body("definedBy", equalTo("SYSTEM")); } @Test(dependsOnMethods = {"testGetAuthenticator"}) @@ -118,7 +119,8 @@ public void testGetAuthenticators() throws Exception { .statusCode(HttpStatus.SC_OK) .body(baseIdentifier + "name", equalTo("BasicAuthenticator")) .body(baseIdentifier + "displayName", equalTo("Username & Password")) - .body(baseIdentifier + "isEnabled", equalTo(true)); + .body(baseIdentifier + "isEnabled", equalTo(true)) + .body(baseIdentifier + "definedBy", equalTo("SYSTEM")); } @Test(dependsOnMethods = {"testGetAuthenticators"}) diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/idp/v1/IdPSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/idp/v1/IdPSuccessTest.java index 9f624c128d5..ba7dfcc0609 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/idp/v1/IdPSuccessTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/idp/v1/IdPSuccessTest.java @@ -360,7 +360,8 @@ public void testUpdateIdPFederatedAuthenticator() throws IOException { response.then() .log().ifValidationFails() .assertThat() - .statusCode(HttpStatus.SC_OK); + .statusCode(HttpStatus.SC_OK) + .body("definedBy", equalTo("SYSTEM")); } @Test(dependsOnMethods = {"testUpdateIdPFederatedAuthenticator"}) @@ -377,6 +378,7 @@ public void testGetIdPFederatedAuthenticator() throws IOException { .body("isEnabled", equalTo(true)) .body("isDefault", equalTo(true)) .body("properties", notNullValue()) + .body("definedBy", equalTo("SYSTEM")) .body("properties.find{ it.key == 'ClientId' }.value", equalTo ("165474950684-7mvqd8m6hieb8mdnffcarnku2aua0tpl.apps.googleusercontent.com")) .body("properties.find{ it.key == 'ClientSecret' }.value", equalTo("testclientsecret")) diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantFailureTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantFailureTest.java index d6eff646b71..469907f7f8e 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantFailureTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantFailureTest.java @@ -26,6 +26,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.wso2.carbon.automation.engine.context.TestUserMode; +import org.wso2.identity.integration.test.rest.api.server.tenant.management.v1.model.TenantResponseModel; import java.io.IOException; @@ -37,6 +38,7 @@ public class TenantFailureTest extends TenantManagementBaseTest { private String tenantId; + private String userId; public TenantFailureTest() throws Exception { @@ -100,4 +102,51 @@ public void testGetOwnersWithInvalidId() { validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "TM-60014", "random-id"); } + @Test void testGetOwnerWithInvalidTenantId() { + + Response response = getResponseOfGet(TENANT_API_BASE_PATH + PATH_SEPARATOR + "random-id" + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + "random-id"); + validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "TM-60014", "random-id"); + } + + @Test + public void updateOwnerWithInvalidTenantId() throws IOException { + + String body = readResource("update-owner.json"); + Response response = getResponseOfPut(TENANT_API_BASE_PATH + PATH_SEPARATOR + "random-id" + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + "random-id", body); + validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "TM-60014", "random-id"); + } + + @Test + public void addTenantWithInvalidClaim() throws IOException { + + Response response = getResponseOfPost(TENANT_API_BASE_PATH, readResource("add-tenant-invalid-claims.json")); + validateErrorResponse(response, HttpStatus.SC_PARTIAL_CONTENT, "TM-60021", "Telephone is not in the correct format."); + } + + @Test(dependsOnMethods = "addTenantWithInvalidClaim") + public void getTenantOwnerWithInvalidOwnerId() { + + Response response = getResponseOfGet(TENANT_API_BASE_PATH + TENANT_DOMAIN_BASE_PATH + PATH_SEPARATOR + "abc3.com"); + + TenantResponseModel tenantResponseModel = response.getBody().as(TenantResponseModel.class); + tenantId = tenantResponseModel.getId(); + userId = tenantResponseModel.getOwners().get(0).getId(); + + response = getResponseOfGet(TENANT_API_BASE_PATH + PATH_SEPARATOR + tenantId + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + "random-id"); + validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "TM-60020", tenantId); + } + + @Test(dependsOnMethods = "getTenantOwnerWithInvalidOwnerId") + public void updateTenantOwnerWithInvalidOwnerId() throws IOException { + + String body = readResource("update-owner.json"); + Response response = getResponseOfPut(TENANT_API_BASE_PATH + PATH_SEPARATOR + tenantId + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + "random-id", body); + validateErrorResponse(response, HttpStatus.SC_BAD_REQUEST, "TM-60020", tenantId); + } + + } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantSuccessTest.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantSuccessTest.java index e78f1897db9..b259d5c38a7 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantSuccessTest.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/TenantSuccessTest.java @@ -43,6 +43,7 @@ public class TenantSuccessTest extends TenantManagementBaseTest { private String tenantId; private String userId; + private static final String TELEPHONE_CLAIM = "http://wso2.org/claims/telephone"; public TenantSuccessTest() throws Exception { @@ -152,4 +153,34 @@ public void testGetOwners() throws Exception { .body(activeStatusIdentifier, notNullValue()) .body(activeStatusIdentifier + ".username", equalTo("kim")); } + + @Test(dependsOnMethods = {"testGetTenant"}) + public void testUpdateOwner() throws Exception { + + String body = readResource("update-owner.json"); + Response response = getResponseOfPut(TENANT_API_BASE_PATH + PATH_SEPARATOR + tenantId + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + userId, body); + + response.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK); + } + + @Test(dependsOnMethods = {"testUpdateOwner"}) + public void testGetOwner() { + + Response response = getResponseOfGet(TENANT_API_BASE_PATH + PATH_SEPARATOR + tenantId + + TENANT_API_OWNER_PATH + PATH_SEPARATOR + userId + "?additionalClaims=" + TELEPHONE_CLAIM); + + String claimsIdentifier = "additionalClaims.find{ it.claim == '" + TELEPHONE_CLAIM + "' }"; + + response.then() + .log().ifValidationFails() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .body("username", equalTo("kim")) + .body( "lastname", equalTo("lee")) + .body(claimsIdentifier + ".value", equalTo("+94 77 123 4568")); + } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaMeTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaMeTestCase.java index c66318a2438..4e8c4c67cf5 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaMeTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaMeTestCase.java @@ -21,6 +21,7 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHeaders; @@ -46,6 +47,7 @@ import org.wso2.identity.integration.test.scim2.rest.api.SCIM2BaseTest; import java.rmi.RemoteException; +import java.util.Arrays; import java.util.LinkedHashMap; import static org.hamcrest.CoreMatchers.notNullValue; @@ -125,7 +127,10 @@ public void init() throws RemoteException, InterruptedException { public void testFinish() { try { - claimMetadataManagementServiceClient.removeClaimDialect(CUSTOM_SCHEMA_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, COUNTRY_CLAIM_ATTRIBUTE_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, + MANAGER_EMAIL_CLAIM_ATTRIBUTE_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, MANAGER_CLAIM_ATTRIBUTE_URI); claimMetadataManagementServiceClient.removeLocalClaim(MANAGER_LOCAL_CLAIM_URI); } catch (RemoteException | ClaimMetadataManagementServiceClaimMetadataException e) { log.error(e); @@ -139,7 +144,7 @@ public void testInit() { RestAssured.basePath = basePath; } - @Test(description = "Creates custom schema dialect, simple attribute and complex attributes.") + @Test(description = "Creates simple attribute and complex attributes in urn:scim:wso2:schema.") private void createClaims() throws Exception { AutomationContext context = new AutomationContext("IDENTITY", mode); @@ -147,17 +152,19 @@ private void createClaims() throws Exception { loginLogoutClient = new LoginLogoutClient(context); cookie = loginLogoutClient.login(); claimMetadataManagementServiceClient = new ClaimMetadataManagementServiceClient(backendURL, cookie); - int claimDialect = claimMetadataManagementServiceClient.getClaimDialects().length; - // Set custom schema dialect. - ClaimDialectDTO claimDialectDTO = new ClaimDialectDTO(); - claimDialectDTO.setClaimDialectURI(CUSTOM_SCHEMA_URI); - claimMetadataManagementServiceClient.addClaimDialect(claimDialectDTO); - - //Set claims + //Set claims. setSimpleAttribute(); setComplexAttribute(); - assertEquals(claimMetadataManagementServiceClient.getClaimDialects().length, claimDialect + 1); + + ExternalClaimDTO[] externalClaimDTOs = + claimMetadataManagementServiceClient.getExternalClaims(CUSTOM_SCHEMA_URI); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), COUNTRY_CLAIM_ATTRIBUTE_URI))); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), MANAGER_CLAIM_ATTRIBUTE_URI))); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), MANAGER_EMAIL_CLAIM_ATTRIBUTE_URI))); } @Test(dependsOnMethods = "createClaims", description = "Create user with custom schema dialect.") diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaUserTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaUserTestCase.java index 4c992866d75..57ba44cd3ba 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaUserTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/scim2/rest/api/customSchema/SCIM2CustomSchemaUserTestCase.java @@ -48,6 +48,7 @@ import org.wso2.identity.integration.test.scim2.rest.api.SCIMUtils; import java.rmi.RemoteException; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -123,7 +124,10 @@ public void init() throws RemoteException, InterruptedException { public void testFinish() { try { - claimMetadataManagementServiceClient.removeClaimDialect(CUSTOM_SCHEMA_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, COUNTRY_CLAIM_ATTRIBUTE_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, + MANAGER_EMAIL_CLAIM_ATTRIBUTE_URI); + claimMetadataManagementServiceClient.removeExternalClaim(CUSTOM_SCHEMA_URI, MANAGER_CLAIM_ATTRIBUTE_URI); claimMetadataManagementServiceClient.removeLocalClaim(MANAGER_LOCAL_CLAIM_URI); } catch (RemoteException | ClaimMetadataManagementServiceClaimMetadataException e) { log.error(e); @@ -137,7 +141,7 @@ public void testInit() { RestAssured.basePath = basePath; } - @Test(description = "Creates custom schema dialect, simple attribute and complex attributes.") + @Test(description = "Creates simple attribute and complex attributes in urn:scim:wso2:schema.") private void createClaims() throws Exception { AutomationContext context = new AutomationContext("IDENTITY", mode); @@ -145,17 +149,19 @@ private void createClaims() throws Exception { loginLogoutClient = new LoginLogoutClient(context); cookie = loginLogoutClient.login(); claimMetadataManagementServiceClient = new ClaimMetadataManagementServiceClient(backendURL, cookie); - int claimDialect = claimMetadataManagementServiceClient.getClaimDialects().length; - // Set custom schema dialect. - ClaimDialectDTO claimDialectDTO = new ClaimDialectDTO(); - claimDialectDTO.setClaimDialectURI(CUSTOM_SCHEMA_URI); - claimMetadataManagementServiceClient.addClaimDialect(claimDialectDTO); - - //Set claims + //Set claims. setSimpleAttribute(); setComplexAttribute(); - assertEquals(claimMetadataManagementServiceClient.getClaimDialects().length, claimDialect + 1); + + ExternalClaimDTO[] externalClaimDTOs = + claimMetadataManagementServiceClient.getExternalClaims(CUSTOM_SCHEMA_URI); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), COUNTRY_CLAIM_ATTRIBUTE_URI))); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), MANAGER_CLAIM_ATTRIBUTE_URI))); + Assert.assertTrue(Arrays.stream(externalClaimDTOs) + .anyMatch(claim -> StringUtils.equals(claim.getExternalClaimURI(), MANAGER_EMAIL_CLAIM_ATTRIBUTE_URI))); } @Test(dependsOnMethods = "createClaims", description = "Create user with custom schema dialect.") diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/util/Utils.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/util/Utils.java index c7d5b6b5d32..0d31062c812 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/util/Utils.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/util/Utils.java @@ -699,4 +699,23 @@ public static String getBasicAuthHeader(User userInfo) { BasicAuthInfo encodedBasicAuthInfo = (BasicAuthInfo) basicAuthHandler.getAuthenticationToken(basicAuthInfo); return encodedBasicAuthInfo.getAuthorizationHeader(); } + + /** + * Get Java Major Version from System Property. + * + * @return Java Major Version + */ + public static int getJavaVersion() { + + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } } diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/identity_new_resource_openjdknashorn.toml b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/identity_new_resource_openjdknashorn.toml new file mode 100644 index 00000000000..57fce1ac3d3 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/identity_new_resource_openjdknashorn.toml @@ -0,0 +1,36 @@ +[server] +hostname = "localhost" +node_ip = "127.0.0.1" +base_path = "https://$ref{server.hostname}:${carbon.management.port}" + +[super_admin] +username = "admin" +password = "admin" +create_admin_account = true + +[user_store] +type = "database_unique_id" + +[database.identity_db] +driver = "$env{IDENTITY_DATABASE_DRIVER}" +url = "$env{IDENTITY_DATABASE_URL}" +username = "$env{IDENTITY_DATABASE_USERNAME}" +password = "$env{IDENTITY_DATABASE_PASSWORD}" + +[database.shared_db] +driver = "$env{SHARED_DATABASE_DRIVER}" +url = "$env{SHARED_DATABASE_URL}" +username = "$env{SHARED_DATABASE_USERNAME}" +password = "$env{SHARED_DATABASE_PASSWORD}" + +[keystore.primary] +file_name = "wso2carbon.p12" +password = "wso2carbon" + +[[resource.access_control]] +context = "(.*)/sample-auth/(.*)" +secure = false +http_method = "all" + +[AdaptiveAuth] +ScriptEngine = "openjdkNashorn" diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/scriptEngine/openjdknashorn_script_engine_config.toml b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/scriptEngine/openjdknashorn_script_engine_config.toml new file mode 100644 index 00000000000..b8a9d3dc55c --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/scriptEngine/openjdknashorn_script_engine_config.toml @@ -0,0 +1,42 @@ +[server] +hostname = "localhost" +node_ip = "127.0.0.1" +base_path = "https://$ref{server.hostname}:${carbon.management.port}" + +[super_admin] +username = "admin" +password = "admin" +create_admin_account = true + +[user_store] +type = "database_unique_id" + +[database.identity_db] +type = "h2" +url = "jdbc:h2:./repository/database/WSO2IDENTITY_DB;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=60000" +username = "wso2carbon" +password = "wso2carbon" + +[database.shared_db] +type = "h2" +url = "jdbc:h2:./repository/database/WSO2SHARED_DB;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=60000" +username = "wso2carbon" +password = "wso2carbon" + +[keystore.primary] +file_name = "wso2carbon.p12" +password = "wso2carbon" + +[truststore] +file_name="client-truststore.p12" +password="wso2carbon" +type="PKCS12" + +[account_recovery.endpoint.auth] +hash= "66cd9688a2ae068244ea01e70f0e230f5623b7fa4cdecb65070a09ec06452262" + +[identity.auth_framework.endpoint] +app_password= "dashboard" + +[AdaptiveAuth] +ScriptEngine = "openjdkNashorn" diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/add-tenant-invalid-claims.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/add-tenant-invalid-claims.json new file mode 100644 index 00000000000..123930163b5 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/add-tenant-invalid-claims.json @@ -0,0 +1,23 @@ +{ + "domain": "abc3.com", + "owners": [ + { + "username": "kim", + "password": "kim123", + "email": "kim@wso2.com", + "firstname": "kim", + "lastname": "kim", + "provisioningMethod": "inline-password", + "additionalClaims": [ + { + "claim": "http://wso2.org/claims/telephone", + "value": "+94 77 123 456" + }, + { + "claim": "http://wso2.org/claims/country", + "value": "SriLanka" + } + ] + } + ] +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/update-owner.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/update-owner.json new file mode 100644 index 00000000000..b82e2bb3137 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/rest/api/server/tenant/management/v1/update-owner.json @@ -0,0 +1,11 @@ +{ + "firstname": "kim", + "lastname": "lee", + "email": "kim@wso2.com", + "additionalClaims": [ + { + "claim": "http://wso2.org/claims/telephone", + "value": "+94 77 123 4568" + } + ] +} 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 6cba73daa60..394603ebcb2 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 @@ -137,6 +137,7 @@ + @@ -144,6 +145,7 @@ + @@ -232,6 +234,7 @@ + @@ -249,10 +252,13 @@ - + - @@ -261,7 +267,17 @@ + + @@ -270,9 +286,17 @@ + + @@ -315,7 +339,6 @@ - @@ -362,7 +385,7 @@ - + diff --git a/modules/local-authenticators/pom.xml b/modules/local-authenticators/pom.xml index 72e99fd73ba..57fff0e105d 100644 --- a/modules/local-authenticators/pom.xml +++ b/modules/local-authenticators/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/modules/oauth2-grant-types/pom.xml b/modules/oauth2-grant-types/pom.xml index 94f8db3ac14..40317e41545 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/p2-profile-gen/pom.xml b/modules/p2-profile-gen/pom.xml index 616e72fe534..2c18d19c23d 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/provisioning-connectors/pom.xml b/modules/provisioning-connectors/pom.xml index 5f3faa33efd..20f702b011a 100644 --- a/modules/provisioning-connectors/pom.xml +++ b/modules/provisioning-connectors/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/modules/social-authenticators/pom.xml b/modules/social-authenticators/pom.xml index 4b52ce3e771..da6a28b08cc 100644 --- a/modules/social-authenticators/pom.xml +++ b/modules/social-authenticators/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/modules/styles/pom.xml b/modules/styles/pom.xml index a316be0a1ef..2f61bdf3b33 100644 --- a/modules/styles/pom.xml +++ b/modules/styles/pom.xml @@ -20,7 +20,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/modules/styles/product/pom.xml b/modules/styles/product/pom.xml index f59e1aa4cea..64b39e4233d 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/tests-utils/admin-services/pom.xml b/modules/tests-utils/admin-services/pom.xml index 52f0b34f973..af08dc6d12d 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/tests-utils/admin-stubs/pom.xml b/modules/tests-utils/admin-stubs/pom.xml index a87633b391d..fb85b873984 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.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../pom.xml diff --git a/modules/tests-utils/pom.xml b/modules/tests-utils/pom.xml index 73534fc5f73..27f14c46ab3 100644 --- a/modules/tests-utils/pom.xml +++ b/modules/tests-utils/pom.xml @@ -19,7 +19,7 @@ org.wso2.is identity-server-parent - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 54b4b510bd7..0d10c824058 100755 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ identity-server-parent pom WSO2 Identity Server - 7.1.0-m2-SNAPSHOT + 7.1.0-m4-SNAPSHOT WSO2 Identity Server http://wso2.org/projects/identity @@ -2244,6 +2244,11 @@ org.wso2.carbon.extension.identity.helper ${identity.extension.utils} + + com.nimbusds + nimbus-jose-jwt + ${nimbus-jose-jwt.version} + @@ -2382,12 +2387,12 @@ - 7.0.162 + 7.0.169 5.11.43 5.10.2 - 5.11.9 + 5.11.10 5.7.7 - 3.4.94 + 3.4.97 5.5.10 @@ -2403,7 +2408,7 @@ 1.9.12 - 1.9.9 + 1.9.11 @@ -2473,20 +2478,20 @@ 2.0.17 - 1.2.234 + 1.2.240 1.3.43 5.5.9 5.5.9 2.3.2 2.5.17 - 1.1.12 + 1.1.13 1.2.65 - 2.30.0 - 2.8.20 - 2.4.44 + 2.30.14 + 2.8.33 + 2.4.47 1.6.378 @@ -2496,13 +2501,13 @@ 4.10.23 - 1.0.12 + 1.0.13 4.12.29 - 4.10.9 + 4.10.12 4.8.35 - 4.11.24 + 4.11.27 1.3.12 5.2.58 2.0.27 @@ -2635,6 +2640,7 @@ 2.0.1 2.0.1 1.15.3 + 9.41.2