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

Handle authentication when no local account is linked #6082

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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 @@ -25,6 +25,7 @@
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_GETTING_ASSOCIATION_FOR_USER;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_INVALID_USER_STORE;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_INVALID_USER_STORE_DOMAIN;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_POST_AUTH_COOKIE_NOT_FOUND;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_PROCESSING_APPLICATION_CLAIM_CONFIGS;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_RETRIEVING_CLAIM;
Expand Down Expand Up @@ -139,6 +140,7 @@ public enum I18NErrorMessages {
"authenticated.subject.identifier.null"),
ERROR_WHILE_CONCLUDING_AUTHENTICATION_USER_ID_NULL("80029", "authentication.error",
"authenticated.user.id.null"),
ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND("80030", "authentication.error", "no.associated.local.user.found"),
ERROR_CODE_DEFAULT("Default", "authentication.attempt.failed", "authorization.failed");

private final String errorCode;
Expand Down Expand Up @@ -282,6 +284,9 @@ public static I18nErrorCodeWrapper translate(String errorCode) {
} else if (ERROR_WHILE_CONCLUDING_AUTHENTICATION_USER_ID_NULL.getErrorCode().equals(errorCode)) {
return new I18nErrorCodeWrapper(ERROR_WHILE_CONCLUDING_AUTHENTICATION_USER_ID_NULL.getStatus(),
ERROR_WHILE_CONCLUDING_AUTHENTICATION_USER_ID_NULL.getStatusMsg());
} else if (ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND.getErrorCode().equals(errorCode)) {
return new I18nErrorCodeWrapper(ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND.getStatus(),
ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND.getStatusMsg());
} else {
return new I18nErrorCodeWrapper(ERROR_CODE_DEFAULT.getStatus(), ERROR_CODE_DEFAULT.getStatusMsg());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,23 @@ private PostAuthnHandlerFlowStatus handleRequestFlow(HttpServletRequest request,
localClaimValues = new HashMap<>();
}

String externalSubject = stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
String userIdClaimURI = FrameworkUtils.getUserIdClaimURI(stepConfig.getAuthenticatedIdP(),
context.getTenantDomain());
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);
}
}

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 +370,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 +843,21 @@ private void callDefaultProvisioningHandler(String username, AuthenticationConte
}
}

localClaimValues.put(FrameworkConstants.ASSOCIATED_ID,
stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier());
String externalSubject = stepConfig.getAuthenticatedUser().getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
String userIdClaimURI = FrameworkUtils.getUserIdClaimURI(stepConfig.getAuthenticatedIdP(),
context.getTenantDomain());
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);
}
}

localClaimValues.put(FrameworkConstants.ASSOCIATED_ID, externalSubject);
localClaimValues.put(FrameworkConstants.IDP_ID, stepConfig.getAuthenticatedIdP());

/*
Expand Down Expand Up @@ -959,8 +985,20 @@ 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()) {
String userIdClaimURI = FrameworkUtils.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);
}
}
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,18 @@ private String getUserNameAssociatedWith(AuthenticationContext context, StepConf
String associatesUserName;
String originalExternalIdpSubjectValueForThisStep = stepConfig.getAuthenticatedUser()
.getAuthenticatedSubjectIdentifier();
if (FrameworkUtils.isConfiguredIdpSubForFederatedUserAssociationEnabled()) {
String userIdClaimURI = FrameworkUtils.getUserIdClaimURI(stepConfig.getAuthenticatedIdP(),
context.getTenantDomain());
if (StringUtils.isNotEmpty(userIdClaimURI)) {
originalExternalIdpSubjectValueForThisStep = stepConfig.getAuthenticatedUser().getUserAttributes()
.entrySet().stream().filter(userAttribute ->
userAttribute.getKey().getRemoteClaim().getClaimUri().equals(userIdClaimURI))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
}
try {
FrameworkUtils.startTenantFlow(context.getTenantDomain());
FederatedAssociationManager federatedAssociationManager = FrameworkUtils.getFederatedAssociationManager();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.PostAuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.AbstractPostAuthnHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.PostAuthnHandlerFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.ClaimConfig;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.common.model.User;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.exception.FederatedAssociationManagerException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_GETTING_ASSOCIATION_FOR_USER;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND;
import static org.wso2.carbon.identity.application.authentication.framework.exception.ErrorToI18nCodeTranslator.I18NErrorMessages.ERROR_PROCESSING_APPLICATION_CLAIM_CONFIGS;

/**
* Post authentication handler to handle the association of federated user with local user.
*/
public class PostAuthAssociationVerificationHandler extends AbstractPostAuthnHandler {

private static final Log LOG = LogFactory.getLog(PostAuthAssociationVerificationHandler.class);
private static final String SSO = "SSO";

private static final PostAuthAssociationVerificationHandler instance = new PostAuthAssociationVerificationHandler();

protected PostAuthAssociationVerificationHandler() {
}

public static PostAuthAssociationVerificationHandler getInstance() {

return instance;
}

@Override
public int getPriority() {

int priority = super.getPriority();
if (priority == -1) {
// This should execute after JIT provisioning handler.
priority = 40;
}
return priority;
}

@Override
public PostAuthnHandlerFlowStatus handle(HttpServletRequest request, HttpServletResponse response,
AuthenticationContext context) throws PostAuthenticationFailedException {

if (LOG.isDebugEnabled()) {
LOG.debug("Executing PostAuthAssociationVerificationHandler");
}

// Skip if the user is not federated or the external IdP is Organization SSO authenticator.
if (context.getExternalIdP() == null ||
(context.getExternalIdP() != null &&
SSO.equals(context.getExternalIdP().getIdentityProvider().getIdentityProviderName()))) {
return PostAuthnHandlerFlowStatus.SUCCESS_COMPLETED;
}

String tenantDomain = context.getTenantDomain();
String spName = context.getServiceProviderName();

try {
AuthenticatedUser authenticatedUser = context.getSequenceConfig().getAuthenticatedUser();
ServiceProvider serviceProvider = FrameworkServiceDataHolder.getInstance().getApplicationManagementService()
.getServiceProvider(spName, tenantDomain);
ClaimConfig serviceProviderClaimConfig = serviceProvider.getClaimConfig();
UserLinkStrategy userLinkStrategy = resolveLocalUserLinkingStrategy(serviceProviderClaimConfig);
if (userLinkStrategy == UserLinkStrategy.MANDATORY) {
User localUser = FrameworkServiceDataHolder.getInstance()
.getFederatedAssociationManager().getAssociatedLocalUser(
tenantDomain,
context.getExternalIdP().getIdentityProvider().getResourceId(),
authenticatedUser.getUserName());
if (localUser == null) {
throw new PostAuthenticationFailedException(ERROR_NO_ASSOCIATED_LOCAL_USER_FOUND.getErrorCode(),
"Federated user is not associated with any local user.");
}
}
} catch (IdentityApplicationManagementException e) {
throw new PostAuthenticationFailedException(ERROR_PROCESSING_APPLICATION_CLAIM_CONFIGS.getErrorCode(),
"Error while retrieving service provider.", e);
} catch (FederatedAssociationManagerException e) {
throw new PostAuthenticationFailedException(ERROR_GETTING_ASSOCIATION_FOR_USER.getErrorCode(),
"Error while retrieving federated associations.", e);
}

return PostAuthnHandlerFlowStatus.SUCCESS_COMPLETED;
}

/**
* Method to get the assert local user behaviour based on the service provider claim configuration.
*
* @param claimConfig Claim configuration of the service provider.
* @return Assert local user behaviour.
*/
private static UserLinkStrategy resolveLocalUserLinkingStrategy(ClaimConfig claimConfig) {

if (claimConfig == null) {
return UserLinkStrategy.DISABLED;
}

if (claimConfig.isMappedLocalSubjectMandatory()) {
return UserLinkStrategy.MANDATORY;
} else if (claimConfig.isAlwaysSendMappedLocalSubjectId()) {
return UserLinkStrategy.OPTIONAL;
} else {
return UserLinkStrategy.DISABLED;
}
}

/**
* Enum to represent the user link strategy.
*/
public enum UserLinkStrategy {

DISABLED,
OPTIONAL,
MANDATORY
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.wso2.carbon.identity.application.authentication.framework.handler.request.PostAuthenticationHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.JITProvisioningPostAuthenticationHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.PostAuthAssociationHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.PostAuthAssociationVerificationHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.PostAuthenticatedSubjectIdentifierHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.PostAuthnMissingClaimHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.impl.consent.ConsentMgtPostAuthnHandler;
Expand Down Expand Up @@ -342,7 +343,9 @@ protected void activate(ComponentContext ctxt) {
.getInstance();
bundleContext
.registerService(PostAuthenticationHandler.class.getName(), postAuthenticatedUserDomainHandler, null);

PostAuthenticationHandler postAuthenticationAssociationHandler = PostAuthAssociationVerificationHandler
.getInstance();
bundleContext.registerService(PostAuthenticationHandler.class, postAuthenticationAssociationHandler, null);
if (log.isDebugEnabled()) {
log.debug("Application Authentication Framework bundle is activated");
}
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
Loading
Loading