Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a config to autofill the username with subject attribute while jit provisioning #6225

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,10 @@ private void redirectToAccountCreateUI(ExternalIdPConfig externalIdPConfig, Auth
}
uriBuilder.addParameter(FrameworkConstants.SERVICE_PROVIDER, context.getSequenceConfig()
.getApplicationConfig().getApplicationName());
uriBuilder.addParameter(FrameworkConstants.USERNAME, username);
if (!externalIdPConfig.isModifyUserNameAllowed() || (externalIdPConfig.isModifyUserNameAllowed() &&
FrameworkUtils.isUsernameFieldAutofillWithSubjectAttr())) {
uriBuilder.addParameter(FrameworkConstants.USERNAME, username);
}
uriBuilder.addParameter(FrameworkConstants.SKIP_SIGN_UP_ENABLE_CHECK, String.valueOf(true));
uriBuilder.addParameter(FrameworkConstants.SESSION_DATA_KEY, context.getContextIdentifier());
addMissingClaims(uriBuilder, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3014,6 +3014,19 @@ public static String getUserNameProvisioningUIUrl() {
return userNamePrvisioningUrl;
}

/**
* Checks if the username field should be autofilled with the subject attribute
* during Just-In-Time (JIT) provisioning with prompt for username, password, and consent.
*
* @return true if the username field should be autofilled with the
* subject attribute; false otherwise.
*/
public static boolean isUsernameFieldAutofillWithSubjectAttr() {

return Boolean.parseBoolean(
IdentityUtil.getProperty("JITProvisioning.AutofillUsernameFieldWithSubjectAttribute"));
}

/**
* This method determines whether username pattern validation should be skipped for JIT provisioning users based
* on the configuration file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@

package org.wso2.carbon.identity.application.authentication.framework.handler.request.impl;

import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.engine.AxisConfiguration;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.AbstractFrameworkTest;
import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.FederatedApplicationAuthenticator;
Expand All @@ -46,16 +51,21 @@
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
import org.wso2.carbon.identity.application.common.model.JustInTimeProvisioningConfig;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.core.internal.IdentityCoreServiceComponent;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.FederatedAssociationManager;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.FederatedAssociationManagerImpl;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.exception.FederatedAssociationManagerException;
import org.wso2.carbon.idp.mgt.IdentityProviderManagementException;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.ConfigurationContextService;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

Expand All @@ -70,6 +80,8 @@
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* This is a test class for {@link JITProvisioningPostAuthenticationHandler}.
Expand All @@ -88,6 +100,8 @@ public class JITProvisioningPostAuthenticationHandlerTest extends AbstractFramew

private MockedStatic<FrameworkUtils> frameworkUtils;
private MockedStatic<ConfigurationFacade> configurationFacade;
private MockedStatic<CarbonUtils> carbonUtils;
private MockedStatic<PrivilegedCarbonContext> privilegedCarbonContextMockedStatic;

@BeforeClass
protected void setupSuite() throws XMLStreamException, IdentityProviderManagementException {
Expand All @@ -111,12 +125,16 @@ protected void setupSuite() throws XMLStreamException, IdentityProviderManagemen
response = mock(HttpServletResponse.class);
postJITProvisioningHandler = JITProvisioningPostAuthenticationHandler.getInstance();
sp = getTestServiceProvider("default-sp-1.xml");
carbonUtils = mockStatic(CarbonUtils.class);
privilegedCarbonContextMockedStatic = mockStatic(PrivilegedCarbonContext.class);
}

@AfterClass
protected void cleanup() {
frameworkUtils.close();
configurationFacade.close();
carbonUtils.close();
privilegedCarbonContextMockedStatic.close();
}

@Test(description = "This test case tests the Post JIT provisioning handling flow without an authenticated user")
Expand Down Expand Up @@ -175,6 +193,83 @@ public void testHandleWithAuthenticatedUserWithFederatedIdp() throws FrameworkEx
}
}

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

return new Object[][] {
{false, true, true},
{true, false, false},
{true, true, true}
};
}

@Test(description = "This test case verifies that the username attribute auto-fill functionality works correctly"
+ " when username and password provisioning are enabled.", dataProvider = "usernameAutoFillDataProvider")
public void testUsernameAutoFillingFunctionality(boolean isUsernameModifiable, boolean isUsernameAutoFillEnabled,
boolean usernameShouldContainInURL)
throws FrameworkException, XMLStreamException, IdentityProviderManagementException, IOException {

try (MockedStatic<FrameworkServiceDataHolder> frameworkServiceDataHolder =
mockStatic(FrameworkServiceDataHolder.class);
MockedStatic<IdentityTenantUtil> identityTenantUtil = mockStatic(IdentityTenantUtil.class);
MockedStatic<IdentityCoreServiceComponent> identityCoreServiceComponentMockedStatic = mockStatic(
IdentityCoreServiceComponent.class)) {
frameworkServiceDataHolder.when(
FrameworkServiceDataHolder::getInstance).thenReturn(mockFrameworkServiceDataHolder);
AuthenticationContext context = processAndGetAuthenticationContext(sp, true, true);
FederatedAssociationManager federatedAssociationManager = mock(FederatedAssociationManagerImpl.class);
frameworkUtils.when(FrameworkUtils::getFederatedAssociationManager).thenReturn(federatedAssociationManager);
frameworkUtils.when(
FrameworkUtils::getStepBasedSequenceHandler)
.thenReturn(mock(StepBasedSequenceHandler.class));
frameworkUtils.when(() -> FrameworkUtils.getMissingClaims(any()))
.thenReturn(new String[] {"test-claim", "test-claim1"});
frameworkUtils.when(FrameworkUtils::isUsernameFieldAutofillWithSubjectAttr)
.thenReturn(isUsernameAutoFillEnabled);

identityTenantUtil.when(() -> IdentityTenantUtil.getTenantId(anyString())).thenReturn(1);

carbonUtils.when(() -> CarbonUtils.getTransportProxyPort(any(AxisConfiguration.class), any()))
.thenReturn(101010);
carbonUtils.when(CarbonUtils::getManagementTransport).thenReturn("https");

// Need to mock getIdPConfigByName with a null parameter.
ConfigurationFacade mockConfigurationFacade = mock(ConfigurationFacade.class);
configurationFacade.when(ConfigurationFacade::getInstance).thenReturn(mockConfigurationFacade);
IdentityProvider identityProvider = getTestIdentityProvider("default-tp-1.xml");
JustInTimeProvisioningConfig justInTimeProvisioningConfig =
identityProvider.getJustInTimeProvisioningConfig();
justInTimeProvisioningConfig.setPromptConsent(true);
justInTimeProvisioningConfig.setModifyUserNameAllowed(isUsernameModifiable);
identityProvider.setJustInTimeProvisioningConfig(justInTimeProvisioningConfig);
ExternalIdPConfig externalIdPConfig = new ExternalIdPConfig(identityProvider);
doReturn(externalIdPConfig).when(mockConfigurationFacade).getIdPConfigByName(eq(null), anyString());

ConfigurationContextService configurationContextService = mock(ConfigurationContextService.class);
ConfigurationContext configurationContext = mock(ConfigurationContext.class);
when(configurationContextService.getServerConfigContext()).thenReturn(configurationContext);
identityCoreServiceComponentMockedStatic.when(IdentityCoreServiceComponent::getConfigurationContextService)
.thenReturn(configurationContextService);

PrivilegedCarbonContext privilegedCarbonContext = mock(PrivilegedCarbonContext.class);
when(privilegedCarbonContext.getTenantDomain()).thenReturn("test-domain");
privilegedCarbonContextMockedStatic.when(PrivilegedCarbonContext::getThreadLocalCarbonContext)
.thenReturn(privilegedCarbonContext);

HttpServletResponse mockResponse = mock(HttpServletResponse.class);
postJITProvisioningHandler.handle(request, mockResponse, context);

ArgumentCaptor<String> uiRedirectionUrl = ArgumentCaptor.forClass(String.class);
verify(mockResponse).sendRedirect(uiRedirectionUrl.capture());
boolean urlContainsUsernameParam = uiRedirectionUrl.getValue().contains("username=test");
if (usernameShouldContainInURL) {
Assert.assertTrue(urlContainsUsernameParam);
} else {
Assert.assertFalse(urlContainsUsernameParam);
}
}
}

/**
* To get the authentication context and to call the handle method of the PostJitProvisioningHandler.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,15 @@
import javax.servlet.http.HttpServletResponse;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.REQUEST_PARAM_SP;
Expand Down Expand Up @@ -904,4 +906,21 @@ public void testGetUserIdClaimURI() throws Exception {
assertEquals(result, "http://wso2.org/claims/username");
}
}

@Test(description = "Verify that the username auto-fill configuration is retrieved correctly")
public void testGetUsernameFieldAutofillWithSubjectAttrConfig() {

try (MockedStatic<IdentityUtil> identityUtilMockedStatic = mockStatic(IdentityUtil.class)) {
identityUtilMockedStatic.when(
() -> IdentityUtil.getProperty(
eq("JITProvisioning.AutofillUsernameFieldWithSubjectAttribute")))
.thenReturn("true");
assertTrue(FrameworkUtils.isUsernameFieldAutofillWithSubjectAttr());
identityUtilMockedStatic.when(
() -> IdentityUtil.getProperty(
eq("JITProvisioning.AutofillUsernameFieldWithSubjectAttribute")))
.thenReturn("false");
assertFalse(FrameworkUtils.isUsernameFieldAutofillWithSubjectAttr());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,7 @@

<JITProvisioning>
<UserNameProvisioningUI>/accountrecoveryendpoint/register.do</UserNameProvisioningUI>
<AutofillUsernameFieldWithSubjectAttribute>true</AutofillUsernameFieldWithSubjectAttribute>
<PasswordProvisioningUI>/accountrecoveryendpoint/signup.do</PasswordProvisioningUI>
<FailAuthnOnProvisionFailure>false</FailAuthnOnProvisionFailure>
<EnableEnhancedFeature>false</EnableEnhancedFeature>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,7 @@

<JITProvisioning>
<UserNameProvisioningUI>{{authentication.jit_provisioning.username_provisioning_url}}</UserNameProvisioningUI>
<AutofillUsernameFieldWithSubjectAttribute>{{authentication.jit_provisioning.autofill_username_field_with_subject_attribute}}</AutofillUsernameFieldWithSubjectAttribute>
<PasswordProvisioningUI>{{authentication.jit_provisioning.password_provisioning_url}}</PasswordProvisioningUI>
<FailAuthnOnProvisionFailure>{{authentication.jit_provisioning.fail_authn_on_provision_failure}}</FailAuthnOnProvisionFailure>
<SkipUsernamePatternValidation>{{authentication.jit_provisioning.skip_username_pattern_validation}}</SkipUsernamePatternValidation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@

"authentication_policy.check_account_exist": true,
"authentication.jit_provisioning.username_provisioning_url": "/accountrecoveryendpoint/register.do",
"authentication.jit_provisioning.autofill_username_field_with_subject_attribute": true,
"authentication.jit_provisioning.password_provisioning_url": "/accountrecoveryendpoint/signup.do",
"authentication.jit_provisioning.skip_username_pattern_validation": false,
"authentication.jit_provisioning.fail_authn_on_provision_failure": false,
Expand Down
Loading