/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.cas;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* An abstract helper class for authentication against a Cas server
*
*
* @author christian
*
*/
public abstract class CasAuthenticationHelper {
protected URL casUrlPrefix;
/**
* true for an SSL (TLS) connection
*/
protected boolean secure;
protected HttpCookie ticketGrantingCookie,warningCookie;
/**
* casUrlPrefix is the CAS Server URL including
* context root
*
* @param casUrlPrefix
*/
public CasAuthenticationHelper (URL casUrlPrefix) {
secure="HTTPS".equalsIgnoreCase(casUrlPrefix.getProtocol());
this.casUrlPrefix=casUrlPrefix;
}
/**
* create URL from a CAS protocol URI
*
* @param casUri
*
*/
protected URL createURLFromCasURI(String casUri) {
URL retValue=null;
try {
retValue = new URL(casUrlPrefix.getProtocol(),casUrlPrefix.getHost(),
casUrlPrefix.getPort(),casUrlPrefix.getPath()+casUri);
} catch (MalformedURLException e) {
throw new RuntimeException("Cannot build url from "+casUrlPrefix.toExternalForm()+
" and "+casUri);
}
return retValue;
}
protected String readResponse(HttpURLConnection conn) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
StringBuffer buff=new StringBuffer();
while((line=in.readLine())!=null) {
buff.append(line);
}
in.close();
return buff.toString();
}
protected List<String> getResponseHeaderValues(HttpURLConnection conn,String hName) {
List<String> result = new ArrayList<String>();
for (int i=0; ; i++) {
String headerName = conn.getHeaderFieldKey(i);
String headerValue = conn.getHeaderField(i);
if (headerName == null && headerValue == null) {
// No more headers
break;
}
if (hName.equalsIgnoreCase(headerName)) {
result.add(headerValue);
}
}
return result;
}
protected List<HttpCookie> getCookies(HttpURLConnection conn) {
List<HttpCookie> result = new ArrayList<HttpCookie>();
List<String> cookieStrings = getResponseHeaderValues(conn, "Set-Cookie");
for (String cookieString : cookieStrings) {
result.addAll(HttpCookie.parse("Set-Cookie: "+cookieString));
}
cookieStrings = getResponseHeaderValues(conn, "Set-Cookie2");
for (String cookieString : cookieStrings) {
result.addAll(HttpCookie.parse("Set-Cookie2: "+cookieString));
}
return result;
}
protected HttpCookie getCookieNamed(List<HttpCookie> cookies, String cookieName) {
for (HttpCookie c : cookies) {
if (c.getName().equalsIgnoreCase(cookieName))
return c;
}
return null;
}
protected void writeParamsForPostAndSend(HttpURLConnection conn, Map<String,String> paramMap) throws IOException{
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
StringBuffer buff = new StringBuffer();
for (Entry<String,String> entry : paramMap.entrySet()) {
if (buff.length()>0)
buff.append("&");
buff.append(entry.getKey()).append("=").
append(URLEncoder.encode(entry.getValue(),"utf-8"));
}
out.writeBytes(buff.toString());
out.flush();
out.close();
}
public HttpCookie getTicketGrantingCookie() {
return ticketGrantingCookie;
}
public HttpCookie getWarningCookie() {
return warningCookie;
}
/**
* Single logout from Cas server
*
*
* @throws IOException
*/
public boolean ssoLogout() throws IOException {
if (!secure) return true;
if (ticketGrantingCookie==null) return true;
URL logoutUrl = createURLFromCasURI(GeoServerCasConstants.LOGOUT_URI);
HttpURLConnection conn = (HttpURLConnection) logoutUrl.openConnection();
addCasCookies(conn);
conn.getInputStream().close();
extractCASCookies(getCookies(conn),conn);
return getTicketGrantingCookie()!=null &&
"\"\"".equals(getTicketGrantingCookie().getValue());
}
/**
* add Cas cookies to request
*
* @param conn
*/
protected void addCasCookies(HttpURLConnection conn) {
String cookieString="";
if (checkCookieForSend(warningCookie))
cookieString=warningCookie.toString();
if (checkCookieForSend(ticketGrantingCookie)) {
if (cookieString.length()> 0)
cookieString+=",";
cookieString+=ticketGrantingCookie.toString();
}
if (cookieString.length() > 0)
conn.setRequestProperty("Cookie", cookieString);
}
public boolean isSecure() {
return secure;
}
protected boolean checkCookieForSend(HttpCookie cookie) {
if (cookie==null) return false;
if (cookie.hasExpired()) return false;
if (isSecure()==false && cookie.getSecure()) {
return false;
}
return true;
}
/**
* The concrete login, after sucessful login,
* the cookies should be set using {@link #extractCASCookies(List, HttpURLConnection)}
*
*
* @throws IOException
*/
public abstract boolean ssoLogin() throws IOException;
/**
* Get a service ticket for the service
*
* Precondition:
* successful log in wiht {@link #ssoLogin()}
* {@link #isSecure()} == true
*
* @param service
*
* @throws IOException
*/
public String getServiceTicket(URL service) throws IOException{
if (getTicketGrantingCookie()==null || getTicketGrantingCookie().getValue().isEmpty()) {
throw new IOException("na valid TGC ");
}
URL loginUrl = createURLFromCasURI(GeoServerCasConstants.LOGIN_URI+"?service="+service.toExternalForm());
HttpURLConnection conn = (HttpURLConnection) loginUrl.openConnection();
conn.setInstanceFollowRedirects(false);
addCasCookies(conn);
conn.getInputStream().close();
List<String> values = getResponseHeaderValues(conn, "Location");
if (values.isEmpty()) {
throw new IOException("No redirect received for " + loginUrl);
}
String redirectURL=values.get(0);
String ticket = null;
URL rURL = new URL(redirectURL);
for (String kvp : rURL.getQuery().split("&")) {
String[] tmp = kvp.split("=");
if ("ticket".equalsIgnoreCase((tmp[0]).trim())) {
ticket=tmp[1].trim();
break;
}
}
return ticket;
}
/**
* extract Cas cookies from all received cookies
*
* @param cookies
* @param conn
*/
public void extractCASCookies(List<HttpCookie> cookies,HttpURLConnection conn) {
warningCookie=getCookieNamed(cookies, "CASPRIVACY");
ticketGrantingCookie=getCookieNamed(cookies, "CASTGC");
}
}