/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Stephane Lacoin (aka matic)
*/
package org.nuxeo.ecm.core.opencmis.impl.client.sso;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.junit.Test;
/**
* Manages the web flow for authenticating a client with CAS.
*
* @author matic
*/
public class CasGreeter {
protected final HttpClient client;
protected final String location;
protected String ticket;
public CasGreeter(HttpClient client, String location) {
this.client = client;
this.location = location;
}
protected final Pattern loginTicketInputPattern = Pattern.compile(".*(<input.+name=\"lt\"[^<]*>).*", Pattern.DOTALL
| Pattern.CASE_INSENSITIVE);
protected final Pattern loginTicketValuePattern = Pattern.compile(".*value=\"(.*)\".*", Pattern.DOTALL
| Pattern.CASE_INSENSITIVE);
@Test
public void testExtractLoginTicketValue() {
String content = "\n<input name=\"lt\" value=\"test\"/> <pfff/>";
String value = extractLoginTicket(content);
assertThat("extracted 'test' value", value, is("test"));
}
protected String extractLoginTicket(String content) {
Matcher inputMatcher = loginTicketInputPattern.matcher(content);
if (inputMatcher.matches() == false) {
throw new Error("Cannot extract ticket input");
}
String inputContent = inputMatcher.group(1);
Matcher valueMatcher = loginTicketValuePattern.matcher(inputContent);
if (valueMatcher.matches() == false) {
throw new Error("Cannot find ticket value");
}
String value = valueMatcher.group(1);
return value;
}
protected final Pattern loginLocationFormPattern = Pattern.compile(".*(<form.*id=\"fm1\"[^<]*>).*", Pattern.DOTALL);
protected final Pattern loginLocationActionPattern = Pattern.compile(".*action=(\".*\").*", Pattern.DOTALL);
protected String extractLoginLocation(String content) {
Matcher formMatcher = loginLocationFormPattern.matcher(content);
if (formMatcher.matches() == false) {
throw new Error("Cannot find login form");
}
Matcher actionMatcher = loginLocationActionPattern.matcher(formMatcher.group(1));
if (actionMatcher.matches() == false) {
throw new Error("Cannot find login action");
}
return actionMatcher.group(1);
}
protected final Pattern redirectLinkPattern = Pattern.compile(".*(<a.*href=\".*?ticket=.*\"[^<]*>).*",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
protected final Pattern redirectValuePattern = Pattern.compile(".*href=\"(.*)\".*", Pattern.DOTALL
| Pattern.CASE_INSENSITIVE);
@Test
public void testExtractRedirectLink() {
String content = "\n<input name=\"lt\" value=\"test\"/> <pfff/>";
String value = extractLoginTicket(content);
assertThat("extracted 'test' value", value, is("test"));
}
protected String extractRedirectLink(String content) {
Matcher inputMatcher = redirectLinkPattern.matcher(content);
if (inputMatcher.matches() == false) {
throw new Error("Cannot extract redirect link");
}
String inputContent = inputMatcher.group(1);
Matcher valueMatcher = redirectValuePattern.matcher(inputContent);
if (valueMatcher.matches() == false) {
throw new Error("Cannot extract redirect value");
}
String value = valueMatcher.group(1);
return value;
}
public String fetchServiceTicket(HttpMethod page) throws HttpException, IOException {
client.executeMethod(page);
try {
if (page.getStatusCode() != HttpStatus.SC_OK) {
throw new Error("Cannot get login form");
}
return extractLoginTicket(page.getResponseBodyAsString());
} finally {
page.releaseConnection();
}
}
public String fetchServiceLocation(HttpMethod page) throws HttpException, IOException {
client.executeMethod(page);
try {
if (page.getStatusCode() != HttpStatus.SC_OK) {
throw new Error("Cannot authenticate");
}
return extractRedirectLink(page.getResponseBodyAsString());
} finally {
page.releaseConnection();
}
}
abstract class Page {
HttpMethod method;
String bodyContent;
String location() {
try {
return method.getURI().getURI();
} catch (URIException e) {
throw new Error("Cannot access to method location");
}
}
abstract Page handleNewContent(String... args);
void handleNewParams(String... args) {
}
Page next(String... args) {
handleNewParams(args);
try {
client.executeMethod(method);
} catch (Exception e) {
throw new Error("execution error", e);
}
try {
if (method.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
method.releaseConnection();
String location = method.getResponseHeader("Location").getValue();
method = new GetMethod(location);
client.executeMethod(method);
}
if (method.getStatusCode() != HttpStatus.SC_OK) {
throw new Error("server error " + method.getStatusLine());
}
bodyContent = method.getResponseBodyAsString();
} catch (Exception e) {
throw new Error("no content", e);
} finally {
method.releaseConnection();
}
return handleNewContent(args);
}
String getTicketGranting() {
for (Cookie cookie : client.getState().getCookies()) {
if ("CASTGC".equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}
boolean hasTicketGranting() {
return getTicketGranting() != null;
}
}
class InitialPage extends Page {
InitialPage(String location) {
method = new GetMethod(location);
}
public void setProxyTicket(String ticket, String proxy, String service) throws HttpException, IOException {
method.setQueryString(new NameValuePair[] { new NameValuePair("ticket", ticket),
new NameValuePair("proxy", proxy), new NameValuePair("service", service) });
}
String extractLoginTicket() {
return CasGreeter.this.extractLoginTicket(bodyContent);
}
@Override
void handleNewParams(String... args) {
if (args.length == 1) {
try {
setProxyTicket(args[0], args[1], args[2]);
} catch (Exception e) {
throw new Error("Cannot set ticket granting");
}
}
}
@Override
Page handleNewContent(String... args) {
if (hasTicketGranting()) {
return new ServicePage(location());
}
return new CredentialsPage(location());
}
}
class CredentialsPage extends Page {
CredentialsPage(String location) {
method = new PostMethod(location);
}
void setParams(String ticket, String username, String password) {
((PostMethod) method).setRequestBody(new NameValuePair[] { new NameValuePair("lt", ticket),
new NameValuePair("username", username), new NameValuePair("password", password),
new NameValuePair("_eventId", "submit"), new NameValuePair("submit", "LOGIN") });
}
@Override
void handleNewParams(String... args) {
setParams(args[0], args[1], args[2]);
}
@Override
Page handleNewContent(String... args) {
if (hasTicketGranting()) {
return new ServicePage(location());
}
return new CredentialsPage(location());
}
}
class ServicePage extends Page {
ServicePage(String location) {
method = new GetMethod(location);
}
@Override
ServicePage handleNewContent(String... args) {
return this;
}
}
public String credsLogon(String username, String password) throws HttpException, IOException {
InitialPage initialPage = new InitialPage(location);
Page credentialsPage = initialPage.next();
Page servicePage = credentialsPage.next(initialPage.extractLoginTicket(), username, password);
return servicePage.getTicketGranting();
}
public String proxyLogon(String ticket, String proxy, String service) throws IllegalArgumentException,
HttpException, IOException {
InitialPage initialPage = new InitialPage(location);
initialPage.setProxyTicket(ticket, proxy, service);
Page servicePage = initialPage.next();
return servicePage.getTicketGranting();
}
}