/*
* Copyright (c) 2005-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.ui.logout;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.wso2.carbon.identity.sso.saml.stub.types.SingleLogoutRequestDTO;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This class is used to send logout requests to each and every session participant. It follows a fire and
* forget approach where the task of sending each and every logout request is submitted to a threadpool
* as a job. This class implements a singleton, because it is expensive to create thread pool for each
* and every object.
*/
public class LogoutRequestSender {
private static Log log = LogFactory.getLog(LogoutRequestSender.class);
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
private static LogoutRequestSender instance = new LogoutRequestSender();
/**
* This class is used to model a single logout request that is being sent to a session participant.
* It will send the logout req. to the session participant in its 'run' method when this job is
* submitted to the thread pool.
*/
private class LogoutReqSenderTask implements Runnable {
private SingleLogoutRequestDTO logoutReqDTO;
public LogoutReqSenderTask(SingleLogoutRequestDTO logoutReqDTO) {
this.logoutReqDTO = logoutReqDTO;
}
public void run() {
List<NameValuePair> logoutReqParams = new ArrayList<NameValuePair>();
// set the logout request
logoutReqParams.add(new BasicNameValuePair("SAMLRequest", logoutReqDTO.getLogoutResponse()));
try {
int port = derivePortFromAssertionConsumerURL(logoutReqDTO.getAssertionConsumerURL());
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(logoutReqParams, "UTF-8");
HttpPost httpPost = new HttpPost(logoutReqDTO.getAssertionConsumerURL());
httpPost.setEntity(entity);
httpPost.addHeader("Cookie", "JSESSIONID=" + logoutReqDTO.getRpSessionId());
TrustManager easyTrustManager = new X509TrustManager() {
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates,
String s)
throws java.security.cert.CertificateException {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates,
String s)
throws java.security.cert.CertificateException {
}
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{easyTrustManager}, null);
SSLSocketFactory sf = new SSLSocketFactory(sslContext);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme httpsScheme = new Scheme("https", sf, port);
HttpClient httpClient = new DefaultHttpClient();
httpClient.getConnectionManager().getSchemeRegistry().register(httpsScheme);
// send the logout request as a POST
HttpResponse response = httpClient.execute(httpPost);
log.info("single logout request is sent to : " + logoutReqDTO.getAssertionConsumerURL() +
" is returned with " + response.getStatusLine().getStatusCode());
if (log.isDebugEnabled()) {
log.debug("single logout request is sent to : " + logoutReqDTO.getAssertionConsumerURL() +
" is returned with " + response.getStatusLine().getStatusCode());
}
} catch (IOException e) {
log.error("Error sending logout requests to : " +
logoutReqDTO.getAssertionConsumerURL(), e);
} catch (GeneralSecurityException e) {
log.error("Error registering the EasySSLProtocolSocketFactory", e);
} catch (RuntimeException e) {
log.error("Runtime exception occurred.", e);
} catch (URISyntaxException e) {
log.error("Error deriving port from the assertion consumer url", e);
}
}
}
/**
* A private constructor since we are implementing a singleton here
*/
private LogoutRequestSender() {
}
/**
* getInstance method of LogoutRequestSender, as it is a singleton
*
* @return LogoutRequestSender instance
*/
public static LogoutRequestSender getInstance() {
return instance;
}
/**
* takes an array of SingleLogoutRequestDTO objects, creates and submits each of them as a task
* to the thread pool
*
* @param logoutReqs Array of SingleLogoutRequestDTO representing all the session participants
*/
public void sendLogoutRequests(SingleLogoutRequestDTO[] logoutReqs) {
if (logoutReqs == null) {
return;
}
// For each logoutReq, create a new task and submit it to the thread pool.
for (SingleLogoutRequestDTO reqDTO : logoutReqs) {
threadPool.submit(new LogoutReqSenderTask(reqDTO));
if (log.isDebugEnabled()) {
log.debug("A logoutReqSenderTask is assigned to the thread pool");
}
}
}
/**
* This method is used to derive the port from the assertion consumer URL.
*
* @param assertionConsumerURL Assertion Consumer URL
* @return Port, if mentioned in the URL, or else 443 as the default value
* @throws MalformedURLException when the ACS is malformed.
*/
private int derivePortFromAssertionConsumerURL(String assertionConsumerURL)
throws URISyntaxException {
int port = 443; // use 443 as the default port
try {
URI uri = new URI(assertionConsumerURL);
if (uri.getPort() != -1) { // if the port is mentioned in the URL
port = uri.getPort();
} else if ("http".equals(uri.getScheme())) { // if it is using http
port = 80;
}
} catch (URISyntaxException e) {
log.error("Error deriving port from the assertion consumer url", e);
throw e;
}
return port;
}
}