Skip to content

Commit

Permalink
Add support for login_hint for param in organization discovery.
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaura committed Aug 21, 2024
1 parent 5022ccf commit 8016383
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_ORGANIZATIONS_BY_NAME;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_ORGANIZATION_NAME_BY_ID;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ERROR_VALIDATING_ORGANIZATION_DISCOVERY_ATTRIBUTE;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ERROR_VALIDATING_ORGANIZATION_LOGIN_HINT_ATTRIBUTE;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_INVALID_APPLICATION;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_INVALID_ORGANIZATION_ID;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ORGANIZATION_NOT_FOUND_FOR_TENANT;
Expand All @@ -168,6 +169,7 @@ public class OrganizationAuthenticator extends OpenIDConnectAuthenticator {
"</form>\n" +
"</body>\n" +
"</html>";
private static final String EMAIL_DOMAIN_DISCOVERY_TYPE = "emailDomain";

@Override
public String getFriendlyName() {
Expand Down Expand Up @@ -407,6 +409,13 @@ public AuthenticatorFlowStatus process(HttpServletRequest request, HttpServletRe
context.setProperty(ORG_ID_PARAMETER, organizationId);
String organizationName = getOrganizationNameById(organizationId);
context.setProperty(ORG_PARAMETER, organizationName);
} else if (request.getParameterMap().containsKey(LOGIN_HINT_PARAMETER)) {
String loginHint = request.getParameter(LOGIN_HINT_PARAMETER);
context.setProperty(ORG_DISCOVERY_PARAMETER, loginHint);
if (!validateLoginHintAttributeValue(loginHint, context, request, response)) {
context.removeProperty(ORG_DISCOVERY_PARAMETER);
return AuthenticatorFlowStatus.INCOMPLETE;
}
} else if (request.getParameterMap().containsKey(ORG_DISCOVERY_PARAMETER)) {
String discoveryInput = request.getParameter(ORG_DISCOVERY_PARAMETER);
context.setProperty(ORG_DISCOVERY_PARAMETER, discoveryInput);
Expand Down Expand Up @@ -519,6 +528,49 @@ private boolean validateOrganizationName(String organizationName, Authentication
return false;
}

/**
* Validates given login_hint parameter.
*
* @param loginHintInput Given login_hint parameter value.
* @param request Servlet request.
* @param response Servlet response.
* @param context Authentication context.
* @return True if the login_hint parameter is valid.
* @throws AuthenticationFailedException If an error occurs while validating login_hint parameter.
*/
private boolean validateLoginHintAttributeValue(String loginHintInput, AuthenticationContext context,
HttpServletRequest request, HttpServletResponse response)
throws AuthenticationFailedException {

// Default discovery type is set to `emailDomain`.
String discoveryType = EMAIL_DOMAIN_DISCOVERY_TYPE;
if (request.getParameterMap().containsKey(ORGANIZATION_DISCOVERY_TYPE)) {
discoveryType = request.getParameter(ORGANIZATION_DISCOVERY_TYPE);
}

if (!isOrganizationDiscoveryTypeEnabled(discoveryType)) {
context.setProperty(ORGANIZATION_LOGIN_FAILURE, "Organization discovery type is invalid or not enabled");
redirectToOrgDiscoveryInputCapture(response, context);
return false;
}
context.setProperty(ORGANIZATION_DISCOVERY_TYPE, discoveryType);

try {
String appResideOrgId = getOrgIdByTenantDomain(context.getLoginTenantDomain());
String organizationId = getOrganizationDiscoveryManager().getOrganizationIdByDiscoveryAttribute
(discoveryType, loginHintInput, appResideOrgId);
if (StringUtils.isNotBlank(organizationId)) {
context.setProperty(ORG_ID_PARAMETER, organizationId);
return true;
}
context.setProperty(ORGANIZATION_LOGIN_FAILURE, "Can't identify organization");
redirectToOrgDiscoveryInputCapture(response, context);
return false;
} catch (OrganizationManagementException e) {
throw handleAuthFailures(ERROR_CODE_ERROR_VALIDATING_ORGANIZATION_LOGIN_HINT_ATTRIBUTE, e);
}
}

private boolean validateDiscoveryAttributeValue(String discoveryInput, AuthenticationContext context,
HttpServletResponse response) throws AuthenticationFailedException {

Expand Down Expand Up @@ -650,6 +702,38 @@ private String getQueryParams(AuthenticationContext context, ClaimMapping[] clai
return paramBuilder.toString();
}

/**
* Check whether the given organization discovery type is enabled.
*
* @param discoveryType Organization discovery type.
* @return True if the organization discovery type is enabled.
* @throws AuthenticationFailedException If an error occurs while checking the organization discovery type.
*/
private boolean isOrganizationDiscoveryTypeEnabled(String discoveryType)
throws AuthenticationFailedException {

try {
DiscoveryConfig discoveryConfig = getOrganizationConfigManager().getDiscoveryConfiguration();
Map<String, AttributeBasedOrganizationDiscoveryHandler> discoveryHandlers =
getOrganizationDiscoveryManager().getAttributeBasedOrganizationDiscoveryHandlers();

List<ConfigProperty> configProperties = discoveryConfig.getConfigProperties();
for (ConfigProperty configProperty : configProperties) {
String type = configProperty.getKey().split(ENABLE_CONFIG)[0];
if (discoveryType.equals(type) && discoveryHandlers.get(type) != null &&
Boolean.parseBoolean(configProperty.getValue())) {
return true;
}
}
} catch (OrganizationConfigException e) {
if (ERROR_CODE_DISCOVERY_CONFIG_NOT_EXIST.getCode().equals(e.getErrorCode())) {
return false;
}
throw handleAuthFailures(ERROR_CODE_ERROR_GETTING_ORGANIZATION_DISCOVERY_CONFIG, e);
}
return false;
}

private boolean isOrganizationDiscoveryEnabled(AuthenticationContext context) throws AuthenticationFailedException {

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

package org.wso2.carbon.identity.application.authenticator.organization.login;

import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.wso2.carbon.base.CarbonBaseConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
Expand All @@ -43,7 +45,10 @@
import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl;
import org.wso2.carbon.identity.oauth.dto.OAuthConsumerAppDTO;
import org.wso2.carbon.identity.organization.config.service.OrganizationConfigManager;
import org.wso2.carbon.identity.organization.config.service.model.ConfigProperty;
import org.wso2.carbon.identity.organization.config.service.model.DiscoveryConfig;
import org.wso2.carbon.identity.organization.discovery.service.AttributeBasedOrganizationDiscoveryHandler;
import org.wso2.carbon.identity.organization.discovery.service.OrganizationDiscoveryManager;
import org.wso2.carbon.identity.organization.management.application.OrgApplicationManager;
import org.wso2.carbon.identity.organization.management.service.OrganizationManager;
import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException;
Expand All @@ -57,6 +62,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -69,11 +75,15 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_ID;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.AUTHENTICATOR_FRIENDLY_NAME;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.AUTHENTICATOR_NAME;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.ENABLE_CONFIG;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.INBOUND_AUTH_TYPE_OAUTH;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.LOGIN_HINT_PARAMETER;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.OIDC_CLAIM_DIALECT_URL;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.ORGANIZATION_DISCOVERY_TYPE;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.ORG_ID_PARAMETER;
import static org.wso2.carbon.identity.application.authenticator.organization.login.constant.AuthenticatorConstants.ORG_PARAMETER;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_APPLICATION_NOT_SHARED;
Expand All @@ -100,6 +110,11 @@ public class OrganizationAuthenticatorTest {
private static final String clientId = "3_TCRZ93rTQtPL8k02_trEYTfVca";
private static final String secretKey = "uW4q6dYgSaHJIv11Llqi1nvOQBUa";

private static final String emailDomainDiscoveryType = "emailDomain";
private static final String invalidDiscoveryType = "invalidDiscoveryType";
private static final String userEmailWithValidDomain = "[email protected]";
private static final String userEmailWithInvalidDomain = "[email protected]";

private static Map<String, String> authenticatorParamProperties;
private static Map<String, String> authenticatorProperties;
private static Map<String, Object> mockContextParam;
Expand All @@ -126,6 +141,9 @@ public class OrganizationAuthenticatorTest {
private DiscoveryConfig mockDiscoveryConfig;
private MockedStatic<IdentityTenantUtil> mockedUtilities;

@Mock
private OrganizationDiscoveryManager mockOrganizationDiscoveryManager;

@BeforeClass
public void setUp() {

Expand All @@ -136,6 +154,7 @@ public void setUp() {
@BeforeMethod
public void init() throws UserStoreException {

initMocks(this);
mockServletRequest = mock(HttpServletRequest.class);
mockServletResponse = mock(HttpServletResponse.class);
mockAuthenticationContext = mock(AuthenticationContext.class);
Expand Down Expand Up @@ -167,6 +186,7 @@ public void init() throws UserStoreException {
authenticatorDataHolder.setApplicationManagementService(mockApplicationManagementService);
authenticatorDataHolder.setClaimMetadataManagementService(mockClaimMetadataManagementService);
authenticatorDataHolder.setOrganizationConfigManager(mockOrganizationConfigManager);
authenticatorDataHolder.setOrganizationDiscoveryManager(mockOrganizationDiscoveryManager);
Tenant tenant = mock(Tenant.class);
TenantManager mockTenantManager = mock(TenantManager.class);
when(mockRealmService.getTenantManager()).thenReturn(mockTenantManager);
Expand Down Expand Up @@ -444,6 +464,60 @@ public void testInitiateAuthenticationRequestInvalidSharedApp() throws Exception
mockAuthenticationContext);
}

@DataProvider(name = "invalidOrgDiscoveryParams")
public Object[][] getInvalidOrgDiscoveryParams() {

return new Object[][]{
// When the given discovery type is not valid.
{userEmailWithValidDomain, invalidDiscoveryType, new ArrayList<>(Collections.singletonList(
new ConfigProperty(emailDomainDiscoveryType + ENABLE_CONFIG, "true")))},
// When the given discovery type is valid but not enabled.
{userEmailWithValidDomain, emailDomainDiscoveryType, new ArrayList<>(Collections.singletonList(
new ConfigProperty(emailDomainDiscoveryType + ENABLE_CONFIG, "false")))},
// When the given email domain of the user email is invalid.
{userEmailWithInvalidDomain, emailDomainDiscoveryType, new ArrayList<>(Collections.singletonList(
new ConfigProperty(emailDomainDiscoveryType + ENABLE_CONFIG, "true")))}
};
}

@Test(dataProvider = "invalidOrgDiscoveryParams")
public void testProcessWithInvalidOrgDiscoveryParam(String userEmail, String discoveryType,
List<ConfigProperty> configProperties) throws Exception {

Map<String, String[]> mockParamMap = new HashMap<>();
mockParamMap.put(LOGIN_HINT_PARAMETER, new String[]{userEmail});
mockParamMap.put(ORGANIZATION_DISCOVERY_TYPE, new String[]{discoveryType});
when(mockServletRequest.getParameterMap()).thenReturn(mockParamMap);
when(mockServletRequest.getParameter(LOGIN_HINT_PARAMETER)).thenReturn(userEmail);
when(mockServletRequest.getParameter(ORGANIZATION_DISCOVERY_TYPE)).thenReturn(discoveryType);

when(authenticatorDataHolder.getOrganizationConfigManager().getDiscoveryConfiguration())
.thenReturn(mockDiscoveryConfig);
when(mockDiscoveryConfig.getConfigProperties()).thenReturn(configProperties);

Map<String, AttributeBasedOrganizationDiscoveryHandler> discoveryHandlers = new HashMap<>();
AttributeBasedOrganizationDiscoveryHandler discoveryHandler =
mock(AttributeBasedOrganizationDiscoveryHandler.class);
discoveryHandlers.put(emailDomainDiscoveryType, discoveryHandler);
when(authenticatorDataHolder.getOrganizationDiscoveryManager().getAttributeBasedOrganizationDiscoveryHandlers())
.thenReturn(discoveryHandlers);

when(mockAuthenticationContext.getLoginTenantDomain()).thenReturn(saasAppOwnedTenant);
when(authenticatorDataHolder.getOrganizationManager().resolveOrganizationId(saasAppOwnedTenant)).thenReturn(
saasAppOwnedOrgId);
when(authenticatorDataHolder.getOrganizationDiscoveryManager()
.getOrganizationIdByDiscoveryAttribute(discoveryType, userEmail, saasAppOwnedOrgId)).thenReturn(null);

when(mockAuthenticationContext.getContextIdentifier()).thenReturn(contextIdentifier);
when(mockAuthenticationContext.getExternalIdP()).thenReturn(mockExternalIdPConfig);
when(mockAuthenticationContext.getServiceProviderResourceId()).thenReturn(saasAppResourceId);
when(mockExternalIdPConfig.getName()).thenReturn(AUTHENTICATOR_FRIENDLY_NAME);

AuthenticatorFlowStatus status = organizationAuthenticator.process(mockServletRequest, mockServletResponse,
mockAuthenticationContext);
Assert.assertEquals(status, AuthenticatorFlowStatus.INCOMPLETE);
}

private void setMockContextParamForValidOrganization() {

mockContextParam.put(ORG_PARAMETER, org);
Expand Down

0 comments on commit 8016383

Please sign in to comment.