diff --git a/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/handler/request/impl/JITProvisioningPostAuthenticationHandler.java b/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/handler/request/impl/JITProvisioningPostAuthenticationHandler.java index 8148ad7f3627..7db418891967 100644 --- a/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/handler/request/impl/JITProvisioningPostAuthenticationHandler.java +++ b/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/handler/request/impl/JITProvisioningPostAuthenticationHandler.java @@ -95,6 +95,7 @@ import static org.wso2.carbon.identity.application.authentication.framework.handler.request.PostAuthnHandlerFlowStatus.SUCCESS_COMPLETED; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.ALLOW_LOGIN_TO_IDP; +import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.RESOLVE_EXISTING_USER_BEFORE_CONSENT_PROMPT; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.SEND_ONLY_LOCALLY_MAPPED_ROLES_OF_IDP; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.EMAIL_ADDRESS_CLAIM; import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkErrorConstants.ErrorMessages.ERROR_WHILE_ENCRYPTING_TOTP_SECRET_KEY; @@ -339,6 +340,16 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request, boolean isUserAllowsToLoginIdp = Boolean.parseBoolean(IdentityUtil .getProperty(ALLOW_LOGIN_TO_IDP)); + // When enabled, resolve and link the federated identity to an existing local account before the + // prompt-consent sign-up redirect, so an existing user is logged in instead of being redirected + // to the sign-up form. Applies only to IdPs that have associate-local-user enabled. + if (isResolveExistingUserBeforeConsentPromptEnabled() + && externalIdPConfig.isAssociateLocalUserEnabled() + && StringUtils.isEmpty(associatedLocalUser) + && externalIdPConfig.isPromptConsentEnabled()) { + associatedLocalUser = resolveAndAssociateExistingLocalUser(externalIdPConfig, context, + localClaimValues, federatedClaimValues, stepConfig, externalSubject); + } // If associatedLocalUser is null, that means relevant association not exist already. if (StringUtils.isEmpty(associatedLocalUser) && externalIdPConfig.isPromptConsentEnabled()) { if (log.isDebugEnabled()) { @@ -355,71 +366,8 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request, return PostAuthnHandlerFlowStatus.INCOMPLETE; } if (StringUtils.isEmpty(associatedLocalUser) && externalIdPConfig.isAssociateLocalUserEnabled()) { - - AccountLookupAttributeMappingConfig[] accountLookupAttributeMappingConfigs = - externalIdPConfig.getAccountLookupAttributeMappings(); - boolean isEmailUsernameLookup = - ArrayUtils.isEmpty(accountLookupAttributeMappingConfigs); - if (log.isDebugEnabled()) { - log.debug("Account lookup attribute mappings are not configured for the IDP: " - + externalIdPConfig.getIdPName() + ". Hence, using email address claim for account " - + "lookup matching with local username."); - } - if (isEmailUsernameLookup && - StringUtils.isNotBlank(localClaimValues.get(EMAIL_ADDRESS_CLAIM))) { - try { - String emailUsername = localClaimValues.get(EMAIL_ADDRESS_CLAIM); - UserRealm realm = getUserRealm(context.getTenantDomain()); - AbstractUserStoreManager userStoreManager = - (AbstractUserStoreManager) getUserStoreManager(context.getExternalIdP() - .getProvisioningUserStoreId(), realm, emailUsername); - if (userStoreManager.isExistingUser(emailUsername)) { - org.wso2.carbon.user.core.common.User user = - userStoreManager.getUser(null, emailUsername); - //associate user - FrameworkUtils.getFederatedAssociationManager() - .createFederatedAssociation(new User(user), - stepConfig.getAuthenticatedIdP(), - externalSubject); - associatedLocalUser = user.getDomainQualifiedUsername(); - } - } catch (UserStoreException e) { - handleExceptions(ErrorMessages.ERROR_WHILE_CHECKING_USERNAME_EXISTENCE.getMessage(), - "error.user.existence", e); - } catch (FrameworkException | FederatedAssociationManagerException e) { - handleExceptions(e.getMessage(), e.getErrorCode(), e); - } - } else { - if (log.isDebugEnabled()) { - log.debug("Account lookup attribute mappings configured for IDP: " + - externalIdPConfig.getIdPName() + ". Attempting to match user using mapped " + - "attributes."); - } - Map localClaimValuesForLookup = - getLocalClaimsForAccountLookup(federatedClaimValues, - externalIdPConfig.getAccountLookupAttributeMappings()); - - org.wso2.carbon.user.core.common.User user = getLocalUser(context.getTenantDomain(), - externalIdPConfig.getProvisioningUserStoreId(), localClaimValuesForLookup); - - try { - if (user != null) { - FrameworkUtils.getFederatedAssociationManager() - .createFederatedAssociation(new User(user), - stepConfig.getAuthenticatedIdP(), - externalSubject); - associatedLocalUser = user.getDomainQualifiedUsername(); - } else { - if (log.isDebugEnabled()) { - log.debug("No local user found for the account lookup attributes: " - + localClaimValuesForLookup + " in tenant domain: " - + context.getTenantDomain()); - } - } - } catch (FederatedAssociationManagerException | FrameworkException e) { - handleExceptions(e.getMessage(), e.getErrorCode(), e); - } - } + associatedLocalUser = resolveAndAssociateExistingLocalUser(externalIdPConfig, context, + localClaimValues, federatedClaimValues, stepConfig, externalSubject); } if (externalIdPConfig.isSkipJITOnAttrAccountLookupEnabled() && StringUtils.isEmpty(associatedLocalUser)) { @@ -826,6 +774,105 @@ private void handleExceptions(String errorMessage, String errorCode, Exception e throw new PostAuthenticationFailedException(errorCode, errorMessage, e); } + /** + * Whether resolving an existing local user before the prompt-consent sign-up redirect is enabled. + * + * @return true if resolving existing users before prompt consent is enabled. + */ + private boolean isResolveExistingUserBeforeConsentPromptEnabled() { + + return Boolean.parseBoolean(IdentityUtil.getProperty(RESOLVE_EXISTING_USER_BEFORE_CONSENT_PROMPT)); + } + + /** + * Resolve an existing local account for the federated user and, if found, create the federated association. + * Uses the IdP's account lookup attribute mappings when configured, otherwise falls back to matching the + * email address claim against the local username. + * + * @param externalIdPConfig Relevant external IDP. + * @param context Authentication context. + * @param localClaimValues Local claim values of the user. + * @param federatedClaimValues Federated claim values of the user. + * @param stepConfig The authentication step config. + * @param externalSubject The federated subject identifier. + * @return The domain-qualified username of the linked local account, or null when no matching account exists. + * @throws PostAuthenticationFailedException If the user-store lookup or association fails. + */ + private String resolveAndAssociateExistingLocalUser(ExternalIdPConfig externalIdPConfig, + AuthenticationContext context, + Map localClaimValues, + Map federatedClaimValues, + StepConfig stepConfig, String externalSubject) + throws PostAuthenticationFailedException { + + String associatedLocalUser = null; + AccountLookupAttributeMappingConfig[] accountLookupAttributeMappingConfigs = + externalIdPConfig.getAccountLookupAttributeMappings(); + boolean isEmailUsernameLookup = + ArrayUtils.isEmpty(accountLookupAttributeMappingConfigs); + if (isEmailUsernameLookup && + StringUtils.isNotBlank(localClaimValues.get(EMAIL_ADDRESS_CLAIM))) { + if (log.isDebugEnabled()) { + log.debug("Account lookup attribute mappings are not configured for the IDP: " + + externalIdPConfig.getIdPName() + ". Hence, using email address claim for account " + + "lookup matching with local username."); + } + try { + String emailUsername = localClaimValues.get(EMAIL_ADDRESS_CLAIM); + UserRealm realm = getUserRealm(context.getTenantDomain()); + AbstractUserStoreManager userStoreManager = + (AbstractUserStoreManager) getUserStoreManager(context.getExternalIdP() + .getProvisioningUserStoreId(), realm, emailUsername); + if (userStoreManager.isExistingUser(emailUsername)) { + org.wso2.carbon.user.core.common.User user = + userStoreManager.getUser(null, emailUsername); + //associate user + FrameworkUtils.getFederatedAssociationManager() + .createFederatedAssociation(new User(user), + stepConfig.getAuthenticatedIdP(), + externalSubject); + associatedLocalUser = user.getDomainQualifiedUsername(); + } + } catch (UserStoreException e) { + handleExceptions(ErrorMessages.ERROR_WHILE_CHECKING_USERNAME_EXISTENCE.getMessage(), + "error.user.existence", e); + } catch (FrameworkException | FederatedAssociationManagerException e) { + handleExceptions(e.getMessage(), e.getErrorCode(), e); + } + } else { + if (log.isDebugEnabled()) { + log.debug("Account lookup attribute mappings configured for IDP: " + + externalIdPConfig.getIdPName() + ". Attempting to match user using mapped " + + "attributes."); + } + Map localClaimValuesForLookup = + getLocalClaimsForAccountLookup(federatedClaimValues, + externalIdPConfig.getAccountLookupAttributeMappings()); + + org.wso2.carbon.user.core.common.User user = getLocalUser(context.getTenantDomain(), + externalIdPConfig.getProvisioningUserStoreId(), localClaimValuesForLookup); + + try { + if (user != null) { + FrameworkUtils.getFederatedAssociationManager() + .createFederatedAssociation(new User(user), + stepConfig.getAuthenticatedIdP(), + externalSubject); + associatedLocalUser = user.getDomainQualifiedUsername(); + } else { + if (log.isDebugEnabled()) { + log.debug("No local user found for the account lookup attributes: " + + localClaimValuesForLookup + " in tenant domain: " + + context.getTenantDomain()); + } + } + } catch (FederatedAssociationManagerException | FrameworkException e) { + handleExceptions(e.getMessage(), e.getErrorCode(), e); + } + } + return associatedLocalUser; + } + /** * Call the relevant URL to add the new user. * diff --git a/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/util/FrameworkConstants.java b/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/util/FrameworkConstants.java index 8642eef5b533..43cb7d634adf 100644 --- a/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/util/FrameworkConstants.java +++ b/components/authentication-framework/org.wso2.carbon.identity.application.authentication.framework/src/main/java/org/wso2/carbon/identity/application/authentication/framework/util/FrameworkConstants.java @@ -293,6 +293,8 @@ public abstract class FrameworkConstants { public static final String REQ_ATTR_RETRY_STATUS = "retryStatus"; public static final String IDP_MAPPED_USER_ROLES = "identityProviderMappedUserRoles"; public static final String ALLOW_ASSOCIATING_TO_EXISTING_USER = "JITProvisioning.AllowAssociatingToExistingUser"; + public static final String RESOLVE_EXISTING_USER_BEFORE_CONSENT_PROMPT = + "JITProvisioning.ResolveExistingUserBeforeConsentPrompt"; // The constant to used as the attribute key or the property key of the federated tokens. public static final String FEDERATED_TOKENS = "federated_tokens"; diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 index 760c9b71fcf1..8408fc6b44bc 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 @@ -2346,6 +2346,9 @@ {{authentication.jit_provisioning.allow_idp_login}} {% endif %} {{authentication.jit_provisioning.associating_to_existing_user}} + {% if authentication.jit_provisioning.resolve_existing_user_before_consent_prompt is defined %} + {{authentication.jit_provisioning.resolve_existing_user_before_consent_prompt}} + {% endif %} {{authentication.jit_provisioning.enable_configured_idp_sub_for_federated_user_association}} {{authentication.jit_provisioning.allow_non_Standard_claim_uri}} {% if authentication.jit_provisioning.show_failure_reason is defined %}