/*
* Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.sso.saml.processors;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.SessionIndex;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.model.SAMLSSOServiceProviderDO;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.sso.saml.SAMLSSOConstants;
import org.wso2.carbon.identity.sso.saml.builders.SingleLogoutMessageBuilder;
import org.wso2.carbon.identity.sso.saml.dto.SAMLSSOReqValidationResponseDTO;
import org.wso2.carbon.identity.sso.saml.dto.SingleLogoutRequestDTO;
import org.wso2.carbon.identity.sso.saml.session.SSOSessionPersistenceManager;
import org.wso2.carbon.identity.sso.saml.session.SessionInfoData;
import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil;
import org.wso2.carbon.user.api.UserStoreException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class SPInitLogoutRequestProcessor implements SPInitSSOLogoutRequestProcessor{
private static Log log = LogFactory.getLog(SPInitLogoutRequestProcessor.class);
/**
* @param logoutRequest
* @param sessionId
* @param queryString
* @return
* @throws IdentityException
*/
public SAMLSSOReqValidationResponseDTO process(LogoutRequest logoutRequest, String sessionId,
String queryString) throws IdentityException {
try {
SAMLSSOReqValidationResponseDTO reqValidationResponseDTO = new SAMLSSOReqValidationResponseDTO();
reqValidationResponseDTO.setLogOutReq(true);
String subject = null;
String issuer = null;
String defaultSigningAlgoUri = IdentityApplicationManagementUtil.getSigningAlgoURIByConfig();
String defaultDigestAlgoUri = IdentityApplicationManagementUtil.getDigestAlgoURIByConfig();
// Get the sessions from the SessionPersistenceManager and prepare
// the logout responses
SSOSessionPersistenceManager ssoSessionPersistenceManager = SSOSessionPersistenceManager
.getPersistenceManager();
if (StringUtils.isBlank(sessionId)) {
String message = "Session was already Expired";
log.error("ssoTokenId cookie not found in the logout request");
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR,
message, logoutRequest.getDestination(), defaultSigningAlgoUri, defaultDigestAlgoUri);
}
String sessionIndex = ssoSessionPersistenceManager.getSessionIndexFromTokenId(sessionId);
if (StringUtils.isBlank(sessionIndex)) {
String message = "Error while retrieving the Session Index ";
log.error("Error in retrieving Session Index from ssoTokenId cookie : " + sessionId);
reqValidationResponseDTO = buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes
.REQUESTOR_ERROR, message, null, defaultSigningAlgoUri, defaultDigestAlgoUri);
reqValidationResponseDTO.setLogoutFromAuthFramework(true);
return reqValidationResponseDTO;
}
// Only if the logout request is received.
if (logoutRequest != null) {
if (logoutRequest.getIssuer() == null) {
String message = "Issuer should be mentioned in the Logout Request";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR,
message, logoutRequest.getDestination(), defaultSigningAlgoUri, defaultDigestAlgoUri);
}
// TODO : Check for BaseID and EncryptedID as well.
if (logoutRequest.getNameID() != null) {
NameID nameID = logoutRequest.getNameID();
subject = nameID.getValue();
} else {
String message = "Subject Name should be specified in the Logout Request";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR,
message, logoutRequest.getDestination(), defaultSigningAlgoUri, defaultDigestAlgoUri);
}
if (logoutRequest.getSessionIndexes() == null) {
String message = "At least one Session Index should be present in the Logout Request";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes.REQUESTOR_ERROR,
message, logoutRequest.getDestination(), defaultSigningAlgoUri, defaultDigestAlgoUri);
}
SessionInfoData sessionInfoData = ssoSessionPersistenceManager.getSessionInfo(sessionIndex);
if (sessionInfoData == null) {
String message = "No Established Sessions corresponding to Session Indexes provided.";
log.error(message);
reqValidationResponseDTO = buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes
.REQUESTOR_ERROR, message, null, defaultSigningAlgoUri, defaultDigestAlgoUri);
reqValidationResponseDTO.setLogoutFromAuthFramework(true);
return reqValidationResponseDTO;
}
issuer = logoutRequest.getIssuer().getValue();
if(IdentityUtil.isBlank(SAMLSSOUtil.getTenantDomainFromThreadLocal())) {
if (issuer.contains("@")) {
String tenantDomain = issuer.substring(issuer.lastIndexOf('@') + 1);
issuer = issuer.substring(0, issuer.lastIndexOf('@'));
if (StringUtils.isNotBlank(tenantDomain) && StringUtils.isNotBlank(issuer)) {
SAMLSSOUtil.setTenantDomainInThreadLocal(tenantDomain);
if (log.isDebugEnabled()) {
log.debug("Tenant Domain: " + tenantDomain + " & Issuer name: " + issuer + "has been " +
"split");
}
}
}
if (IdentityUtil.isBlank(SAMLSSOUtil.getTenantDomainFromThreadLocal())) {
SAMLSSOServiceProviderDO serviceProvider = sessionInfoData.getServiceProviderList().get(issuer);
if (serviceProvider != null) {
SAMLSSOUtil.setTenantDomainInThreadLocal(serviceProvider.getTenantDomain());
} else {
throw IdentityException.error("Service provider :" + issuer + " does not exist in session " +
"info data.");
}
}
}
subject = sessionInfoData.getSubject(issuer);
Map<String, SAMLSSOServiceProviderDO> sessionsList = sessionInfoData
.getServiceProviderList();
SAMLSSOServiceProviderDO logoutReqIssuer = sessionsList.get(issuer);
if (logoutReqIssuer.isDoSingleLogout()) {
//validate session index
SessionIndex requestSessionIndex = logoutRequest.getSessionIndexes().size() > 0 ? logoutRequest
.getSessionIndexes().get(0) : null;
if (requestSessionIndex == null || !sessionIndex.equals(requestSessionIndex.getSessionIndex())) {
String message = "Session Index validation for Logout Request failed. " +
"Received: [" + (requestSessionIndex == null ? "null" : requestSessionIndex
.getSessionIndex()) + "]." + " Expected: [" + sessionIndex + "]";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes
.REQUESTOR_ERROR, message, logoutRequest.getDestination(), logoutReqIssuer
.getSigningAlgorithmUri(), logoutReqIssuer.getDigestAlgorithmUri());
}
}
if (logoutReqIssuer.isDoValidateSignatureInRequests()) {
// Validate 'Destination'
List<String> idpUrlSet = SAMLSSOUtil.getDestinationFromTenantDomain(SAMLSSOUtil
.getTenantDomainFromThreadLocal());
if (logoutRequest.getDestination() == null
|| !idpUrlSet.contains(logoutRequest.getDestination())) {
String message = "Destination validation for Logout Request failed. " +
"Received: [" + logoutRequest.getDestination() +
"]." + " Expected: [" + StringUtils.join(idpUrlSet, ',') + "]";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes
.REQUESTOR_ERROR, message, logoutRequest.getDestination(), logoutReqIssuer
.getSigningAlgorithmUri(), logoutReqIssuer.getDigestAlgorithmUri());
}
// Validate Signature
boolean isSignatureValid = SAMLSSOUtil.validateLogoutRequestSignature(logoutRequest,
logoutReqIssuer.getCertAlias(),
subject,
queryString);
if (!isSignatureValid) {
String message = "Signature validation for Logout Request failed";
log.error(message);
return buildErrorResponse(logoutRequest.getID(), SAMLSSOConstants.StatusCodes
.REQUESTOR_ERROR, message, logoutRequest.getDestination(), logoutReqIssuer
.getSigningAlgorithmUri(), logoutReqIssuer.getDigestAlgorithmUri());
}
}
SingleLogoutMessageBuilder logoutMsgBuilder = new SingleLogoutMessageBuilder();
Map<String, String> rpSessionsList = sessionInfoData.getRPSessionsList();
List<SingleLogoutRequestDTO> singleLogoutReqDTOs = new ArrayList<SingleLogoutRequestDTO>();
for (Map.Entry<String, SAMLSSOServiceProviderDO> entry : sessionsList.entrySet()) {
String key = entry.getKey();
SAMLSSOServiceProviderDO value = entry.getValue();
if (!key.equals(issuer)) {
SingleLogoutRequestDTO logoutReqDTO = new SingleLogoutRequestDTO();
if (StringUtils.isNotBlank(value.getSloRequestURL())) {
logoutReqDTO.setAssertionConsumerURL(value.getSloRequestURL());
} else if (StringUtils.isNotBlank(value.getSloResponseURL())) {
logoutReqDTO.setAssertionConsumerURL(value.getSloResponseURL());
} else {
logoutReqDTO.setAssertionConsumerURL(value.getAssertionConsumerUrl());
}
LogoutRequest logoutReq = logoutMsgBuilder.buildLogoutRequest(sessionInfoData.getSubject(key)
, sessionIndex, SAMLSSOConstants.SingleLogoutCodes.LOGOUT_USER, logoutReqDTO
.getAssertionConsumerURL(), value.getNameIDFormat(), value.getTenantDomain(), value
.getSigningAlgorithmUri(), value.getDigestAlgorithmUri());
String logoutReqString = SAMLSSOUtil.marshall(logoutReq);
logoutReqDTO.setLogoutResponse(logoutReqString);
logoutReqDTO.setRpSessionId(rpSessionsList.get(key));
singleLogoutReqDTOs.add(logoutReqDTO);
} else {
reqValidationResponseDTO.setIssuer(value.getIssuer());
reqValidationResponseDTO.setDoSignResponse(value.isDoSignResponse());
reqValidationResponseDTO.setSigningAlgorithmUri(value.getSigningAlgorithmUri());
reqValidationResponseDTO.setDigestAlgorithmUri(value.getDigestAlgorithmUri());
if (StringUtils.isNotBlank(value.getSloResponseURL())) {
reqValidationResponseDTO.setAssertionConsumerURL(value.getSloResponseURL());
} else {
reqValidationResponseDTO.setAssertionConsumerURL(value.getAssertionConsumerUrl());
}
}
}
reqValidationResponseDTO.setLogoutRespDTO(singleLogoutReqDTOs.toArray(
new SingleLogoutRequestDTO[singleLogoutReqDTOs.size()]));
LogoutResponse logoutResponse = logoutMsgBuilder.buildLogoutResponse(
logoutRequest.getID(),
SAMLSSOConstants.StatusCodes.SUCCESS_CODE,
null,
reqValidationResponseDTO.getAssertionConsumerURL(),
reqValidationResponseDTO.isDoSignResponse(),
SAMLSSOUtil.getTenantDomainFromThreadLocal(),
reqValidationResponseDTO.getSigningAlgorithmUri(),
reqValidationResponseDTO.getDigestAlgorithmUri());
reqValidationResponseDTO.setLogoutResponse(SAMLSSOUtil.encode(SAMLSSOUtil.marshall(logoutResponse)));
reqValidationResponseDTO.setValid(true);
}
return reqValidationResponseDTO;
} catch (UserStoreException | IdentityException e) {
throw IdentityException.error("Error Processing the Logout Request", e);
}
}
/**
* Builds the SAML error response and sets the compressed value to the reqValidationResponseDTO
*
* @param id
* @param status
* @param statMsg
* @param destination
* @return
* @throws IdentityException
*/
private SAMLSSOReqValidationResponseDTO buildErrorResponse(String id, String status, String statMsg, String
destination, String responseSigningAlgorithmUri, String responseDigestAlgorithmUri)
throws IdentityException {
SAMLSSOReqValidationResponseDTO reqValidationResponseDTO = new SAMLSSOReqValidationResponseDTO();
LogoutResponse logoutResp = new SingleLogoutMessageBuilder().buildLogoutResponse(id, status, statMsg,
destination, false, null, responseSigningAlgorithmUri, responseDigestAlgorithmUri);
reqValidationResponseDTO.setLogOutReq(true);
reqValidationResponseDTO.setValid(false);
try {
reqValidationResponseDTO.setResponse(SAMLSSOUtil.compressResponse(SAMLSSOUtil.marshall(logoutResp)));
} catch (IOException e) {
throw IdentityException.error("Error while creating logout response", e);
}
return reqValidationResponseDTO;
}
}