Skip to content
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 @@ -22,6 +22,10 @@
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorStateInfo;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -154,4 +158,18 @@ public void setTenantDomain(String tenantDomain) {

this.tenantDomain = tenantDomain;
}

public static <T extends Serializable> T deepCopy(T obj) {
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Missing docstring for the public method 'deepCopy'. All public methods should have a docstring explaining the purpose, parameters, return value, and potential exceptions.

Copilot generated this review using guidance from repository custom instructions.

try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Comment on lines +162 to +174
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The deepCopy method does not close the streams (ObjectOutputStream and ObjectInputStream), which could lead to resource leaks. Wrap streams in try-with-resources blocks to ensure proper cleanup.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorStateInfo;
import org.wso2.carbon.identity.application.authentication.framework.config.model.AuthenticatorConfig;
import org.wso2.carbon.identity.application.authentication.framework.config.model.ExternalIdPConfig;
import org.wso2.carbon.identity.application.authentication.framework.config.model.SequenceConfig;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedIdPData;
Expand Down Expand Up @@ -120,6 +121,30 @@ public class AuthenticationContext extends MessageContext implements Serializabl

private boolean sendToMultiOptionPage;

private Map<Integer, AuthenticatorConfig> orgAuthenticatorConfigs = new HashMap<>();

public Map<Integer, AuthenticatorConfig> getOrgAuthenticatorConfigs() {

return orgAuthenticatorConfigs;
}
Comment on lines +126 to +129
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Missing docstring for the public method 'getOrgAuthenticatorConfigs'. All public methods should have a docstring.

Copilot generated this review using guidance from repository custom instructions.

public void addOrganicAuthenticatorConfig(int orgNumber, AuthenticatorConfig authenticatorConfig) {

this.orgAuthenticatorConfigs.put(orgNumber, authenticatorConfig);
}
Comment on lines +131 to +134
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Missing docstring for the public method 'addOrganicAuthenticatorConfig'. All public methods should have a docstring.

Copilot generated this review using guidance from repository custom instructions.

private int currentOrgNumber;

public int getCurrentOrgNumber() {

return currentOrgNumber;
}
Comment on lines +138 to +141
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Missing docstring for the public method 'getCurrentOrgNumber'. All public methods should have a docstring.

Copilot generated this review using guidance from repository custom instructions.

public void setCurrentOrgNumber(int currentOrgNumber) {

this.currentOrgNumber = currentOrgNumber;
}
Comment on lines +143 to +146
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Missing docstring for the public method 'setCurrentOrgNumber'. All public methods should have a docstring.

Copilot generated this review using guidance from repository custom instructions.

/**
* This attribute holds the context expiry time in epoch timestamp (nanoseconds).
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.wso2.carbon.identity.application.authentication.framework.handler.request.impl;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand All @@ -43,7 +44,7 @@
import org.wso2.carbon.identity.application.authentication.framework.internal.util.SessionEventPublishingUtil;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedIdPData;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;xw
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Remove the trailing 'xw' characters from the import statement.

Suggested change
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;xw
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticationResult;

Copilot uses AI. Check for mistakes.
import org.wso2.carbon.identity.application.authentication.framework.model.CommonAuthResponseWrapper;
import org.wso2.carbon.identity.application.authentication.framework.store.SessionDataStore;
import org.wso2.carbon.identity.application.authentication.framework.store.UserSessionStore;
Expand Down Expand Up @@ -282,66 +283,144 @@ public void handle(HttpServletRequest request, HttpServletResponse response, Aut
context.setCurrentStep(currentStep);
continue;
}
ApplicationAuthenticator authenticator =
authenticatorConfig.getApplicationAuthenticator();

String idpName = stepConfig.getAuthenticatedIdP();
//TODO: Need to fix occurrences where idPName becomes "null"
if ((idpName == null || "null".equalsIgnoreCase(idpName) || idpName.isEmpty()) &&
sequenceConfig.getAuthenticatedReqPathAuthenticator() != null) {
idpName = FrameworkConstants.LOCAL_IDP_NAME;
}
try {
externalIdPConfig = ConfigurationFacade.getInstance()
.getIdPConfigByName(idpName, context.getTenantDomain());
context.setExternalIdP(externalIdPConfig);
context.setAuthenticatorProperties(FrameworkUtils
.getAuthenticatorPropertyMapFromIdP(
externalIdPConfig, authenticator.getName()));

if (authenticatorConfig.getAuthenticatorStateInfo() != null) {
context.setStateInfo(authenticatorConfig.getAuthenticatorStateInfo());
} else {
context.setStateInfo(
getStateInfoFromPreviousAuthenticatedIdPs(idpName, authenticatorConfig.getName(),
context));
}

AuthenticatorFlowStatus status = authenticator.process(request, response, context);
request.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, status);
ApplicationAuthenticator authenticator;
if ("SSO".equals(idpName)) {
AuthenticatedIdPData authenticatedIdPData = context.getPreviousAuthenticatedIdPs().get(idpName);
if (MapUtils.isEmpty(context.getOrgAuthenticatorConfigs())) {
int i = 1;
for (AuthenticatorConfig authConfig : authenticatedIdPData.getAuthenticators()) {
context.addOrganicAuthenticatorConfig(i, authConfig);
i++;
}
}

if (!status.equals(AuthenticatorFlowStatus.INCOMPLETE)) {
// TODO what if logout fails. this is an edge case
currentStep++;
context.setCurrentStep(currentStep);
continue;
if (context.getCurrentOrgNumber() == 0) {
context.setCurrentOrgNumber(1);
}
// sends the logout request to the external IdP
return;
} catch (AuthenticationFailedException | LogoutFailedException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while handling logout request.")
.inputParam(LogConstants.InputKeys.IDP, idpName)
.resultStatus(DiagnosticLog.ResultStatus.FAILED);

// Sanitize the error message before adding to diagnostic log.
String errorMessage = e.getMessage();
if (context.getLastAuthenticatedUser() != null) {
String userName = context.getLastAuthenticatedUser().getUserName();
errorMessage = LoggerUtils.getSanitizedErrorMessage(errorMessage, userName);

int orgCount = context.getOrgAuthenticatorConfigs().size();
while (context.getCurrentOrgNumber() <= orgCount) {
AuthenticatorConfig orgAuthenticatorConfig =
context.getOrgAuthenticatorConfigs().get(context.getCurrentOrgNumber());

authenticator = orgAuthenticatorConfig.getApplicationAuthenticator();
try {
externalIdPConfig = ConfigurationFacade.getInstance()
.getIdPConfigByName(idpName, context.getTenantDomain());
context.setExternalIdP(externalIdPConfig);
context.setAuthenticatorProperties(FrameworkUtils
.getAuthenticatorPropertyMapFromIdP(
externalIdPConfig, authenticator.getName()));

if (orgAuthenticatorConfig.getAuthenticatorStateInfo() != null) {
context.setStateInfo(orgAuthenticatorConfig.getAuthenticatorStateInfo());
} else {
context.setStateInfo(
getStateInfoFromPreviousAuthenticatedIdPs(idpName,
orgAuthenticatorConfig.getName(), context));
}

AuthenticatorFlowStatus status = authenticator.process(request, response, context);
request.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, status);

if (!status.equals(AuthenticatorFlowStatus.INCOMPLETE)) {
// TODO what if logout fails. this is an edge case
context.setCurrentOrgNumber(context.getCurrentOrgNumber() + 1);
request.setAttribute("logoutInit", "true");
continue;
}
// sends the logout request to the external IdP
return;
} catch (AuthenticationFailedException | LogoutFailedException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while handling logout request.")
.inputParam(LogConstants.InputKeys.IDP, idpName)
.resultStatus(DiagnosticLog.ResultStatus.FAILED);

// Sanitize the error message before adding to diagnostic log.
String errorMessage = e.getMessage();
if (context.getLastAuthenticatedUser() != null) {
String userName = context.getLastAuthenticatedUser().getUserName();
errorMessage = LoggerUtils.getSanitizedErrorMessage(errorMessage, userName);
}
diagnosticLogBuilder.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, errorMessage);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
}
throw new FrameworkException("Exception while handling logout request", e);
} catch (IdentityProviderManagementException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while getting IdP by name")
.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, e.getMessage())
.resultStatus(DiagnosticLog.ResultStatus.FAILED);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
}
log.error("Exception while getting IdP by name", e);
}
diagnosticLogBuilder.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, errorMessage);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
}
throw new FrameworkException("Exception while handling logout request", e);
} catch (IdentityProviderManagementException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while getting IdP by name")
.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, e.getMessage())
.resultStatus(DiagnosticLog.ResultStatus.FAILED);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
currentStep++;
context.setCurrentStep(currentStep);
} else {
authenticator = authenticatorConfig.getApplicationAuthenticator();
try {
externalIdPConfig = ConfigurationFacade.getInstance()
.getIdPConfigByName(idpName, context.getTenantDomain());
context.setExternalIdP(externalIdPConfig);
context.setAuthenticatorProperties(FrameworkUtils
.getAuthenticatorPropertyMapFromIdP(
externalIdPConfig, authenticator.getName()));

if (authenticatorConfig.getAuthenticatorStateInfo() != null) {
context.setStateInfo(authenticatorConfig.getAuthenticatorStateInfo());
} else {
context.setStateInfo(
getStateInfoFromPreviousAuthenticatedIdPs(idpName, authenticatorConfig.getName(),
context));
}

AuthenticatorFlowStatus status = authenticator.process(request, response, context);
request.setAttribute(FrameworkConstants.RequestParams.FLOW_STATUS, status);

if (!status.equals(AuthenticatorFlowStatus.INCOMPLETE)) {
// TODO what if logout fails. this is an edge case
currentStep++;
context.setCurrentStep(currentStep);
continue;
}
// sends the logout request to the external IdP
return;
} catch (AuthenticationFailedException | LogoutFailedException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while handling logout request.")
.inputParam(LogConstants.InputKeys.IDP, idpName)
.resultStatus(DiagnosticLog.ResultStatus.FAILED);

// Sanitize the error message before adding to diagnostic log.
String errorMessage = e.getMessage();
if (context.getLastAuthenticatedUser() != null) {
String userName = context.getLastAuthenticatedUser().getUserName();
errorMessage = LoggerUtils.getSanitizedErrorMessage(errorMessage, userName);
}
diagnosticLogBuilder.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, errorMessage);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
}
throw new FrameworkException("Exception while handling logout request", e);
} catch (IdentityProviderManagementException e) {
if (LoggerUtils.isDiagnosticLogsEnabled() && diagnosticLogBuilder != null) {
diagnosticLogBuilder.resultMessage("Exception while getting IdP by name")
.inputParam(LogConstants.InputKeys.ERROR_MESSAGE, e.getMessage())
.resultStatus(DiagnosticLog.ResultStatus.FAILED);
LoggerUtils.triggerDiagnosticLogEvent(diagnosticLogBuilder);
}
log.error("Exception while getting IdP by name", e);
}
log.error("Exception while getting IdP by name", e);
}
}
} else if (context.getPreviousAuthenticatedIdPs().size() != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,7 @@ protected void doAuthentication(HttpServletRequest request, HttpServletResponse

// This fix needs to be generalized and improved for non organization login flows as well.
if (isLoggedInWithOrganizationLogin(authenticatorConfig)) {
authenticatorConfig = AuthenticatorConfig.deepCopy(authenticatorConfig);
handleDuplicateOrganizationAuthenticators(authenticatedIdPData, authenticatedUser, authenticatorConfig);
}

Expand Down
Loading