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

Control JIT provisioning users associating to local users through associating_to_existing_user configuration. #5468

Merged
merged 7 commits into from
Feb 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.wso2.carbon.identity.application.authentication.framework.handler.provisioning.ProvisioningHandler;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkErrorConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.model.User;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
Expand Down Expand Up @@ -67,6 +68,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.ALLOW_ASSOCIATING_TO_EXISTING_USER;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.Config.SEND_MANUALLY_ADDED_LOCAL_ROLES_OF_IDP;
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.InternalRoleDomains.APPLICATION_DOMAIN;
Expand All @@ -84,6 +86,7 @@ public class DefaultProvisioningHandler implements ProvisioningHandler {
private static final String USER_WORKFLOW_ENGAGED_ERROR_CODE = "WFM-10001";
private static volatile DefaultProvisioningHandler instance;
private static final String LOCAL_DEFAULT_CLAIM_DIALECT = "http://wso2.org/claims";
private static Boolean allowAssociationToExistingUser;
RushanNanayakkara marked this conversation as resolved.
Show resolved Hide resolved

public static DefaultProvisioningHandler getInstance() {
if (instance == null) {
Expand Down Expand Up @@ -217,6 +220,20 @@ private void handleUserProvisioning(String username, UserStoreManager userStoreM
Map<String, String> userClaims = prepareClaimMappings(attributes);

if (userStoreManager.isExistingUser(username)) {
String associatedUserName = FrameworkUtils.getFederatedAssociationManager()
.getUserForFederatedAssociation(tenantDomain, idp, subjectVal);

if (StringUtils.isBlank(associatedUserName)) {
// If a local user is using the same username, association is not allowed unless enabled through config.
if (isAssociationToExistingUserAllowed()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont' we need to handle migration for this configuration?

Copy link
Contributor Author

@RushanNanayakkara RushanNanayakkara Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For any cloud deployments, if it does not associate JIT provisioning users to local accounts, rather creates new accounts. Therefore the default behaviour of this change will not affect asgardeo deployment.

For other customers whose behaviour is to associate to local users, they will have to activate this through the configuration.

// Associate User
associateUser(username, userStoreDomain, tenantDomain, subjectVal, idp);
} else {
throw new FrameworkException(
FrameworkErrorConstants.ErrorMessages.USER_ALREADY_EXISTS_ERROR.getMessage(),
FrameworkErrorConstants.ErrorMessages.USER_ALREADY_EXISTS_ERROR.getCode(), null);
}
}
/*
Set PROVISIONED_USER thread local property to true, to identify already provisioned
user claim update scenario.
Expand Down Expand Up @@ -276,12 +293,6 @@ tobeDeleted claims (claims came from federated idp as null). If there is a match
}
}
}
String associatedUserName = FrameworkUtils.getFederatedAssociationManager()
.getUserForFederatedAssociation(tenantDomain, idp, subjectVal);
if (StringUtils.isEmpty(associatedUserName)) {
// Associate User
associateUser(username, userStoreDomain, tenantDomain, subjectVal, idp);
}
} else {
String password = generatePassword();
String passwordFromUser = userClaims.get(FrameworkConstants.PASSWORD);
Expand Down Expand Up @@ -853,4 +864,18 @@ private Set<String> getIndelibleClaims() {
}
return indelibleClaims;
}

/**
* Check whether the configuration that allows to associate the existing users is enabled or not.
*
* @return whether the association of the existing users with the federated users is allowed.
*/
private Boolean isAssociationToExistingUserAllowed() {

if (allowAssociationToExistingUser == null) {
allowAssociationToExistingUser = Boolean.parseBoolean(
IdentityUtil.getProperty(ALLOW_ASSOCIATING_TO_EXISTING_USER));
}
return allowAssociationToExistingUser;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.user.core.util.UserCoreUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.io.IOException;
import java.util.ArrayList;
Expand Down Expand Up @@ -210,7 +209,6 @@ private PostAuthnHandlerFlowStatus handleResponseFlow(HttpServletRequest request
if (context.getProperty(FrameworkConstants.CHANGING_USERNAME_ALLOWED) != null) {
username = request.getParameter(FrameworkConstants.USERNAME);
}
isUsernameExists(context, username);
RushanNanayakkara marked this conversation as resolved.
Show resolved Hide resolved
callDefaultProvisioningHandler(username, context, externalIdPConfig, combinedLocalClaims,
stepConfig);
handleConsents(request, stepConfig, context.getTenantDomain());
Expand Down Expand Up @@ -335,9 +333,6 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request,
username, request);
// Set the property to make sure the request is a returning one.
context.setProperty(FrameworkConstants.PASSWORD_PROVISION_REDIRECTION_TRIGGERED, true);
if (!externalIdPConfig.isModifyUserNameAllowed()) {
isUsernameExists(context, username);
}
return PostAuthnHandlerFlowStatus.INCOMPLETE;
}
if (StringUtils.isEmpty(associatedLocalUser) && externalIdPConfig.isAssociateLocalUserEnabled()) {
Expand Down Expand Up @@ -413,9 +408,6 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request,
localClaimValues.get(EMAIL_ADDRESS_CLAIM))) {
username = UserCoreUtil.addTenantDomainToEntry(username, context.getTenantDomain());
}
if (StringUtils.isEmpty(associatedLocalUser)) {
isUsernameExists(context, username);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen if a customer uses custom jsp file? won't it cause an issue as we are skipping a isUsernameExists function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To identify if the user is existing, same as the defualt implementation, the user will have to first performa a username validation using the api/identity/user/v1.0/validate-username endpoint. This will throw an error if the username is already existing.
If the custom jsp skips this check, the federation flow will continue without the user being provisioned. However the user will not be aware of the provisioning failure unless username is validated initially and the existence of the username is handled appropriately.

}
callDefaultProvisioningHandler(username, context, externalIdPConfig, localClaimValues,
stepConfig);
}
Expand Down Expand Up @@ -1194,36 +1186,6 @@ private String getRoleClaimMapping(AuthenticationContext context) throws Framewo
return null;
}

/**
* This method throws a PostAuthenticationFailedException if the provided username is already existing in the
* system.
*
* @param context AuthenticationContext.
* @param username Username of the federated user.
* @throws PostAuthenticationFailedException if the provided username already exists.
*/
private void isUsernameExists(AuthenticationContext context, String username)
throws PostAuthenticationFailedException {

try {
UserRealm realm = getUserRealm(context.getTenantDomain());
UserStoreManager userStoreManager = getUserStoreManager(context.getExternalIdP()
.getProvisioningUserStoreId(), realm, username);
String sanitizedUserName = UserCoreUtil.removeDomainFromName(
MultitenantUtils.getTenantAwareUsername(username));
if (userStoreManager.isExistingUser(sanitizedUserName)) {
// Logging the error because the thrown exception is handled in the UI.
log.error(ErrorMessages.USER_ALREADY_EXISTS_ERROR.getCode() + " - "
+ ErrorMessages.USER_ALREADY_EXISTS_ERROR.getMessage());
handleExceptions(ErrorMessages.USER_ALREADY_EXISTS_ERROR.getMessage(),
ErrorMessages.USER_ALREADY_EXISTS_ERROR.getCode(), null);
}
} catch (UserStoreException e) {
handleExceptions(ErrorMessages.ERROR_WHILE_CHECKING_USERNAME_EXISTENCE.getMessage(),
"error.user.existence", e);
}
}

/**
* Append internal domain if there is no domain appended already.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ public abstract class FrameworkConstants {
public static final String CONTEXT_IDENTIFIER = "contextIdentifier";
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";

private FrameworkConstants() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,7 @@
{% if authentication.jit_provisioning.allow_idp_login is defined %}
<AllowLoginToIDP>{{authentication.jit_provisioning.allow_idp_login}}</AllowLoginToIDP>
{% endif %}
<AllowAssociatingToExistingUser>{{authentication.jit_provisioning.associating_to_existing_user}}</AllowAssociatingToExistingUser>
RushanNanayakkara marked this conversation as resolved.
Show resolved Hide resolved
RushanNanayakkara marked this conversation as resolved.
Show resolved Hide resolved
</JITProvisioning>

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