blob: 6cac4bfccfb9b2c8f53551d58dff64d552845217 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.scout.commons.Base64Utility;
import org.eclipse.scout.commons.EncryptionUtility;
import org.eclipse.scout.commons.SoapHandlingUtility;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.commons.security.SimplePrincipal;
import org.eclipse.scout.http.servletfilter.FilterConfigInjection;
import org.eclipse.scout.http.servletfilter.security.SecureHttpServletRequestWrapper;
import org.eclipse.scout.rt.server.internal.Activator;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Transformation filter used to create a subject based on the soap headers containing a virtual request (from ajax,
* rap)
* <p>
* If there is already a subject set as {@link Subject#getSubject(java.security.AccessControlContext)} then the filter
* is transparent.
* <p>
* Reads the soap wsse header and transforms it to a subject with the user principal. The password is the triple-des
* encoding of "${timestamp}:${username}" using the config.ini parameter <code>scout.ajax.token.key</code>
* <p>
* Normally this filters the alias /ajax
* <p>
* This filter is registered in the scout server plugin.xml as /ajax by default with order 1'000'010 and has the active
* flag set to true
*/
public class SoapWsseJaasFilter implements Filter {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(SoapWsseJaasFilter.class);
private static final byte[] tripleDesKey;
static {
String key = Activator.getDefault().getBundle().getBundleContext().getProperty("scout.ajax.token.key");
if (key == null) {
tripleDesKey = null;
}
else {
tripleDesKey = new byte[24];
byte[] keyBytes;
try {
keyBytes = key.getBytes("UTF-8");
System.arraycopy(keyBytes, 0, tripleDesKey, 0, Math.min(keyBytes.length, tripleDesKey.length));
}
catch (UnsupportedEncodingException e) {
LOG.error("reading property 'scout.ajax.token.key'", e);
}
}
}
private SAXParserFactory m_saxParserFactory;
private FilterConfigInjection m_injection;
@Override
public void init(FilterConfig config0) throws ServletException {
m_injection = new FilterConfigInjection(config0, getClass());
try {
m_saxParserFactory = SoapHandlingUtility.createSaxParserFactory();
}
catch (Exception e) {
throw new ServletException(e);
}
}
@Override
public void destroy() {
m_saxParserFactory = null;
m_injection = null;
}
@Override
public void doFilter(ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
if (isSubjectSet()) {
chain.doFilter(request, response);
return;
}
FilterConfigInjection.FilterConfig config = m_injection.getConfig(request);
if (!config.isActive()) {
chain.doFilter(request, response);
return;
}
InputStream httpIn = request.getInputStream();
ByteArrayOutputStream cacheOut = new ByteArrayOutputStream();
Subject subject;
try {
subject = parseSubject(httpIn, cacheOut);
}
catch (Throwable t) {
LOG.warn("WS-Security check", t);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
final InputStream cacheIn = new ByteArrayInputStream(cacheOut.toByteArray());
cacheOut = null;
//
final HttpServletRequestWrapper replayRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
@Override
public int read() throws IOException {
return cacheIn.read();
}
};
}
};
continueChainWithPrincipal(subject, replayRequest, (HttpServletResponse) response, chain);
}
private void continueChainWithPrincipal(Subject subject, final HttpServletRequest req, final HttpServletResponse res, final FilterChain chain) throws IOException, ServletException {
try {
Subject.doAs(
subject,
new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
Principal principal = Subject.getSubject(AccessController.getContext()).getPrincipals().iterator().next();
HttpServletRequest secureReq = new SecureHttpServletRequestWrapper(req, principal);
chain.doFilter(secureReq, res);
return null;
}
}
);
}
catch (PrivilegedActionException e) {
Throwable t = e.getCause();
if (t instanceof IOException) {
throw (IOException) t;
}
else if (t instanceof ServletException) {
throw (ServletException) t;
}
else {
throw new ServletException(t);
}
}
}
protected Subject parseSubject(final InputStream httpIn, final ByteArrayOutputStream cacheOut) throws Exception {
InputStream filterIn = new InputStream() {
@Override
public int read() throws IOException {
int ch = httpIn.read();
if (ch < 0) {
return ch;
}
cacheOut.write(ch);
return ch;
}
@Override
public int read(byte[] b) throws IOException {
int n = httpIn.read(b);
if (n <= 0) {
return n;
}
cacheOut.write(b, 0, n);
return n;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = httpIn.read(b, off, len);
if (n <= 0) {
return n;
}
cacheOut.write(b, off, n);
return n;
}
};
WSSEUserTokenHandler handler = new WSSEUserTokenHandler();
SoapHandlingUtility.createSaxParser(m_saxParserFactory).parse(new InputSource(filterIn), handler);
return createSubject(cleanString(handler.user), cleanString(handler.tokenRaw), cleanString(handler.tokenEncoding));
}
/**
* override this method to do additional checks on credentials
*/
protected Subject createSubject(String user, String tokenRaw, String tokenEncoding) throws Exception {
if (user == null || tokenRaw == null) {
LOG.error("Ajax back-end call contains no ws-security token. Check if the config.ini of the /rap and the /ajax webapp contains the property 'scout.ajax.token.key'.");
throw new SecurityException("SOAP header contains no ws-security token");
}
byte[] token = Base64Utility.decode(tokenRaw);
String msg = new String(EncryptionUtility.decrypt(token, tripleDesKey), "UTF-8");
String[] tupel = msg.split(":", 2);
long timestamp = Long.parseLong(tupel[0]);
String userRef = tupel[1];
if (timestamp < 0L || userRef == null || !userRef.equals(user)) {
throw new SecurityException("SOAP header contains no ws-security token");
}
Subject subject = new Subject();
subject.getPrincipals().add(new SimplePrincipal(user));
subject.setReadOnly();
return subject;
}
private boolean isSubjectSet() {
Subject subject = Subject.getSubject(AccessController.getContext());
if (subject == null) {
return false;
}
if (subject.getPrincipals().size() == 0) {
return false;
}
String name = subject.getPrincipals().iterator().next().getName();
if (name == null || name.trim().length() == 0) {
return false;
}
return true;
}
private String cleanString(String s) {
if (s == null) {
return null;
}
s = s.trim();
if (s.length() == 0) {
return null;
}
if (s.equalsIgnoreCase("null")) {
return null;
}
return s;
}
/**
* <pre>
* <wsse:Security soapenv:mustUnderstand="1">
* <wsse:UsernameToken>
* <wsse:Username>user</wsse:Username>
* <wsse:Password Type="http://...#Virtual">QDgxVkqYnuqk...==</wsse:Password>
* </wsse:UsernameToken>
* </wsse:Security>
* </pre>
*/
private static class WSSEUserTokenHandler extends DefaultHandler {
public String user;
public String tokenEncoding;
public String tokenRaw;
//
private boolean insideEnvelope;
private boolean insideHeader;
private boolean insideSecurity;
private boolean insideUsernameToken;
private boolean insideUsername;
private boolean insidePasswort;
private boolean done;
@Override
public void startElement(String namespaceURI, String localName, String qNameText, Attributes attributes) throws SAXException {
if (done) {
return;
}
QName qname = new QName(namespaceURI, localName);
if (SoapHandlingUtility.SOAPENV_ENVELOPE_ELEMENT.equals(qname)) {
insideEnvelope = true;
return;
}
if (SoapHandlingUtility.SOAPENV_HEADER_ELEMENT.equals(qname)) {
insideHeader = true;
return;
}
if (SoapHandlingUtility.WSSE_SECURITY_ELEMENT.equals(qname)) {
insideSecurity = true;
return;
}
if (SoapHandlingUtility.WSSE_USERNAME_TOKEN_ELEMENT.equals(qname)) {
insideUsernameToken = true;
return;
}
if (SoapHandlingUtility.WSSE_USERNAME_ELEMENT.equals(qname)) {
insideUsername = true;
return;
}
if (SoapHandlingUtility.WSSE_PASSWORD_ELEMENT.equals(qname)) {
insidePasswort = true;
tokenEncoding = attributes.getValue("", SoapHandlingUtility.WSSE_PASSWORD_TYPE_ATTRIBUTE);
return;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (done) {
return;
}
if (insideEnvelope && insideHeader && insideSecurity && insideUsernameToken) {
if (insideUsername) {
user = new String(ch, start, length);
}
if (insidePasswort) {
tokenRaw = new String(ch, start, length);
}
}
}
@Override
public void endElement(String namespaceURI, String localName, String qNameText) throws SAXException {
if (done) {
return;
}
QName qname = new QName(namespaceURI, localName);
if (SoapHandlingUtility.SOAPENV_ENVELOPE_ELEMENT.equals(qname)) {
insideEnvelope = false;
done = true;
return;
}
if (SoapHandlingUtility.SOAPENV_HEADER_ELEMENT.equals(qname)) {
insideHeader = false;
return;
}
if (SoapHandlingUtility.WSSE_SECURITY_ELEMENT.equals(qname)) {
insideSecurity = false;
return;
}
if (SoapHandlingUtility.WSSE_USERNAME_TOKEN_ELEMENT.equals(qname)) {
insideUsernameToken = false;
return;
}
if (SoapHandlingUtility.WSSE_USERNAME_ELEMENT.equals(qname)) {
insideUsername = false;
return;
}
if (SoapHandlingUtility.WSSE_PASSWORD_ELEMENT.equals(qname)) {
insidePasswort = false;
return;
}
}
}
}