From cee13e8f5e5721efe2e79fbf4ed7ebd594d924a1 Mon Sep 17 00:00:00 2001 From: Yasasr1 Date: Tue, 12 Nov 2024 11:04:58 +0530 Subject: [PATCH 1/4] Add organization discovery integration tests. --- .../OrganizationDiscoveryTestCase.java | 408 ++++++++++++++++++ .../ClaimManagementRestClient.java | 21 +- .../OrgDiscoveryConfigRestClient.java | 114 +++++ .../email_as_username.toml | 46 ++ .../email_as_username_request.json | 15 + .../enable_email_domain_org_discovery.json | 12 + .../map_email_domain_to_sub_org.json | 10 + .../organization-onboarding-apis.json | 7 + .../revert_email_as_username_request.json | 15 + .../src/test/resources/testng.xml | 1 + 10 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java create mode 100644 modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/organizationDiscovery/email_as_username.toml create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/email_as_username_request.json create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/enable_email_domain_org_discovery.json create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/map_email_domain_to_sub_org.json create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/organization-onboarding-apis.json create mode 100644 modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/revert_email_as_username_request.json diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java new file mode 100644 index 00000000000..4f432582b56 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java @@ -0,0 +1,408 @@ +/* + * 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.organizationDiscovery; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.utils.URLEncodedUtils; +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.DefaultRedirectStrategy; +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.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.integration.common.utils.mgt.ServerConfigurationManager; +import org.wso2.identity.integration.test.oauth2.OAuth2ServiceAbstractIntegrationTest; +import org.wso2.identity.integration.test.oauth2.dataprovider.model.ApplicationConfig; +import org.wso2.identity.integration.test.rest.api.common.RESTTestBase; +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.ApplicationSharePOSTRequest; +import org.wso2.identity.integration.test.restclients.ClaimManagementRestClient; +import org.wso2.identity.integration.test.restclients.OAuth2RestClient; +import org.wso2.identity.integration.test.restclients.OrgMgtRestClient; +import org.wso2.identity.integration.test.restclients.OrgDiscoveryConfigRestClient; +import org.wso2.identity.integration.test.util.Utils; +import org.wso2.identity.integration.test.utils.OAuth2Constant; + +import java.io.File; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.wso2.identity.integration.test.utils.OAuth2Constant.AUTHORIZE_ENDPOINT_URL; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.COMMON_AUTH_URL; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.HTTP_RESPONSE_HEADER_LOCATION; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.OAUTH2_CLIENT_ID; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.REDIRECT_URI_NAME; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.SCOPE_PLAYGROUND_NAME; +import static org.wso2.identity.integration.test.utils.OAuth2Constant.SESSION_DATA_KEY; + +public class OrganizationDiscoveryTestCase extends OAuth2ServiceAbstractIntegrationTest { + + private static final String EMAIL_AS_USERNAME_TOML = "email_as_username.toml"; + private static final String LOCAL_CLAIM_DIALECT = "local"; + private static final String USERNAME_CLAIM_ID = "aHR0cDovL3dzbzIub3JnL2NsYWltcy91c2VybmFtZQ"; + private static final String EMAIL_WITH_VALID_DOMAIN = "john@wso2.com"; + private static final String EMAIL_WITH_INVALID_DOMAIN = "john@gmail.com"; + private static final String REGISTER_ENDPOINT_PATH = "/accountrecoveryendpoint/register.do"; + private static final String EMAIL_AS_USERNAME_CLAIM_JSON = "email_as_username_request.json"; + private static final String REVERT_EMAIL_AS_USERNAME_CLAIM_JSON = "revert_email_as_username_request.json"; + private static final String ENABLE_EMAIL_DOMAIN_ORG_DISCOVERY_JSON = "enable_email_domain_org_discovery.json"; + private static final String ORG_ONBOARDING_APIS_JSON = "organization-onboarding-apis.json"; + private static final String EMAIL_DOMAIN_SUB_ORG_JSON = "map_email_domain_to_sub_org.json"; + public static final String AUTHENTICATOR = "authenticator"; + public static final String IDP = "idp"; + public static final String LOGIN_HINT = "login_hint"; + public static final String ORGANIZATION_AUTHENTICATOR = "OrganizationAuthenticator"; + public static final String SSO = "SSO"; + private String subOrgId; + private ApplicationResponseModel application; + private ServerConfigurationManager serverConfigurationManager; + private CloseableHttpClient client; + private ClaimManagementRestClient claimManagementRestClient; + private OrgDiscoveryConfigRestClient organizationDiscoveryConfigRestClient; + private OAuth2RestClient oAuth2RestClient; + private OrgMgtRestClient orgMgtRestClient; + + @BeforeClass(alwaysRun = true) + public void testInit() throws Exception { + + super.init(); + + // Creating the http client which is used during the tests. + Lookup cookieSpecRegistry = RegistryBuilder.create() + .register(CookieSpecs.DEFAULT, new RFC6265CookieSpecProvider()) + .build(); + + RequestConfig requestConfig = RequestConfig.custom() + .setCookieSpec(CookieSpecs.DEFAULT) + .build(); + + client = HttpClientBuilder.create() + .setDefaultRequestConfig(requestConfig) + .setDefaultCookieSpecRegistry(cookieSpecRegistry) + .setRedirectStrategy(new DefaultRedirectStrategy() { + @Override + protected boolean isRedirectable(String method) { + + return false; + } + }).build(); + + // Create an application. + ApplicationConfig applicationConfig = new ApplicationConfig.Builder() + .grantTypes(new ArrayList<>(Collections.singleton(OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE))) + .tokenType(ApplicationConfig.TokenType.OPAQUE) + .expiryTime(3600) + .build(); + application = addApplication(applicationConfig); + + // Apply toml configuration to set email as username and restart the server. + applyEmailAsUsernameConfig(); + // Init again after restart. + super.init(); + + // Create a sub-organization + orgMgtRestClient = new OrgMgtRestClient(isServer, tenantInfo, serverURL, new JSONObject( + RESTTestBase.readResource(ORG_ONBOARDING_APIS_JSON, this.getClass()))); + subOrgId = orgMgtRestClient.addOrganization("subOrg"); + + // Update mapped attribute of username claim to email. + claimManagementRestClient = new ClaimManagementRestClient(serverURL, tenantInfo); + String emailAsUsernameRequestBody = RESTTestBase.readResource(EMAIL_AS_USERNAME_CLAIM_JSON, this.getClass()); + claimManagementRestClient.updateClaim(LOCAL_CLAIM_DIALECT, USERNAME_CLAIM_ID, emailAsUsernameRequestBody); + + // Enable email domain based org discovery for self-registration + organizationDiscoveryConfigRestClient = new OrgDiscoveryConfigRestClient(serverURL, tenantInfo); + String orgDiscoveryConfigRequestBody = RESTTestBase.readResource( + ENABLE_EMAIL_DOMAIN_ORG_DISCOVERY_JSON, this.getClass()); + organizationDiscoveryConfigRestClient.addOrganizationDiscoveryConfig(orgDiscoveryConfigRequestBody); + + // Map an email domain to the created sub-organization + String mapEmailDomainRequestBody = RESTTestBase.readResource(EMAIL_DOMAIN_SUB_ORG_JSON, this.getClass()); + organizationDiscoveryConfigRestClient.mapDiscoveryAttributes(subOrgId, mapEmailDomainRequestBody); + } + + @Test(description = "Test email domain based organization discovery not initiated " + + "for unshared app", priority = 1) + public void testSelfRegistrationOrgDiscoveryForUnsharedApp() throws Exception { + + // Initiate the authorize request to get the login page and retrieve the session data key. + Map loginPageQueryParams = initiateAuthorizeRequest(); + + // Send a GET request to the register endpoint with the query parameters. + HttpResponse response = sendGetRequest(client, isServer.getContextUrls().getWebAppURLHttps() + + REGISTER_ENDPOINT_PATH + "?" + buildQueryString(loginPageQueryParams)); + + // Parse the page content to check if the common auth request will be sent based on the configurations. + String pageContent = EntityUtils.toString(response.getEntity()); + Assert.assertFalse(willRedirectToDomainDiscovery(pageContent), + "Should not be redirected to domain discovery page for an unshared app."); + } + + @Test(description = "Test email domain based organization discovery for self-registration", priority = 2) + public void testSelfRegistrationOrgDiscovery() throws Exception { + + // Share the app + oAuth2RestClient = new OAuth2RestClient(serverURL, tenantInfo); + ApplicationSharePOSTRequest applicationSharePOSTRequest = new ApplicationSharePOSTRequest(); + applicationSharePOSTRequest.setShareWithAllChildren(true); + oAuth2RestClient.shareApplication(application.getId(), applicationSharePOSTRequest); + + // Initiate the authorize request to get the login page and retrieve the session data key. + Map loginPageQueryParams = initiateAuthorizeRequest(); + + // Send a GET request to the register endpoint with the query parameters. + HttpResponse response = sendGetRequest(client, isServer.getContextUrls().getWebAppURLHttps() + + REGISTER_ENDPOINT_PATH + "?" + buildQueryString(loginPageQueryParams)); + + // Parse the page content to check if the common auth request will be sent based on the configurations. + String pageContent = EntityUtils.toString(response.getEntity()); + Assert.assertTrue(willRedirectToDomainDiscovery(pageContent), + "Register page will not send the common auth request."); + + // Send the common auth request which will be sent from the register page. + response = initiateCommonAuthGet(loginPageQueryParams); + Header locationHeader = response.getFirstHeader(HTTP_RESPONSE_HEADER_LOCATION); + // Check the response from the common auth request. + Assert.assertNotNull(locationHeader, "Location header is not present in the common auth response."); + Assert.assertTrue(locationHeader.getValue().contains("org_discovery.do"), + "Organization authenticator did not redirect to domain discovery page."); + EntityUtils.consume(response.getEntity()); + + // Send the form post from domain discovery page. + response = initiateCommonAuthPost(loginPageQueryParams, EMAIL_WITH_VALID_DOMAIN); + // Check if the response is a redirect to the sub-organization with the matching email domain. + locationHeader = response.getFirstHeader(HTTP_RESPONSE_HEADER_LOCATION); + Assert.assertNotNull(locationHeader, "Location header is not present in the common auth response."); + Assert.assertTrue(locationHeader.getValue().contains(subOrgId), + "Failed to redirect to the sub-organization with the matching email domain."); + EntityUtils.consume(response.getEntity()); + } + + @Test(description = "Test email domain based organization discovery for self-registration with an " + + "invalid email domain", priority = 3) + public void testSelfRegistrationOrgDiscoveryWithInvalidEmail() throws Exception { + + // Initiate the authorize request to get the login page and retrieve the session data key. + Map loginPageQueryParams = initiateAuthorizeRequest(); + + // Send a GET request to the register endpoint with the query parameters. + HttpResponse response = sendGetRequest(client, isServer.getContextUrls().getWebAppURLHttps() + + REGISTER_ENDPOINT_PATH + "?" + buildQueryString(loginPageQueryParams)); + + // Parse the page content to check if the common auth request will be sent based on the configurations. + String pageContent = EntityUtils.toString(response.getEntity()); + Assert.assertTrue(willRedirectToDomainDiscovery(pageContent), + "Register page will not send the common auth request."); + + // Send the common auth request which will be sent from the register page. + response = initiateCommonAuthGet(loginPageQueryParams); + Header locationHeader = response.getFirstHeader(HTTP_RESPONSE_HEADER_LOCATION); + // Check the response from the common auth request. + Assert.assertNotNull(locationHeader, "Location header is not present in the common auth response."); + Assert.assertTrue(locationHeader.getValue().contains("org_discovery.do"), + "Organization authenticator did not redirect to domain discovery page."); + EntityUtils.consume(response.getEntity()); + + // Send the form post from domain discovery page. + response = initiateCommonAuthPost(loginPageQueryParams, EMAIL_WITH_INVALID_DOMAIN); + locationHeader = response.getFirstHeader(HTTP_RESPONSE_HEADER_LOCATION); + // Assert that the response is a redirect to the register page with an auth failure. + Assert.assertTrue(locationHeader.getValue().contains("authFailure=true")); + EntityUtils.consume(response.getEntity()); + } + + @AfterClass(alwaysRun = true) + public void testClear() throws Exception { + + organizationDiscoveryConfigRestClient.deleteOrganizationDiscoveryConfig(); + deleteApp(application.getId()); + orgMgtRestClient.deleteOrganization(subOrgId); + String revertEmailAsUsernameClaimRequestBody = + RESTTestBase.readResource(REVERT_EMAIL_AS_USERNAME_CLAIM_JSON, this.getClass()); + claimManagementRestClient.updateClaim( + LOCAL_CLAIM_DIALECT, USERNAME_CLAIM_ID, revertEmailAsUsernameClaimRequestBody); + + serverConfigurationManager.restoreToLastConfiguration(false); + organizationDiscoveryConfigRestClient.closeHttpClient(); + claimManagementRestClient.closeHttpClient(); + oAuth2RestClient.closeHttpClient(); + orgMgtRestClient.closeHttpClient(); + client.close(); + } + + private void applyEmailAsUsernameConfig() throws Exception { + + String carbonHome = Utils.getResidentCarbonHome(); + File defaultConfigFile = getDeploymentTomlFile(carbonHome); + File emailAsUsernameConfigFile = new File(getISResourceLocation() + File.separator + + "organizationDiscovery" + File.separator + EMAIL_AS_USERNAME_TOML); + serverConfigurationManager = new ServerConfigurationManager(isServer); + serverConfigurationManager.applyConfigurationWithoutRestart(emailAsUsernameConfigFile, defaultConfigFile, true); + serverConfigurationManager.restartGracefully(); + } + + private Map initiateAuthorizeRequest() throws Exception { + + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair("response_type", OAuth2Constant.OAUTH2_GRANT_TYPE_CODE)); + urlParameters.add(new BasicNameValuePair(OAUTH2_CLIENT_ID, application.getClientId())); + urlParameters.add(new BasicNameValuePair(REDIRECT_URI_NAME, OAuth2Constant.CALLBACK_URL)); + urlParameters.add(new BasicNameValuePair(SCOPE_PLAYGROUND_NAME, "internal_login")); + + HttpResponse response = sendPostRequestWithParameters(client, urlParameters, + getTenantQualifiedURL(AUTHORIZE_ENDPOINT_URL, tenantInfo.getDomain())); + + Header locationHeader = response.getFirstHeader(HTTP_RESPONSE_HEADER_LOCATION); + Assert.assertNotNull(locationHeader, "Location header is not present in the authorization response."); + EntityUtils.consume(response.getEntity()); + + return extractQueryParams(locationHeader.getValue()); + } + + private HttpResponse initiateCommonAuthGet(Map loginPageQueryParams) throws Exception { + + Map queryParams = new HashMap<>(); + queryParams.put(IDP, SSO); + queryParams.put(SESSION_DATA_KEY, loginPageQueryParams.get(SESSION_DATA_KEY)); + queryParams.put(AUTHENTICATOR, ORGANIZATION_AUTHENTICATOR); + queryParams.put("isSelfRegistration", "true"); + + return sendGetRequest(client, getTenantQualifiedURL(COMMON_AUTH_URL, tenantInfo.getDomain()) + + "?" + buildQueryString(queryParams)); + } + + private HttpResponse initiateCommonAuthPost(Map loginPageQueryParams, String loginHint) + throws Exception { + + List urlParams = new ArrayList<>(); + urlParams.add(new BasicNameValuePair(LOGIN_HINT, loginHint)); + urlParams.add(new BasicNameValuePair(SESSION_DATA_KEY, loginPageQueryParams.get(SESSION_DATA_KEY))); + urlParams.add(new BasicNameValuePair(AUTHENTICATOR, ORGANIZATION_AUTHENTICATOR)); + urlParams.add(new BasicNameValuePair(IDP, SSO)); + + return sendPostRequestWithParameters(client, urlParams, + getTenantQualifiedURL(COMMON_AUTH_URL, tenantInfo.getDomain())); + } + + private Map extractQueryParams(String url) throws Exception { + + Map queryParams = new HashMap<>(); + List params = URLEncodedUtils.parse(new URI(url), StandardCharsets.UTF_8); + for (NameValuePair param : params) { + queryParams.put(param.getName(), param.getValue()); + } + + return queryParams; + } + + private String buildQueryString(Map queryParams) throws Exception { + + StringBuilder queryString = new StringBuilder(); + for (Map.Entry entry : queryParams.entrySet()) { + if (queryString.length() > 0) { + queryString.append("&"); + } + queryString.append(URLEncoder.encode(entry.getKey(), "UTF-8")) + .append("=") + .append(URLEncoder.encode(entry.getValue(), "UTF-8")); + } + + return queryString.toString(); + } + + /** + * This method parses the user registration page content and checks the values of three specific variables in the + * page. If all three variables are true, it will return true indicating that the page will redirect to the domain + * discovery page. + * @param pageContent The content of the registration page. + * @return true if the page content will redirect to the domain discovery page. + */ + private boolean willRedirectToDomainDiscovery(String pageContent) { + + Document page = Jsoup.parse(pageContent); + Elements scriptElements = page.select("script"); + + Map patterns = Map.of( + "isSSOLoginAuthenticatorConfigured", + Pattern.compile("var isSSOLoginAuthenticatorConfigured = JSON.parse\\((.*?)\\);"), + "emailDomainDiscoveryEnabled", + Pattern.compile("var emailDomainDiscoveryEnabled = JSON.parse\\((.*?)\\);"), + "emailDomainBasedSelfSignupEnabled", + Pattern.compile("var emailDomainBasedSelfSignupEnabled = JSON.parse\\((.*?)\\);") + ); + + boolean isSSOLoginAuthenticatorConfigured = false; + boolean emailDomainDiscoveryEnabled = false; + boolean emailDomainBasedSelfSignupEnabled = false; + + for (Element script : scriptElements) { + // Skip if the script is not inline. + if (!script.hasAttr("src")) { + String jsContent = script.html(); + + for (Map.Entry entry : patterns.entrySet()) { + String variableName = entry.getKey(); + Pattern pattern = entry.getValue(); + Matcher matcher = pattern.matcher(jsContent); + + if (matcher.find() && "true".equals(matcher.group(1))) { + switch (variableName) { + case "isSSOLoginAuthenticatorConfigured": + isSSOLoginAuthenticatorConfigured = true; + break; + case "emailDomainDiscoveryEnabled": + emailDomainDiscoveryEnabled = true; + break; + case "emailDomainBasedSelfSignupEnabled": + emailDomainBasedSelfSignupEnabled = true; + break; + } + } + } + } + } + + return isSSOLoginAuthenticatorConfigured && emailDomainDiscoveryEnabled && emailDomainBasedSelfSignupEnabled; + } +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/ClaimManagementRestClient.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/ClaimManagementRestClient.java index b48eb0dbe98..e30a2c84189 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/ClaimManagementRestClient.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/ClaimManagementRestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * Copyright (c) 2023-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 @@ -111,6 +111,25 @@ public void deleteExternalClaim(String dialectId, String claimId) throws IOExcep } } + /** + * Update the claim referenced by the provided id. + * + * @param dialectId Claim dialect id. + * @param claimId Claim id. + * @param requestBody Request body. + */ + public void updateClaim(String dialectId, String claimId, String requestBody) { + + String endPointUrl = serverBasePath + CLAIM_DIALECTS_ENDPOINT_URI + PATH_SEPARATOR + dialectId + + CLAIMS_ENDPOINT_URI + PATH_SEPARATOR + claimId; + try (CloseableHttpResponse response = getResponseOfHttpPut(endPointUrl, requestBody, getHeaders())) { + Assert.assertEquals(response.getStatusLine().getStatusCode(), HttpServletResponse.SC_OK, + "Claim update failed"); + } catch (IOException e) { + Assert.fail("Error occurred while updating the claim."); + } + } + private Header[] getHeaders() { Header[] headerList = new Header[2]; diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java new file mode 100644 index 00000000000..c31b0c1504d --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java @@ -0,0 +1,114 @@ +/* + * 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.restclients; + +import io.restassured.http.ContentType; +import org.apache.commons.codec.binary.Base64; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.message.BasicHeader; +import org.testng.Assert; +import org.wso2.carbon.automation.engine.context.beans.Tenant; +import org.wso2.identity.integration.common.utils.ISIntegrationTest; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +public class OrgDiscoveryConfigRestClient extends RestBaseClient { + + private static final String API_BASE_PATH = "api/server/v1"; + private static final String ORGANIZATION_CONFIG_PATH = "/organization-configs"; + private static final String ORGANIZATIONS_PATH = "/organizations"; + private static final String DISCOVERY_PATH = "/discovery"; + + private final String username; + private final String password; + private final String serverBasePath; + + public OrgDiscoveryConfigRestClient(String backendURL, Tenant tenantInfo) { + + this.username = tenantInfo.getContextUser().getUserName(); + this.password = tenantInfo.getContextUser().getPassword(); + + String tenantDomain = tenantInfo.getContextUser().getUserDomain(); + this.serverBasePath = backendURL + ISIntegrationTest.getTenantedRelativePath(API_BASE_PATH, tenantDomain); + } + + /** + * Add organization discovery config to the root organization. + * + * @param requestBody Request body. + */ + public void addOrganizationDiscoveryConfig(String requestBody) { + + try (CloseableHttpResponse httpResponse = getResponseOfHttpPost( + serverBasePath + ORGANIZATION_CONFIG_PATH + DISCOVERY_PATH, requestBody, getHeaders())) { + Assert.assertEquals(httpResponse.getStatusLine().getStatusCode(), HttpServletResponse.SC_CREATED, + "Failed to add organization discovery config."); + } catch (IOException e) { + throw new RuntimeException("Error occurred while adding organization discovery config.", e); + } + } + + /** + * Delete organization discovery config of the root organization. + */ + public void deleteOrganizationDiscoveryConfig() { + + try (CloseableHttpResponse httpResponse = getResponseOfHttpDelete( + serverBasePath + ORGANIZATION_CONFIG_PATH + DISCOVERY_PATH, getHeaders())) { + Assert.assertEquals(httpResponse.getStatusLine().getStatusCode(), HttpServletResponse.SC_NO_CONTENT, + "Failed to delete organization discovery config."); + } catch (IOException e) { + throw new RuntimeException("Error occurred while deleting organization discovery config.", e); + } + } + + /** + * Map discovery attributes to an organization. + * + * @param orgId Organization ID. + * @param requestBody Request body. + */ + public void mapDiscoveryAttributes(String orgId, String requestBody) { + + String endpointUrl = serverBasePath + ORGANIZATIONS_PATH + PATH_SEPARATOR + orgId + DISCOVERY_PATH; + try (CloseableHttpResponse httpResponse = getResponseOfHttpPut(endpointUrl, requestBody, getHeaders())) { + Assert.assertEquals(httpResponse.getStatusLine().getStatusCode(), HttpServletResponse.SC_OK, + "Failed to map discovery attributes."); + } catch (IOException e) { + throw new RuntimeException("Error occurred while mapping discovery attributes.", e); + } + } + + public void closeHttpClient() throws IOException { + + client.close(); + } + + private Header[] getHeaders() { + + return new Header[]{ + new BasicHeader(CONTENT_TYPE_ATTRIBUTE, String.valueOf(ContentType.JSON)), + new BasicHeader(AUTHORIZATION_ATTRIBUTE, BASIC_AUTHORIZATION_ATTRIBUTE + + Base64.encodeBase64String((username + ":" + password).getBytes()).trim()) + }; + } +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/organizationDiscovery/email_as_username.toml b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/organizationDiscovery/email_as_username.toml new file mode 100644 index 00000000000..f94aa62124d --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/artifacts/IS/organizationDiscovery/email_as_username.toml @@ -0,0 +1,46 @@ +[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" +type="PKCS12" + +[truststore] +file_name="client-truststore.p12" +password="wso2carbon" +type="PKCS12" + +[account_recovery.endpoint.auth] +hash= "66cd9688a2ae068244ea01e70f0e230f5623b7fa4cdecb65070a09ec06452262" + +[identity.auth_framework.endpoint] +app_password= "dashboard" + +[tenant_mgt] +enable_email_domain= true + +[identity_mgt.user_self_registration] +allow_self_registration = true diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/email_as_username_request.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/email_as_username_request.json new file mode 100644 index 00000000000..94a1828d393 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/email_as_username_request.json @@ -0,0 +1,15 @@ +{ + "claimURI": "http://wso2.org/claims/username", + "description": "Username", + "displayOrder": 0, + "displayName": "Username", + "readOnly": false, + "required": false, + "supportedByDefault": false, + "attributeMapping": [ + { + "mappedAttribute": "mail", + "userstore": "PRIMARY" + } + ] +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/enable_email_domain_org_discovery.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/enable_email_domain_org_discovery.json new file mode 100644 index 00000000000..78b735f9538 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/enable_email_domain_org_discovery.json @@ -0,0 +1,12 @@ +{ + "properties": [ + { + "key": "emailDomain.enable", + "value": "true" + }, + { + "key": "emailDomainBasedSelfSignup.enable", + "value": "true" + } + ] +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/map_email_domain_to_sub_org.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/map_email_domain_to_sub_org.json new file mode 100644 index 00000000000..970d61aaf25 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/map_email_domain_to_sub_org.json @@ -0,0 +1,10 @@ +{ + "attributes": [ + { + "type": "emailDomain", + "values": [ + "wso2.com" + ] + } + ] +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/organization-onboarding-apis.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/organization-onboarding-apis.json new file mode 100644 index 00000000000..98d2b1996d3 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/organization-onboarding-apis.json @@ -0,0 +1,7 @@ +{ + "/api/server/v1/organizations": [ + "internal_organization_view", + "internal_organization_create", + "internal_organization_delete" + ] +} diff --git a/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/revert_email_as_username_request.json b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/revert_email_as_username_request.json new file mode 100644 index 00000000000..66da1b27fe4 --- /dev/null +++ b/modules/integration/tests-integration/tests-backend/src/test/resources/org/wso2/identity/integration/test/organizationDiscovery/revert_email_as_username_request.json @@ -0,0 +1,15 @@ +{ + "claimURI": "http://wso2.org/claims/username", + "description": "Username", + "displayOrder": 0, + "displayName": "Username", + "readOnly": false, + "required": false, + "supportedByDefault": false, + "attributeMapping": [ + { + "mappedAttribute": "uid", + "userstore": "PRIMARY" + } + ] +} 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 c1cfe030e82..20df6841630 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 @@ -428,6 +428,7 @@ + From b533666cdce36a614284a45a75976af9bd1d1b0e Mon Sep 17 00:00:00 2001 From: Yasasr1 Date: Mon, 9 Dec 2024 09:15:14 +0530 Subject: [PATCH 2/4] Address review comments. --- .../OrganizationDiscoveryTestCase.java | 12 ++++++++++++ .../restclients/OrgDiscoveryConfigRestClient.java | 3 +++ 2 files changed, 15 insertions(+) diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java index 4f432582b56..8a025c65573 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java @@ -76,6 +76,9 @@ import static org.wso2.identity.integration.test.utils.OAuth2Constant.SCOPE_PLAYGROUND_NAME; import static org.wso2.identity.integration.test.utils.OAuth2Constant.SESSION_DATA_KEY; +/** + * This class contains the tests for the organization discovery feature with email domain based organization discovery. + */ public class OrganizationDiscoveryTestCase extends OAuth2ServiceAbstractIntegrationTest { private static final String EMAIL_AS_USERNAME_TOML = "email_as_username.toml"; @@ -329,6 +332,10 @@ private Map extractQueryParams(String url) throws Exception { Map queryParams = new HashMap<>(); List params = URLEncodedUtils.parse(new URI(url), StandardCharsets.UTF_8); + if (params.isEmpty()) { + return queryParams; + } + for (NameValuePair param : params) { queryParams.put(param.getName(), param.getValue()); } @@ -338,6 +345,10 @@ private Map extractQueryParams(String url) throws Exception { private String buildQueryString(Map queryParams) throws Exception { + if (queryParams.isEmpty()) { + return ""; + } + StringBuilder queryString = new StringBuilder(); for (Map.Entry entry : queryParams.entrySet()) { if (queryString.length() > 0) { @@ -355,6 +366,7 @@ private String buildQueryString(Map queryParams) throws Exceptio * This method parses the user registration page content and checks the values of three specific variables in the * page. If all three variables are true, it will return true indicating that the page will redirect to the domain * discovery page. + * * @param pageContent The content of the registration page. * @return true if the page content will redirect to the domain discovery page. */ diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java index c31b0c1504d..7ab83798813 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/restclients/OrgDiscoveryConfigRestClient.java @@ -31,6 +31,9 @@ import javax.servlet.http.HttpServletResponse; +/** + * Rest client for organization discovery configuration management API. + */ public class OrgDiscoveryConfigRestClient extends RestBaseClient { private static final String API_BASE_PATH = "api/server/v1"; From 48a3d31f130bc65eb456ad1ae2d30be8861ad1d1 Mon Sep 17 00:00:00 2001 From: Yasasr1 Date: Tue, 10 Dec 2024 09:38:51 +0530 Subject: [PATCH 3/4] Fix test failure. --- .../OrganizationDiscoveryTestCase.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java index 8a025c65573..9177f9e238b 100644 --- a/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java +++ b/modules/integration/tests-integration/tests-backend/src/test/java/org/wso2/identity/integration/test/organizationDiscovery/OrganizationDiscoveryTestCase.java @@ -45,6 +45,7 @@ import org.wso2.carbon.integration.common.utils.mgt.ServerConfigurationManager; import org.wso2.identity.integration.test.oauth2.OAuth2ServiceAbstractIntegrationTest; import org.wso2.identity.integration.test.oauth2.dataprovider.model.ApplicationConfig; +import org.wso2.identity.integration.test.oauth2.dataprovider.model.UserClaimConfig; import org.wso2.identity.integration.test.rest.api.common.RESTTestBase; 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.ApplicationSharePOSTRequest; @@ -106,7 +107,7 @@ public class OrganizationDiscoveryTestCase extends OAuth2ServiceAbstractIntegrat private OAuth2RestClient oAuth2RestClient; private OrgMgtRestClient orgMgtRestClient; - @BeforeClass(alwaysRun = true) + @BeforeClass(dependsOnGroups = "wso2.is", alwaysRun = true) public void testInit() throws Exception { super.init(); @@ -131,8 +132,13 @@ protected boolean isRedirectable(String method) { } }).build(); + List userClaimConfigs = Collections.singletonList( + new UserClaimConfig.Builder().localClaimUri("http://wso2.org/claims/emailaddress").oidcClaimUri("email") + .build()); + // Create an application. ApplicationConfig applicationConfig = new ApplicationConfig.Builder() + .claimsList(userClaimConfigs) .grantTypes(new ArrayList<>(Collections.singleton(OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE))) .tokenType(ApplicationConfig.TokenType.OPAQUE) .expiryTime(3600) @@ -165,7 +171,7 @@ protected boolean isRedirectable(String method) { organizationDiscoveryConfigRestClient.mapDiscoveryAttributes(subOrgId, mapEmailDomainRequestBody); } - @Test(description = "Test email domain based organization discovery not initiated " + + @Test(dependsOnGroups = "wso2.is", description = "Test email domain based organization discovery not initiated " + "for unshared app", priority = 1) public void testSelfRegistrationOrgDiscoveryForUnsharedApp() throws Exception { @@ -182,7 +188,9 @@ public void testSelfRegistrationOrgDiscoveryForUnsharedApp() throws Exception { "Should not be redirected to domain discovery page for an unshared app."); } - @Test(description = "Test email domain based organization discovery for self-registration", priority = 2) + @Test(dependsOnGroups = "wso2.is", dependsOnMethods = "testSelfRegistrationOrgDiscoveryForUnsharedApp", + description = "Test email domain based organization discovery for self-registration", + priority = 2) public void testSelfRegistrationOrgDiscovery() throws Exception { // Share the app @@ -222,8 +230,9 @@ public void testSelfRegistrationOrgDiscovery() throws Exception { EntityUtils.consume(response.getEntity()); } - @Test(description = "Test email domain based organization discovery for self-registration with an " + - "invalid email domain", priority = 3) + @Test(dependsOnGroups = "wso2.is", + description = "Test email domain based organization discovery for self-registration with an " + + "invalid email domain", priority = 3) public void testSelfRegistrationOrgDiscoveryWithInvalidEmail() throws Exception { // Initiate the authorize request to get the login page and retrieve the session data key. @@ -255,8 +264,8 @@ public void testSelfRegistrationOrgDiscoveryWithInvalidEmail() throws Exception EntityUtils.consume(response.getEntity()); } - @AfterClass(alwaysRun = true) - public void testClear() throws Exception { + @AfterClass(dependsOnGroups = "wso2.is", alwaysRun = true) + public void atEnd() throws Exception { organizationDiscoveryConfigRestClient.deleteOrganizationDiscoveryConfig(); deleteApp(application.getId()); From fff69161f67c79e29e1b42ed9f880fe282783b38 Mon Sep 17 00:00:00 2001 From: Yasasr1 Date: Wed, 18 Dec 2024 08:28:39 +0530 Subject: [PATCH 4/4] Fix test failure. --- .../tests-backend/src/test/resources/testng.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7ff3126ea08..f7c7b180272 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 @@ -71,6 +71,7 @@ + @@ -407,7 +408,6 @@ -