Skip to content

Commit

Permalink
Merge pull request #45 from dhaura/DP-add-login_hint-param
Browse files Browse the repository at this point in the history
Add Support for "login_hint" for Param in Organization Discovery
  • Loading branch information
dhaura authored Aug 22, 2024
2 parents 5022ccf + f18745a commit daa5c44
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 1 deletion.
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 context Authentication context.
* @param request Servlet request.
* @param response Servlet response.
* @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 @@ -69,11 +74,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 +109,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 +140,9 @@ public class OrganizationAuthenticatorTest {
private DiscoveryConfig mockDiscoveryConfig;
private MockedStatic<IdentityTenantUtil> mockedUtilities;

@Mock
private OrganizationDiscoveryManager mockOrganizationDiscoveryManager;

@BeforeClass
public void setUp() {

Expand All @@ -136,6 +153,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 +185,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 +463,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, true},
// When the given discovery type is valid but not enabled.
{userEmailWithValidDomain, emailDomainDiscoveryType, false},
// When the given email domain of the user email is invalid.
{userEmailWithInvalidDomain, emailDomainDiscoveryType, true}
};
}

@Test(dataProvider = "invalidOrgDiscoveryParams")
public void testProcessWithInvalidOrgDiscoveryParam(String userEmail, String discoveryType,
boolean isEmailDomainDiscoveryEnabled) 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);
List<ConfigProperty> configProperties = new ArrayList<>();
configProperties.add(new ConfigProperty(emailDomainDiscoveryType + ENABLE_CONFIG,
String.valueOf(isEmailDomainDiscoveryEnabled)));
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
<org.wso2.identity.organization.mgt.imp.pkg.version.range>[1.0.0,2.0.0)
</org.wso2.identity.organization.mgt.imp.pkg.version.range>

<carbon.identity.org.mgt.core.version>1.0.68</carbon.identity.org.mgt.core.version>
<carbon.identity.org.mgt.core.version>1.1.14</carbon.identity.org.mgt.core.version>
<org.wso2.identity.organization.mgt.core.imp.pkg.version.range>[1.0.0,2.0.0)
</org.wso2.identity.organization.mgt.core.imp.pkg.version.range>

Expand Down

0 comments on commit daa5c44

Please sign in to comment.