/*
* © Copyright IBM Corp. 2012
*
* Licensed 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 com.ibm.sbt.services.endpoints;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.CookieStore;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie2;
import org.apache.http.protocol.HttpContext;
import com.ibm.commons.runtime.Context;
import com.ibm.commons.runtime.util.UrlUtil;
import com.ibm.commons.util.PathUtil;
import com.ibm.commons.util.StringUtil;
import com.ibm.sbt.security.authentication.AuthenticationException;
import com.ibm.sbt.service.core.handlers.AuthCredsHandler;
import com.ibm.sbt.service.core.servlet.ServiceServlet;
import com.ibm.sbt.services.client.ClientServicesException;
import com.ibm.sbt.services.endpoints.js.JSReference;
/**
* Bean that provides a authentication using an LTPA token.
*
* @author Philippe Riand
* @author Niklas Heidloff
* @author Paul Bastide <pbastide@us.ibm.com>
*/
public class SSOEndpoint extends AbstractEndpoint {
private static final long serialVersionUID = 1L;
private String domain = null;
private String authenticationPage = null;
static final String sourceClass = SSOEndpoint.class.getName();
static final Logger logger = Logger.getLogger(sourceClass);
public SSOEndpoint() {
}
@Override
public String getAuthType(){
return "sso";
}
@Override
public boolean isAuthenticated() throws ClientServicesException {
return true;
}
@Override
public void authenticate(boolean force) throws ClientServicesException {
if(force || !isAuthenticated()) {
String authPage = getAuthenticationPage();
Context context = Context.get();
if(StringUtil.isNotEmpty(authPage)) {
try{
if(!UrlUtil.isAbsoluteUrl(authPage)){
authPage = UrlUtil.makeUrlAbsolute((HttpServletRequest)context.getHttpRequest(), authPage);
}
String redirectUrl = UrlUtil.getRequestUrl((HttpServletRequest)context.getHttpRequest());// change needed to handle portlethttprequest
String endPointName = authPage.substring(authPage.indexOf("=")+1, authPage.length());
String baseUrl = UrlUtil.getBaseUrl(((HttpServletRequest)context.getHttpRequest()));
String servletPath = ServiceServlet.getServletPath();
String basicProxyUrl = AuthCredsHandler.URL_PATH;
// constructing proxy action url
String postToProxy = PathUtil.concat(baseUrl, servletPath, '/');
postToProxy = PathUtil.concat(postToProxy,basicProxyUrl, '/');
postToProxy = PathUtil.concat(postToProxy,endPointName, '/');
postToProxy = PathUtil.concat(postToProxy,"JavaApp", '/');
// encode URL's
postToProxy = URLEncoder.encode(postToProxy,"UTF-8");
redirectUrl = URLEncoder.encode(redirectUrl,"UTF-8");
// passing proxy action url as a parameter to the authentication page
authPage = PathUtil.concat(authPage,"proxyPath",'&');
authPage = PathUtil.concat(authPage,postToProxy,'=');
// passing redirectURL as a parameter to the authentication page
authPage = PathUtil.concat(authPage,"redirectURL",'&');
authPage = PathUtil.concat(authPage,redirectUrl,'=');
context.sendRedirect(authPage);
} catch (IOException e) {
throw new ClientServicesException(e,"LTPA token refresh failed because: "+e.getMessage());
}
} else {
throw new ClientServicesException(null,"LTPA token expired or invalid. Cannot refresh: authentication page is not set");
}
}
}
@Override
public JSReference getAuthenticator(String endpointName, String sbtUrl) {
JSReference reference = new JSReference("sbt/authenticator/SSO");
reference.getProperties().put("url", sbtUrl);
return reference;
}
public void redirect() {
}
@Override
public void initialize(DefaultHttpClient httpClient) {
HttpRequestInterceptor ltpaInterceptor = new LtpaInterceptor(getUrl(), getDomain());
httpClient.addRequestInterceptor(ltpaInterceptor, 0);
}
private static class LtpaInterceptor implements HttpRequestInterceptor {
String _domain;
boolean siteminder = false;
public LtpaInterceptor(String url, String domain) {
/**
* Customer Reported Issue with SiteMinder
* SSO Endpoint is extended with SMSESSION
*
* REF: Must add to managed-beans.xml
* <managed-property><property-name>siteminder</property-name><value>true</value></managed-property>
*/
Context ctx = Context.get();
String siteminderProp = ctx.getProperty("siteminder");
if((siteminderProp != null) && (siteminderProp.compareTo("true")==0)){
siteminder = true;
}
if (domain == null) {
try {
URL u = new URL(url);
_domain = u.getHost();
//handles ipv6 hosts
if (_domain.startsWith("[") && _domain.endsWith("]")) {
_domain = _domain.substring(0, _domain.length()-1);
}
} catch (Exception e ) {
//fall back for incomplete urls
_domain = url.substring(url.indexOf("//")+2);
if(_domain.indexOf(":")!=-1) {
_domain = _domain.substring(0, _domain.indexOf(":"));
}
}
// Fix in calculating the domain correctly
// If the host is qs.renovations.com, domain should be renovations.com and not qs.renovations.com
if(StringUtil.isNotEmpty(_domain) && countMatch(_domain,'.')>1){
_domain = _domain.substring(_domain.indexOf('.')+1,_domain.length());
}
} else {
_domain = domain;
}
if (logger.isLoggable(Level.INFO)) {
String msg = MessageFormat.format("SSO endpoint domain for {0} is {1}", url, _domain);
logger.log(Level.INFO, msg);
}
}
private static int countMatch(String source, char match) {
int count=0;
if(StringUtil.isNotEmpty(source)) {
for(int i=0; i<source.length(); i++) {
if(StringUtil.equals(source.charAt(i), match)) {
count++;
}
}
}
return count;
}
public String getRawCookieValue(javax.servlet.http.Cookie cookie, HttpServletRequest request) {
try {
String header = request.getHeader("cookie");
String[] headerParts = StringUtil.splitString(header, "; ", false);
String name = cookie.getName();
for (int i=0; i<headerParts.length; i++) {
String cookieHeader = headerParts[i];
if (cookieHeader.startsWith(name)) {
String value = cookieHeader.substring(name.length()+1);
return value;
}
}
} catch (Exception e) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "Unable to parse cookie header", e);
}
}
return cookie.getValue();
}
@Override
@SuppressWarnings("unchecked")
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
CookieStore cookieStore;
cookieStore = new BasicCookieStore();
Context ctx = Context.get();
boolean ltpaTokenFound = false;
java.util.Map<java.lang.String, java.lang.Object> cookieMap = ctx.getRequestCookieMap();
if(cookieMap.containsKey("LtpaToken")) {
javax.servlet.http.Cookie cookie = (javax.servlet.http.Cookie) cookieMap.get("LtpaToken");
BasicClientCookie2 cookie1 = new BasicClientCookie2(cookie.getName(), getRawCookieValue(cookie, ctx.getHttpRequest()));
if(cookie.getDomain()!=null) {
cookie1.setDomain(cookie.getDomain());
}
else {
cookie1.setDomain(_domain);
}
if(cookie.getPath()!=null) {
cookie1.setPath(cookie.getPath());
}
else {
cookie1.setPath("/");
}
cookieStore.addCookie(cookie1);
ltpaTokenFound = true;
}
if(cookieMap.containsKey("LtpaToken2")) {
javax.servlet.http.Cookie cookie = (javax.servlet.http.Cookie) cookieMap.get("LtpaToken2");
BasicClientCookie2 cookie2 = new BasicClientCookie2(cookie.getName(), getRawCookieValue(cookie, ctx.getHttpRequest()));
if(cookie.getDomain()!=null) {
cookie2.setDomain(cookie.getDomain());
}
else {
cookie2.setDomain(_domain);
}
if(cookie.getPath()!=null) {
cookie2.setPath(cookie.getPath());
}
else {
cookie2.setPath("/");
}
cookieStore.addCookie(cookie2);
ltpaTokenFound = true;
}
/**
* Customer Reported Issue with SiteMinder
* SSO Endpoint is extended with SMSESSION
*
* REF: shortcircuits using siteminder context check
*/
if(siteminder && cookieMap.containsKey("SMSESSION")) {
javax.servlet.http.Cookie cookie = (javax.servlet.http.Cookie) cookieMap.get("SMSESSION");
BasicClientCookie2 cookie2 = new BasicClientCookie2(cookie.getName(), getRawCookieValue(cookie, ctx.getHttpRequest()));
if(cookie.getDomain()!=null) {
cookie2.setDomain(cookie.getDomain());
}
else {
cookie2.setDomain(_domain);
}
if(cookie.getPath()!=null) {
cookie2.setPath(cookie.getPath());
}
else {
cookie2.setPath("/");
}
cookieStore.addCookie(cookie2);
ltpaTokenFound = true;
}
if (!ltpaTokenFound && logger.isLoggable(Level.INFO)) {
String uri = "";
try {
uri = request.getRequestLine().getUri();
} catch (Exception e) {}
logger.log(Level.INFO, "Unable to find LTPA token for request "+uri);
}
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}
}
@Override
public void logout() throws AuthenticationException {
// TODO Auto-generated method stub
}
public String getAuthenticationPage() {
return authenticationPage;
}
public void setAuthenticationPage(String authenticationPage) {
this.authenticationPage = authenticationPage;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}