Skip to content

Commit

Permalink
Fix user name association in PostAuthAssociationHandler for configure…
Browse files Browse the repository at this point in the history
…d idp subject identifier (#6125)

* Honour the sub claim configured in IDP based on config

* Fix warnings

* Refactor code

* Add unit tests
  • Loading branch information
kayathiri4 authored Dec 11, 2024
1 parent 016a4ec commit 02ba530
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,13 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request,
localClaimValues = new HashMap<>();
}

String externalSubject = stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
externalSubject = FrameworkUtils.getExternalSubject(stepConfig, context.getTenantDomain());
}
String associatedLocalUser =
getLocalUserAssociatedForFederatedIdentifier(stepConfig.getAuthenticatedIdP(),
stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier(),
externalSubject,
context.getTenantDomain());
boolean isUserAllowsToLoginIdp = Boolean.parseBoolean(IdentityUtil
.getProperty(ALLOW_LOGIN_TO_IDP));
Expand Down Expand Up @@ -356,8 +360,7 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request,
FrameworkUtils.getFederatedAssociationManager()
.createFederatedAssociation(new User(user),
stepConfig.getAuthenticatedIdP(),
stepConfig.getAuthenticatedUser()
.getAuthenticatedSubjectIdentifier());
externalSubject);
associatedLocalUser = user.getDomainQualifiedUsername();
}
} catch (UserStoreException e) {
Expand Down Expand Up @@ -830,8 +833,11 @@ private void callDefaultProvisioningHandler(String username, AuthenticationConte
}
}

localClaimValues.put(FrameworkConstants.ASSOCIATED_ID,
stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier());
String externalSubject = stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
externalSubject = FrameworkUtils.getExternalSubject(stepConfig, context.getTenantDomain());
}
localClaimValues.put(FrameworkConstants.ASSOCIATED_ID, externalSubject);
localClaimValues.put(FrameworkConstants.IDP_ID, stepConfig.getAuthenticatedIdP());

/*
Expand Down Expand Up @@ -959,8 +965,12 @@ private void callDefaultProvisioningHandler(String username, AuthenticationConte
private void handleConsents(HttpServletRequest request, StepConfig stepConfig, String tenantDomain)
throws PostAuthenticationFailedException {

String externalSubject = stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
externalSubject = FrameworkUtils.getExternalSubject(stepConfig, tenantDomain);
}
String userName = getLocalUserAssociatedForFederatedIdentifier(stepConfig.getAuthenticatedIdP(),
stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier(), tenantDomain);
externalSubject, tenantDomain);
String consent = request.getParameter("consent");
String policyURL = request.getParameter("policy");
if (StringUtils.isNotEmpty(consent)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ private String getUserNameAssociatedWith(AuthenticationContext context, StepConf
String associatesUserName;
String originalExternalIdpSubjectValueForThisStep = stepConfig.getAuthenticatedUser()
.getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
originalExternalIdpSubjectValueForThisStep = FrameworkUtils.getExternalSubject(stepConfig,
context.getTenantDomain());
}
try {
FrameworkUtils.startTenantFlow(context.getTenantDomain());
FederatedAssociationManager federatedAssociationManager = FrameworkUtils.getFederatedAssociationManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ public abstract class FrameworkConstants {
public static final String IDP_RESOURCE_ID = "IDPResourceID";
public static final String ENABLE_JIT_PROVISION_ENHANCE_FEATURE = "JITProvisioning.EnableEnhancedFeature";
public static final String ERROR_CODE_INVALID_ATTRIBUTE_UPDATE = "SUO-10000";
public static final String ENABLE_CONFIGURED_IDP_SUB_FOR_FEDERATED_USER_ASSOCIATION
= "JITProvisioning.EnableConfiguredIdpSubForFederatedUserAssociation";

// Error details sent from authenticators
public static final String AUTH_ERROR_CODE = "AuthErrorCode";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;
import org.wso2.carbon.identity.application.authentication.framework.store.UserSessionStore;
import org.wso2.carbon.identity.application.common.model.Claim;
import org.wso2.carbon.identity.application.common.model.ClaimConfig;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig;
import org.wso2.carbon.identity.application.common.model.IdPGroup;
Expand Down Expand Up @@ -205,12 +206,14 @@
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.AUTHENTICATION_CONTEXT_EXPIRY_VALIDATION;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.SKIP_LOCAL_USER_SEARCH_FOR_AUTHENTICATION_FLOW_HANDLERS;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.USER_SESSION_MAPPING_ENABLED;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.ENABLE_CONFIGURED_IDP_SUB_FOR_FEDERATED_USER_ASSOCIATION;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.InternalRoleDomains.APPLICATION_DOMAIN;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.InternalRoleDomains.WORKFLOW_DOMAIN;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.REQUEST_PARAM_SP;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.RequestParams.CORRELATION_ID;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.RequestParams.IS_IDF_INITIATED_FROM_AUTHENTICATOR;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.RequestParams.USER_TENANT_DOMAIN_HINT;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.USERNAME_CLAIM;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.USE_IDP_ROLE_CLAIM_AS_IDP_GROUP_CLAIM;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkErrorConstants.ErrorMessages.ERROR_WHILE_GETTING_IDP_BY_NAME;
import static org.wso2.carbon.identity.configuration.mgt.core.constant.ConfigurationConstants.ErrorMessages.ERROR_CODE_ATTRIBUTE_DOES_NOT_EXISTS;
Expand Down Expand Up @@ -799,6 +802,87 @@ public static Optional<String> getApplicationResourceId(AuthenticationContext co
.map(ServiceProvider::getApplicationResourceId);
}

/**
* Retrieve the user id claim configured for the federated IDP.
*
* @param federatedIdpName Federated IDP name.
* @param tenantDomain Tenant domain.
* @return User ID claim configured for the IDP.
* @throws PostAuthenticationFailedException PostAuthenticationFailedException.
*/
public static String getUserIdClaimURI(String federatedIdpName, String tenantDomain)
throws PostAuthenticationFailedException {

String userIdClaimURI;
IdentityProvider idp;
try {
idp = FrameworkServiceDataHolder.getInstance().getIdentityProviderManager()
.getIdPByName(federatedIdpName, tenantDomain);
} catch (IdentityProviderManagementException e) {
throw new PostAuthenticationFailedException(
ERROR_WHILE_GETTING_IDP_BY_NAME.getCode(),
String.format(FrameworkErrorConstants.ErrorMessages.ERROR_WHILE_GETTING_IDP_BY_NAME.getMessage(),
tenantDomain), e);
}
if (idp == null) {
return null;
}
ClaimConfig claimConfigs = idp.getClaimConfig();
if (claimConfigs == null) {
return null;
}
ClaimMapping[] claimMappings = claimConfigs.getClaimMappings();
if (claimMappings == null || claimMappings.length < 1) {
return null;
}
userIdClaimURI = claimConfigs.getUserClaimURI();
if (userIdClaimURI != null) {
return userIdClaimURI;
}
ClaimMapping userNameClaimMapping = Arrays.stream(claimMappings).filter(claimMapping ->
StringUtils.equals(USERNAME_CLAIM, claimMapping.getLocalClaim().getClaimUri()))
.findFirst()
.orElse(null);
if (userNameClaimMapping != null) {
userIdClaimURI = userNameClaimMapping.getRemoteClaim().getClaimUri();
}
return userIdClaimURI;
}

/**
* Get the external subject from the step config.
*
* @param stepConfig Step config.
* @param tenantDomain Tenant domain.
* @return External subject.
* @throws PostAuthenticationFailedException PostAuthenticationFailedException.
*/
public static String getExternalSubject(StepConfig stepConfig, String tenantDomain)
throws PostAuthenticationFailedException {

String externalSubject = null;
String userIdClaimURI = getUserIdClaimURI(stepConfig.getAuthenticatedIdP(), tenantDomain);
if (StringUtils.isNotEmpty(userIdClaimURI)) {
externalSubject = stepConfig.getAuthenticatedUser().getUserAttributes().entrySet().stream()
.filter(userAttribute -> userAttribute.getKey().getRemoteClaim().getClaimUri()
.equals(userIdClaimURI))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
return externalSubject;
}

/**
* Get the configuration whether the external subject attribute based on IdP configurations..
*
* @return true if the IdP configurations has to be honoured.
*/
public static boolean isConfiguredIdpSubForFederatedUserAssociationEnabled() {

return Boolean.parseBoolean(IdentityUtil.getProperty(ENABLE_CONFIGURED_IDP_SUB_FOR_FEDERATED_USER_ASSOCIATION));
}

private static String getServiceProviderNameByReferer(HttpServletRequest request) {

String serviceProviderName = null;
Expand Down Expand Up @@ -2745,7 +2829,7 @@ public static String getMappedIdpRoleClaimUri(String idpRoleClaimUri, StepConfig
}
// check for role claim uri in the idaps dialect.
for (Entry<String, String> entry : carbonToStandardClaimMapping.entrySet()) {
if (StringUtils.isNotEmpty(idpRoleMappingURI) &&
if (StringUtils.isNotEmpty(idpRoleMappingURI) &&
idpRoleMappingURI.equalsIgnoreCase(entry.getValue())) {
idpRoleMappingURI = entry.getKey();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceComponent;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;
import org.wso2.carbon.identity.application.common.model.ClaimConfig;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
import org.wso2.carbon.identity.common.testng.WithCarbonHome;
import org.wso2.carbon.identity.core.model.IdentityCookieConfig;
import org.wso2.carbon.identity.core.util.IdentityConfigParser;
Expand All @@ -69,6 +71,7 @@
import org.wso2.carbon.identity.event.services.IdentityEventService;
import org.wso2.carbon.identity.event.services.IdentityEventServiceImpl;
import org.wso2.carbon.identity.testutil.IdentityBaseTest;
import org.wso2.carbon.idp.mgt.IdentityProviderManager;
import org.wso2.carbon.user.core.UserCoreConstants;

import java.io.UnsupportedEncodingException;
Expand Down Expand Up @@ -129,6 +132,18 @@ public class FrameworkUtilsTest extends IdentityBaseTest {
@Mock
AuthenticationContextCache mockedAuthenticationContextCache;

@Mock
private IdentityProviderManager mockedIdentityProviderManager;

@Mock
private IdentityProvider mockedIdentityProvider;

@Mock
private ClaimConfig mockedClaimConfig;

@Mock
private ClaimMapping mockedClaimMapping;

@Captor
ArgumentCaptor<Cookie> cookieCaptor;

Expand Down Expand Up @@ -868,4 +883,25 @@ public void testGetIdpRoleClaimUriFromClaimMappings(Object claimMappings,
assertEquals(roleClaim, expectedRoleClaimUri);
}
}

@Test
public void testGetUserIdClaimURI() throws Exception {

when(mockedIdentityProviderManager.getIdPByName("testIdp", "testTenant"))
.thenReturn(mockedIdentityProvider);
when(mockedIdentityProvider.getClaimConfig()).thenReturn(mockedClaimConfig);

when(mockedClaimConfig.getUserClaimURI()).thenReturn("http://wso2.org/claims/username");
when(mockedClaimConfig.getClaimMappings()).thenReturn(new ClaimMapping[]{mockedClaimMapping});

try (MockedStatic<FrameworkServiceDataHolder> mockedFrameworkService =
mockStatic(FrameworkServiceDataHolder.class)) {
FrameworkServiceDataHolder frameworkServiceDataHolder = mock(FrameworkServiceDataHolder.class);
when(frameworkServiceDataHolder.getIdentityProviderManager()).thenReturn(mockedIdentityProviderManager);
mockedFrameworkService.when(FrameworkServiceDataHolder::getInstance).thenReturn(frameworkServiceDataHolder);

String result = FrameworkUtils.getUserIdClaimURI("testIdp", "testTenant");
assertEquals(result, "http://wso2.org/claims/username");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,5 +263,6 @@
"console",
"SYSTEM"
],
"authentication.jit_provisioning.associating_to_existing_user": false
"authentication.jit_provisioning.associating_to_existing_user": false,
"authentication.jit_provisioning.enable_configured_idp_sub_for_federated_user_association": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@
<PoolSize>0</PoolSize>
<RetryCount>{{oauth.token_generation.retry_count_on_persistence_failures}}</RetryCount>
</TokenPersistence>

<!--
Config related to OAuth2 authorization code persistence.
Prior to IS 7.0.0, this was handled by the config <TokenPersistence>.<Enable>.
Expand Down Expand Up @@ -2031,6 +2031,7 @@
<AllowLoginToIDP>{{authentication.jit_provisioning.allow_idp_login}}</AllowLoginToIDP>
{% endif %}
<AllowAssociatingToExistingUser>{{authentication.jit_provisioning.associating_to_existing_user}}</AllowAssociatingToExistingUser>
<EnableConfiguredIdpSubForFederatedUserAssociation>{{authentication.jit_provisioning.enable_configured_idp_sub_for_federated_user_association}}</EnableConfiguredIdpSubForFederatedUserAssociation>
</JITProvisioning>

<!--Application management service configurations-->
Expand Down

0 comments on commit 02ba530

Please sign in to comment.