changeset 5956:259ed81d47e9

merge
author Tom Gottfried <tom.gottfried@intevation.de>
date Wed, 08 May 2013 18:23:50 +0200
parents b819209732a0 (current diff) 24dc13ac8e6c (diff)
children 7b0db743f074
files gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Assertion.java gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Namespaces.java gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Signature.java gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/User.java
diffstat 19 files changed, 852 insertions(+), 498 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/AuthenticationServlet.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,89 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3) 
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details. 
+ */
+
+package org.dive4elements.river.client.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.log4j.Logger;
+
+import org.dive4elements.river.client.server.auth.User;
+import org.dive4elements.river.client.server.auth.UserClient;
+
+/**
+ * Base class for servlets performing authentication and login.
+ */
+public class AuthenticationServlet extends HttpServlet {
+
+    private static Logger logger = Logger.getLogger(AuthenticationServlet.class);
+
+    private static final String FLYS_PAGE = "FLYS.html";
+    private static final String LOGIN_PAGE = "login.jsp";
+
+    protected void redirectFailure(HttpServletResponse resp, String path)
+        throws IOException {
+        resp.sendRedirect(path + "/" + LOGIN_PAGE);
+    }
+
+    protected void redirectFailure(HttpServletResponse resp, String path,
+            Exception e) throws IOException {
+        this.redirectFailure(resp, path, e.getMessage());
+    }
+
+    protected void redirectFailure(HttpServletResponse resp, String path,
+            String message) throws IOException {
+        resp.sendRedirect(path + "/" + LOGIN_PAGE + "?error=" + message);
+    }
+
+    protected void redirectSuccess(HttpServletResponse resp, String path,
+            String uri) throws IOException {
+        if (uri == null) {
+            String redirecturl = getServletContext().getInitParameter("redirect-url");
+            if (redirecturl == null) {
+                redirecturl = FLYS_PAGE;
+            }
+            uri = "/" + redirecturl;
+        }
+        resp.sendRedirect(uri);
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException {
+        logger.debug("Processing get request");
+        this.redirectFailure(resp, req.getContextPath());
+    }
+
+    protected void performLogin(HttpServletRequest req,
+                                HttpServletResponse resp, User user)
+                                    throws ServletException, IOException {
+        String url = getServletContext().getInitParameter("server-url");
+        UserClient client = new UserClient(url);
+        if (!client.userExists(user)) {
+            logger.debug("Creating db user");
+            if (!client.createUser(user)) {
+                this.redirectFailure(resp, req.getContextPath(),
+                                     "Could not create new user");
+                return;
+            }
+        }
+
+        HttpSession session = req.getSession();
+        session.setAttribute("user", user);
+
+        String uri = (String)session.getAttribute("requesturi");
+
+        this.redirectSuccess(resp, req.getContextPath(), uri);
+    }
+}
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/LoginServlet.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/LoginServlet.java	Wed May 08 18:23:50 2013 +0200
@@ -12,61 +12,20 @@
 
 import javax.servlet.ServletException;
 import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 
 import org.apache.log4j.Logger;
 
 import org.dive4elements.river.client.server.auth.Authentication;
 import org.dive4elements.river.client.server.auth.AuthenticationException;
 import org.dive4elements.river.client.server.auth.AuthenticationFactory;
-import org.dive4elements.river.client.server.auth.User;
-import org.dive4elements.river.client.server.auth.UserClient;
 import org.dive4elements.river.client.server.features.Features;
 
-public class LoginServlet extends HttpServlet {
+public class LoginServlet extends AuthenticationServlet {
 
     private static Logger logger = Logger.getLogger(LoginServlet.class);
 
-    private static final String FLYS_PAGE = "FLYS.html";
-    private static final String LOGIN_PAGE = "login.jsp";
-
-    private void redirectFailure(HttpServletResponse resp, String path)
-        throws IOException {
-        resp.sendRedirect(path + "/" + LOGIN_PAGE);
-    }
-
-    private void redirectFailure(HttpServletResponse resp, String path,
-            Exception e) throws IOException {
-        this.redirectFailure(resp, path, e.getMessage());
-    }
-
-    private void redirectFailure(HttpServletResponse resp, String path,
-            String message) throws IOException {
-        resp.sendRedirect(path + "/" + LOGIN_PAGE + "?error=" + message);
-    }
-
-    private void redirectSuccess(HttpServletResponse resp, String path,
-            String uri) throws IOException {
-        if (uri == null) {
-            String redirecturl = getServletContext().getInitParameter("redirect-url");
-            if (redirecturl == null) {
-                redirecturl = FLYS_PAGE;
-            }
-            uri = "/" + redirecturl;
-        }
-        resp.sendRedirect(uri);
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-    throws ServletException, IOException {
-        logger.debug("Processing get request");
-        this.redirectFailure(resp, req.getContextPath());
-    }
-
     @Override
     protected void doPost(HttpServletRequest req, HttpServletResponse resp)
     throws ServletException, IOException
@@ -90,25 +49,7 @@
                 this.redirectFailure(resp, req.getContextPath());
                 return;
             }
-            User user = aresp.getUser();
-
-            String url = getServletContext().getInitParameter("server-url");
-            UserClient client = new UserClient(url);
-            if (!client.userExists(user)) {
-                logger.debug("Creating db user");
-                if (!client.createUser(user)) {
-                    this.redirectFailure(resp, req.getContextPath(),
-                            "Could not create new user");
-                    return;
-                }
-            }
-
-            HttpSession session = req.getSession();
-            session.setAttribute("user", user);
-
-            String uri = (String)session.getAttribute("requesturi");
-
-            this.redirectSuccess(resp, req.getContextPath(), uri);
+            this.performLogin(req, resp, aresp.getUser());
         }
         catch(AuthenticationException e) {
             logger.error(e, e);
@@ -123,6 +64,6 @@
         Features features = (Features)sc.getAttribute(Features.CONTEXT_ATTRIBUTE);
         String auth = sc.getInitParameter("authentication");
         return AuthenticationFactory.getInstance(auth).auth(username, password,
-                encoding, features);
+                encoding, features, sc);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/SamlServlet.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,91 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3) 
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details. 
+ */
+
+package org.dive4elements.river.client.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringBufferInputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64InputStream;
+
+import org.apache.log4j.Logger;
+
+import org.dive4elements.river.client.server.auth.AuthenticationException;
+import org.dive4elements.river.client.server.auth.User;
+import org.dive4elements.river.client.server.auth.saml.TicketValidator;
+import org.dive4elements.river.client.server.auth.saml.Assertion;
+import org.dive4elements.river.client.server.features.Features;
+
+
+public class SamlServlet extends AuthenticationServlet {
+
+    private static Logger logger = Logger.getLogger(SamlServlet.class);
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+    {
+        String encoding = req.getCharacterEncoding();
+        String samlTicketXML = req.getParameter("samlTicket");
+
+        logger.debug("Processing post request");
+
+        if (samlTicketXML == null) {
+            logger.debug("No saml ticket provided");
+            this.redirectFailure(resp, req.getContextPath());
+            return;
+        }
+
+        try {
+            User user = this.auth(samlTicketXML);
+            if (user == null) {
+                logger.debug("Authentication not successful");
+                this.redirectFailure(resp, req.getContextPath());
+                return;
+            }
+            this.performLogin(req, resp, user);
+        }
+        catch(AuthenticationException e) {
+            logger.error(e, e);
+            this.redirectFailure(resp, req.getContextPath(), e);
+        }
+    }
+
+    private User auth(String samlTicketXML)
+        throws AuthenticationException, IOException
+    {
+        ServletContext sc = this.getServletContext();
+
+        Assertion assertion = null;
+        try {
+            String keyfile =
+                (String)sc.getInitParameter("saml-trusted-public-key");
+            TicketValidator validator =
+                new TicketValidator(sc.getRealPath(keyfile));
+
+            InputStream in = new StringBufferInputStream(samlTicketXML);
+            assertion = validator.checkTicket(new Base64InputStream(in));
+        }
+        catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+        }
+        if (assertion == null) {
+            throw new AuthenticationException("Login failed.");
+        }
+
+        Features features = (Features)sc.getAttribute(Features.CONTEXT_ATTRIBUTE);
+        return new org.dive4elements.river.client.server.auth.saml.User(
+            assertion, features.getFeatures(assertion.getRoles()), null);
+    }
+}
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/Authenticator.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/Authenticator.java	Wed May 08 18:23:50 2013 +0200
@@ -10,11 +10,16 @@
 
 import java.io.IOException;
 
+import javax.servlet.ServletContext;
+
 import org.dive4elements.river.client.server.features.Features;
 
+
 public interface Authenticator {
 
-    public Authentication auth(String username, String password, String encoding, Features features)
+    public Authentication auth(String username, String password,
+                               String encoding, Features features,
+                               ServletContext context)
         throws AuthenticationException, IOException;
 
 }
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/plain/Authenticator.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/plain/Authenticator.java	Wed May 08 18:23:50 2013 +0200
@@ -20,6 +20,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.servlet.ServletContext;
+
 import org.apache.log4j.Logger;
 
 import org.dive4elements.river.client.server.features.Features;
@@ -88,7 +90,8 @@
         String username,
         String password,
         String encoding,
-        Features features
+        Features features,
+        ServletContext context
     )
     throws AuthenticationException, IOException
     {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/Assertion.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,187 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+
+package org.dive4elements.river.client.server.auth.saml;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.TimeZone;
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathConstants;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * Represents a SAML assertion about a user.
+ */
+public class Assertion {
+
+    private static Logger logger = Logger.getLogger(Assertion.class);
+
+    private Element assertion;
+    private LinkedList<String> roles;
+    private String user_id;
+    private String name_id;
+    private String group_id;
+    private String group_name;
+    private Date notbefore;
+    private Date notonorafter;
+
+    private static final String ATTR_CONT_USER_ID =
+        "urn:conterra:names:sdi-suite:policy:attribute:user-id";
+    private static final String ATTR_CONT_GROUP_ID =
+        "urn:conterra:names:sdi-suite:policy:attribute:group-id";
+    private static final String ATTR_CONT_GROUP_NAME =
+        "urn:conterra:names:sdi-suite:policy:attribute:group-name";
+    private static final String ATTR_CONT_ROLE =
+        "urn:conterra:names:sdi-suite:policy:attribute:role";
+
+
+    public Assertion(Element assertion) {
+        this.assertion = assertion;
+        this.roles = new LinkedList<String>();
+        this.parseCondition();
+        this.parseAttributeStatement();
+    }
+
+    private void parseCondition() {
+        Element conditions = (Element)XPathUtils.xpathNode(this.assertion,
+                                                           "saml:Conditions");
+        if (conditions == null) {
+            logger.error("Cannot find Assertion conditions element");
+            return;
+        }
+
+        this.notbefore = parseDateAttribute(conditions, "NotBefore");
+        if (this.notbefore == null) {
+            logger.warn("Could not extract NotBefore date.");
+        }
+        this.notonorafter = parseDateAttribute(conditions, "NotOnOrAfter");
+        if (this.notonorafter == null) {
+            logger.warn("Could not extract NotOnOrAfter date.");
+        }
+    }
+
+    private Date parseDateAttribute(Element element, String name) {
+        SimpleDateFormat dateformat = new SimpleDateFormat();
+        // format should be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" but that's
+        // only available in java 7+. However, parsing without the
+        // time-zone yields Date values in the local time-zone,
+        // therefore we need to convert to GMT ourselves.
+        dateformat.applyPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
+        String value = element.getAttribute(name);
+        try {
+            return toGMT(dateformat.parse(value));
+        }
+        catch(ParseException e) {
+            logger.error("Cannot parse Condition attribute "
+                         + name + " with value " + value
+                         + " (" + e.getLocalizedMessage() + ")");
+        }
+        return null;
+    }
+
+    private Date toGMT(Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(Calendar.ZONE_OFFSET, 0);
+        cal.set(Calendar.DST_OFFSET, 0);
+        return cal.getTime();
+    }
+
+    private void parseAttributeStatement() {
+        Element attrstatement = (Element)XPathUtils.xpathNode(this.assertion,
+                                                   "saml:AttributeStatement");
+        if (attrstatement == null) {
+            logger.error("Cannot find Assertion AttributeStatement element");
+            return;
+        }
+
+        this.name_id = XPathUtils.xpathString(attrstatement,
+                                              "saml:Subject"
+                                              + "/saml:NameIdentifier");
+
+        this.user_id = getAttrValue(attrstatement, ATTR_CONT_USER_ID);
+        this.group_id = getAttrValue(attrstatement, ATTR_CONT_GROUP_ID);
+        this.group_name = getAttrValue(attrstatement, ATTR_CONT_GROUP_NAME);
+        this.roles = getAttrValues(attrstatement, ATTR_CONT_ROLE);
+    }
+
+    static Object getAttrObject(Element attrs, String name, QName returnType) {
+        return XPathUtils.xpath(attrs,
+                                "saml:Attribute[@AttributeName='" + name + "']"
+                                + "/saml:AttributeValue",
+                                returnType);
+    }
+
+    static String getAttrValue(Element attrs, String name) {
+        return (String)getAttrObject(attrs, name, XPathConstants.STRING);
+    }
+
+    static LinkedList<String> getAttrValues(Element attrs, String name) {
+        LinkedList<String> strings = new LinkedList<String>();
+        NodeList nodes = (NodeList)getAttrObject(attrs, name,
+                                                 XPathConstants.NODESET);
+        for (int i = 0; i < nodes.getLength(); i++) {
+            strings.add(nodes.item(i).getTextContent());
+        }
+
+        return strings;
+    }
+
+    public List<String> getRoles() {
+        return this.roles;
+    }
+
+    public String getUserID() {
+        return this.user_id;
+    }
+
+    public String getNameID() {
+        return this.name_id;
+    }
+
+    public String getGroupID() {
+        return this.group_id;
+    }
+
+    public String getGroupName() {
+        return this.group_name;
+    }
+
+    public Date getFrom() {
+        return this.notbefore;
+    }
+
+    public Date getUntil() {
+        return this.notonorafter;
+    }
+
+    /**
+     * Returns whether the ticket to which the assertion belongs is
+     * valid at the time the method is called. The method returns true,
+     * if both dates (notbefore and notonorafter) have been determined
+     * successfully and the current date/time is between both.
+     * @return Whether the ticket is valid now.
+     */
+    public boolean isValidNow() {
+        Date now = new Date();
+        return (this.notbefore != null && this.notonorafter != null
+                && now.after(this.notbefore)
+                && !this.notonorafter.before(now));
+    }
+}
+// vim: set fileencoding=utf-8 ts=4 sw=4 et si tw=80:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/SamlNamespaceContext.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,106 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+
+package org.dive4elements.river.client.server.auth.saml;
+
+import java.util.Iterator;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+
+/**
+ * The namespace context for SAML documents.
+ */
+public class SamlNamespaceContext implements NamespaceContext
+{
+    /**
+     * The URI of the namespace of SAML assertions.
+     */
+    public static final String SAML_NS_ASSERT =
+        "urn:oasis:names:tc:SAML:1.0:assertion";
+
+    /**
+     * The URI of the namespace of the SAML protocol.
+     */
+    public static final String SAML_NS_PROTO =
+        "urn:oasis:names:tc:SAML:1.0:protocol";
+
+    /**
+     * The URI of the namespace for XML signatures.
+     */
+    public static final String XML_SIG_NS =
+        "http://www.w3.org/2000/09/xmldsig#";
+
+    /**
+     * Final instance to be easily used to avoid creation
+     * of instances.
+     */
+    public static final SamlNamespaceContext INSTANCE =
+        new SamlNamespaceContext();
+
+
+    /**
+     * The default constructor.
+     */
+    public SamlNamespaceContext() {
+    }
+
+
+    /**
+     * @see javax.xml.namespace.NamespaceContext#getNamespaceURI(String)
+     * @param prefix The prefix
+     * @return The corresponing URI
+     */
+    public String getNamespaceURI(String prefix) {
+
+        if (prefix == null) {
+            throw new NullPointerException("Null prefix");
+        }
+
+        if ("saml".equals(prefix)) {
+            return SAML_NS_ASSERT;
+        }
+
+        if ("samlp".equals(prefix)) {
+            return SAML_NS_PROTO;
+        }
+
+        if ("ds".equals(prefix)) {
+            return XML_SIG_NS;
+        }
+
+        if ("xml".equals(prefix)) {
+            return XMLConstants.XML_NS_URI;
+        }
+
+        return XMLConstants.NULL_NS_URI;
+    }
+
+
+    /**
+     * @see javax.xml.namespace.NamespaceContext#getPrefix(String)
+     * @param uri The URI
+     * @return nothing.
+     * @throws java.lang.UnsupportedOperationException
+     */
+    public String getPrefix(String uri) {
+        throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * @see javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String)
+     * @param uri The URI
+     * @return nothing
+     * @throws java.lang.UnsupportedOperationException
+     */
+    public Iterator getPrefixes(String uri) {
+        throw new UnsupportedOperationException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/TicketValidator.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,162 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+
+package org.dive4elements.river.client.server.auth.saml;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Key;
+import java.util.Iterator;
+import java.util.Date;
+import javax.security.cert.X509Certificate;
+import javax.security.cert.CertificateException;
+import javax.xml.crypto.Data;
+import javax.xml.crypto.NodeSetData;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMValidateContext;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.dive4elements.artifacts.httpclient.utils.XMLUtils;
+
+
+/**
+ * Validator for SAML tickets.
+ */
+public class TicketValidator {
+
+    /**
+     * The logger used by the TicketValidator instances.
+     */
+    private static Logger logger = Logger.getLogger(TicketValidator.class);
+
+    /**
+     * The trusted Key for signature checks.
+     */
+    private Key trustedKey;
+
+    /**
+     * Creates a new TicketValidator from a trusted key.
+     * @param trustedKey  The trusted key for the signature checks.
+     */
+    public TicketValidator(Key trustedKey) {
+        this.trustedKey = trustedKey;
+    }
+
+    /**
+     * Creates a new TicketValidator, loading the trusted key from a
+     * file.
+     * @param filename The filename of the X509 certificate containing
+     * the trusted public key.
+     */
+    public TicketValidator(String filename) throws IOException,
+                                                   CertificateException {
+        this.trustedKey = loadKey(filename);
+    }
+
+    /**
+     * Loads the public key from a file containing an X509 certificate.
+     */
+    private Key loadKey(String filename) throws IOException,
+                                                CertificateException {
+        X509Certificate cert = X509Certificate.getInstance(
+                                               new FileInputStream(filename));
+        cert.checkValidity(new Date());
+        return cert.getPublicKey();
+    }
+
+
+    /**
+     * Check the ticket represented by the given DOM element.
+     * @param root the DOM element under which the signature can be
+     * found.
+     * @return The assertion element from the signed data.
+     */
+    public Assertion checkTicket(Element root) throws Exception {
+        markAssertionIdAttributes(root);
+
+        Node signode = XPathUtils.xpathNode(root, ".//ds:Signature");
+
+        DOMValidateContext context = new DOMValidateContext(this.trustedKey,
+                                                            signode);
+        context.setProperty("javax.xml.crypto.dsig.cacheReference", true);
+
+        XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
+        XMLSignature signature = factory.unmarshalXMLSignature(context);
+        if (!signature.validate(context)) {
+            logger.error("Signature of SAML ticket could not be validated.");
+            return null;
+        }
+
+        Element assertionElement = extractAssertion(signature, context);
+        if (assertionElement == null) {
+            logger.error("Could not extract assertion from signed content.");
+            return null;
+        }
+
+        Assertion assertion = new Assertion(assertionElement);
+        if (!assertion.isValidNow()) {
+            logger.error("Ticket is not valid now"
+                         + " (NotBefore: " + assertion.getFrom()
+                         + ", NotOnOrAfter: " + assertion.getUntil());
+            return null;
+        }
+
+        return assertion;
+    }
+
+    /**
+     * Check the ticket read from an InputStream containing a SAML
+     * document.
+     * @param xml InputStream with the SAML ticket as XML
+     * @return The assertion element from the signed data.
+     */
+    public Assertion checkTicket(InputStream in) throws Exception {
+        return checkTicket(XMLUtils.readDocument(in).getDocumentElement());
+    }
+
+    /**
+     * Mark the AssertionID attribute of SAML Assertion elements as ID
+     * attribute, so that the signature checker can resolve the
+     * references properly and find the signed data.
+     */
+    private void markAssertionIdAttributes(Element root) {
+        NodeList nodes = XPathUtils.xpathNodeList(root, "saml:Assertion");
+        for (int i = 0; i < nodes.getLength(); i++) {
+            Element el = (Element)nodes.item(i);
+            el.setIdAttribute("AssertionID", true);
+        }
+    }
+
+    private Element extractAssertion(XMLSignature sig,
+                                     DOMValidateContext context) {
+        for (Object obj: sig.getSignedInfo().getReferences()) {
+            Data data = ((Reference)obj).getDereferencedData();
+            if (data instanceof NodeSetData) {
+                Iterator i = ((NodeSetData)data).iterator();
+                for (int k = 0; i.hasNext(); k++) {
+                    Object node = i.next();
+                    if (node instanceof Element) {
+                        Element el = (Element)node;
+                        if (el.getTagName().equals("Assertion"))
+                            return el;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/User.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,43 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3) 
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details. 
+ */
+
+package org.dive4elements.river.client.server.auth.saml;
+
+import java.util.Date;
+import java.util.List;
+
+import org.dive4elements.river.client.server.auth.DefaultUser;
+import org.dive4elements.river.client.server.auth.saml.Assertion;
+
+public class User
+extends DefaultUser
+implements org.dive4elements.river.client.server.auth.User {
+
+    private Assertion assertion;
+
+    public User(Assertion assertion, List<String> features, String password) {
+        this.setName(assertion.getUserID());
+        this.setAccount(assertion.getNameID());
+        this.setRoles(assertion.getRoles());
+        this.assertion = assertion;
+        this.setAllowedFeatures(features);
+        this.setPassword(password);
+    }
+
+    @Override
+    public boolean hasExpired() {
+        // We could check the validity dates of the assertion here, but
+        // when using this for Single-Sign-On this would lead to the
+        // code in GGInAFilter to re-authenticate with the password
+        // stored in the User object, which isn't known in the case of
+        // Single-Sign-On.
+        return false;
+    }
+}
+
+// vim:set ts=4 sw=4 si et fenc=utf8 tw=80:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/XPathUtils.java	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,85 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+
+package org.dive4elements.river.client.server.auth.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.xpath.XPathConstants;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.dive4elements.artifacts.common.utils.XMLUtils;
+
+
+/**
+ * Convenience methods to evaluate XPath queries on SAML documents. The
+ * methods are just front-ends for the {@link XMLUtils.xpath} method.
+ */
+public class XPathUtils
+{
+    /**
+     * Evaluates an XPath query on a given object and returns the result
+     * as a given type, using SamlNamespaceContext as the namespace
+     * context.
+     * @param root  The object which is used as the root of the tree to
+     * be searched in.
+     * @param query The XPath query
+     * @param returnType The type of the result.
+     * @return The result of type 'returnType' or null if something
+     * went wrong during XPath evaluation.
+     */
+    public static final Object xpath(Object root, String query,
+                                     QName returnType) {
+        return XMLUtils.xpath(root, query, returnType,
+                              SamlNamespaceContext.INSTANCE);
+    }
+
+
+    /**
+     * Evaluates an XPath query on a given object and returns the result
+     * as a String, using SamlNamespaceContext as the namespace context.
+     * @param root  The object which is used as the root of the tree to
+     * be searched in.
+     * @param query The XPath query
+     * @return The result as a String or null if something went wrong
+     * during XPath evaluation.
+     */
+    public static final String xpathString(Object root, String query) {
+        return (String)xpath(root, query, XPathConstants.STRING);
+    }
+
+
+    /**
+     * Evaluates an XPath query on a given object and returns the result
+     * as a Node, using SamlNamespaceContext as the namespace context.
+     * @param root  The object which is used as the root of the tree to
+     * be searched in.
+     * @param query The XPath query
+     * @return The result as a Node or null if something went wrong
+     * during XPath evaluation.
+     */
+    public static final Node xpathNode(Object root, String query) {
+        return (Node)xpath(root, query, XPathConstants.NODE);
+    }
+
+
+    /**
+     * Evaluates an XPath query on a given object and returns the result
+     * as a NodeList, using SamlNamespaceContext as the namespace
+     * context.
+     * @param root  The object which is used as the root of the tree to
+     * be searched in.
+     * @param query The XPath query
+     * @return The result as a NodeList or null if something
+     * went wrong during XPath evaluation.
+     */
+    public static final NodeList xpathNodeList(Object root, String query) {
+        return (NodeList)xpath(root, query, XPathConstants.NODESET);
+    }
+}
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Assertion.java	Wed May 08 18:23:41 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * This file is Free Software under the GNU AGPL (>=v3) 
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details. 
- */
-
-package org.dive4elements.river.client.server.auth.was;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Iterator;
-import java.util.Date;
-import java.util.List;
-import java.util.LinkedList;
-
-import org.apache.log4j.Logger;
-
-import org.jdom.Element;
-
-public class Assertion {
-
-    private static Logger logger = Logger.getLogger(Assertion.class);
-
-    private Element assertion;
-    private LinkedList<String> roles;
-    private String assertion_id;
-    private String user_id;
-    private String name_id;
-    private String group_id;
-    private String group_name;
-    private Date notbefore;
-    private Date notonorafter;
-    private Signature signature;
-
-    private static final String ATTR_CONT_USER_ID =
-        "urn:conterra:names:sdi-suite:policy:attribute:user-id";
-    private static final String ATTR_CONT_GROUP_ID =
-        "urn:conterra:names:sdi-suite:policy:attribute:group-id";
-    private static final String ATTR_CONT_GROUP_NAME =
-        "urn:conterra:names:sdi-suite:policy:attribute:group-name";
-    private static final String ATTR_CONT_ROLE =
-        "urn:conterra:names:sdi-suite:policy:attribute:role";
-
-
-    public Assertion(Element assertion) {
-        this.assertion = assertion;
-        this.roles = new LinkedList<String>();
-
-        this.assertion_id = assertion.getAttributeValue("AssertionID");
-
-        this.parseContition();
-        this.parseAttributeStatement();
-    }
-
-    private void parseContition() {
-        Element condition = this.assertion.getChild("Conditions",
-                Namespaces.SAML_NS_ASSERT);
-        if (condition != null) {
-            SimpleDateFormat dateformat = new SimpleDateFormat();
-            // format should be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" but that's only
-            // available in java 7+
-            dateformat.applyPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
-            String from = condition.getAttributeValue("NotBefore");
-            if (from != null) {
-                try {
-                    this.notbefore = dateformat.parse(from);
-                }
-                catch(ParseException e) {
-                    logger.error("Unknown datetime format for Condition " +
-                            "NotBefore " + from);
-                }
-            }
-
-            String until = condition.getAttributeValue("NotOnOrAfter");
-            logger.debug("Session is valid until " + until);
-            if (until != null) {
-                try {
-                    this.notonorafter = dateformat.parse(until);
-                }
-                catch(ParseException e) {
-                    logger.error("Unknown datetime format for Condition " +
-                            "NotOnOrAfter " + until);
-                }
-            }
-        }
-    }
-
-    private void parseAttributeStatement() {
-        Element attrstatement = this.assertion.getChild("AttributeStatement",
-                Namespaces.SAML_NS_ASSERT);
-        if (attrstatement != null) {
-
-            Element subject = attrstatement.getChild("Subject",
-                    Namespaces.SAML_NS_ASSERT);
-            if (subject != null) {
-                this.name_id = subject.getChildText("NameIdentifier",
-                        Namespaces.SAML_NS_ASSERT);
-            }
-
-            List attributes = attrstatement.getChildren("Attribute",
-                    Namespaces.SAML_NS_ASSERT);
-            for(Iterator i = attributes.iterator(); i.hasNext();) {
-                Element attr = (Element)i.next();
-                String attrname = attr.getAttributeValue("AttributeName");
-                if (attrname.equals(ATTR_CONT_USER_ID)) {
-                    this.user_id = this.getAttributeValue(attr);
-                }
-                else if (attrname.equals(ATTR_CONT_GROUP_ID)) {
-                    this.group_id = this.getAttributeValue(attr);
-                }
-                else if (attrname.equals(ATTR_CONT_GROUP_NAME)) {
-                    this.group_name = this.getAttributeValue(attr);
-                }
-                else if (attrname.equals(ATTR_CONT_ROLE)) {
-                    List roles = attr.getChildren("AttributeValue",
-                            Namespaces.SAML_NS_ASSERT);
-                    for(Iterator j = roles.iterator(); j.hasNext();) {
-                        Element role = (Element)j.next();
-                        this.roles.add(role.getText());
-                    }
-                }
-                else {
-                    logger.debug("Unknown AttributeName " + attrname +
-                            " found while parsing AttributeStatement.");
-                }
-            }
-        }
-    }
-
-    private String getAttributeValue(Element attr) {
-        return attr.getChildText("AttributeValue", Namespaces.SAML_NS_ASSERT);
-    }
-
-    public List<String> getRoles() {
-        return this.roles;
-    }
-
-    public Boolean isValid() {
-        // TODO:
-        // check signature digest
-        // check signature value
-        // check signature cert
-        return false;
-    }
-
-    public Signature getSiganture() {
-        if (this.signature == null) {
-            Element signature = this.assertion.getChild("Signature",
-                    Namespaces.XML_SIG_NS);
-            if (signature != null) {
-                this.signature = new Signature(signature);
-            }
-        }
-        return this.signature;
-    }
-
-    public String getUserID() {
-        return this.user_id;
-    }
-
-    public String getNameID() {
-        return this.name_id;
-    }
-
-    public String getGroupID() {
-        return this.group_id;
-    }
-
-    public String getGroupName() {
-        return this.group_name;
-    }
-
-    public String getID() {
-        return this.assertion_id;
-    }
-
-    public Date getFrom() {
-        return this.notbefore;
-    }
-
-    public Date getUntil() {
-        return this.notonorafter;
-    }
-}
-// vim: set fileencoding=utf-8 ts=4 sw=4 et si tw=80:
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Authenticator.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Authenticator.java	Wed May 08 18:23:50 2013 +0200
@@ -10,6 +10,7 @@
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
+import javax.servlet.ServletContext;
 
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
@@ -32,7 +33,8 @@
         String username,
         String password,
         String encoding,
-        Features features
+        Features features,
+        ServletContext context
     ) throws
         AuthenticationException,
         IOException
@@ -60,7 +62,10 @@
                     return null;
                 }
                 else {
-                    return new Response(entity, username, password, features);
+                    String trustedKey =
+                    (String)context.getInitParameter("saml-trusted-public-key");
+                    return new Response(entity, username, password, features,
+                                        context.getRealPath(trustedKey));
                 }
             }
             catch(GeneralSecurityException e) {
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Namespaces.java	Wed May 08 18:23:41 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * This file is Free Software under the GNU AGPL (>=v3) 
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details. 
- */
-
-package org.dive4elements.river.client.server.auth.was;
-
-import org.jdom.Namespace;
-
-public class Namespaces {
-
-    public static final Namespace SAML_NS_ASSERT =
-        Namespace.getNamespace("urn:oasis:names:tc:SAML:1.0:assertion");
-    public static final Namespace SAML_NS_PROTO =
-        Namespace.getNamespace("urn:oasis:names:tc:SAML:1.0:protocol");
-    public static final Namespace XML_SIG_NS =
-        Namespace.getNamespace("http://www.w3.org/2000/09/xmldsig#");
-
-}
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Response.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Response.java	Wed May 08 18:23:50 2013 +0200
@@ -18,16 +18,20 @@
 
 import org.apache.log4j.Logger;
 
-import org.jdom.Document;
-import org.jdom.Element;
-import org.jdom.JDOMException;
-import org.jdom.input.SAXBuilder;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 
+import org.dive4elements.artifacts.httpclient.utils.XMLUtils;
 import org.dive4elements.river.client.server.auth.Authentication;
 import org.dive4elements.river.client.server.auth.AuthenticationException;
+import org.dive4elements.river.client.server.auth.saml.Assertion;
+import org.dive4elements.river.client.server.auth.saml.XPathUtils;
+import org.dive4elements.river.client.server.auth.saml.TicketValidator;
+import org.dive4elements.river.client.server.auth.saml.User;
 
 import org.dive4elements.river.client.server.features.Features;
 
+
 public class Response implements Authentication {
 
     private static Logger logger = Logger.getLogger(Response.class);
@@ -37,9 +41,12 @@
     private String username;
     private String password;
     private Features features;
+    private String trustedKeyFile;
 
 
-    public Response(HttpEntity entity, String username, String password, Features features) throws AuthenticationException, IOException {
+    public Response(HttpEntity entity, String username, String password,
+                    Features features, String trustedKeyFile)
+        throws AuthenticationException, IOException {
 
         if (entity == null) {
             throw new ServiceException("Invalid response");
@@ -47,36 +54,27 @@
 
         String contenttype = entity.getContentType().getValue();
 
-        try {
-            InputStream in = entity.getContent();
-
-            if (!contenttype.equals("application/vnd.ogc.se_xml")) {
-                // XXX: Assume base64 encoded content.
-                in = new Base64InputStream(in);
-            }
-
-            SAXBuilder builder = new SAXBuilder();
-            Document doc = builder.build(in);
-            Element root = doc.getRootElement();
-            String rname = root.getName();
+        InputStream in = entity.getContent();
 
-            if (rname != null && rname.equals("ServiceExceptionReport")) {
-                throw new ServiceException(root.getChildText("ServiceException"));
-            }
+        if (!contenttype.equals("application/vnd.ogc.se_xml")) {
+            // XXX: Assume base64 encoded content.
+            in = new Base64InputStream(in);
+        }
 
-            this.root = root;
-            this.username = username;
-            this.password = password;
-            this.features = features;
+        Document doc = XMLUtils.readDocument(in);
+        Element root = doc.getDocumentElement();
+        String rname = root.getTagName();
 
+        if (rname != null && rname.equals("ServiceExceptionReport")) {
+            throw new ServiceException(XPathUtils.xpathString(root,
+                                                          "ServiceException"));
         }
-        catch(JDOMException e) {
-            throw new AuthenticationException(e);
-        }
-    }
 
-    public Element getRoot() {
-        return this.root;
+        this.root = root;
+        this.username = username;
+        this.password = password;
+        this.features = features;
+        this.trustedKeyFile = trustedKeyFile;
     }
 
     @Override
@@ -86,24 +84,20 @@
     }
 
     public String getStatus() {
-        Element status = this.root.getChild("Status", Namespaces.SAML_NS_PROTO);
-        if (status == null) {
-            return null;
-        }
-        Element statuscode = status.getChild("StatusCode",
-                Namespaces.SAML_NS_PROTO);
-        if (statuscode == null) {
-            return null;
-        }
-        return statuscode.getAttributeValue("Value");
+        return XPathUtils.xpathString(this.root,
+                                      "./samlp:Status/samlp:StatusCode/@Value");
     }
 
+
     public Assertion getAssertion() {
         if (this.assertion == null && this.root != null) {
-            Element assertion = this.root.getChild("Assertion",
-                    Namespaces.SAML_NS_ASSERT);
-            if (assertion != null) {
-                this.assertion = new Assertion(assertion);
+            try {
+                TicketValidator validator =
+                    new TicketValidator(this.trustedKeyFile);
+                this.assertion = validator.checkTicket(this.root);
+            }
+            catch (Exception e) {
+                logger.error(e.getLocalizedMessage(), e);
             }
         }
         return this.assertion;
@@ -119,8 +113,7 @@
                 this.assertion.getRoles());
         logger.debug("User " + this.username + " with features " + features +
                      " successfully authenticated.");
-        return new User(this.username, this.password, assertion.getNameID(),
-                this.assertion.getRoles(), assertion, features);
+        return new User(assertion, features, this.password);
     }
 }
 // vim: set si et fileencoding=utf-8 ts=4 sw=4 tw=80:
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Signature.java	Wed May 08 18:23:41 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * This file is Free Software under the GNU AGPL (>=v3) 
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details. 
- */
-
-package org.dive4elements.river.client.server.auth.was;
-
-import java.io.ByteArrayInputStream;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.log4j.Logger;
-import org.jdom.Element;
-
-public class Signature {
-
-    private static Logger logger = Logger.getLogger(Signature.class);
-
-    private static final String XML_SIG_DIGEST_SHA1 =
-        "http://www.w3.org/2000/09/xmldsig#sha1";
-    private static final String XML_SIG_SIGNATURE_RSA_SHA1 =
-        "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
-
-    private final Element signature;
-    private Certificate cert;
-    private byte[] value;
-    private byte[] digestvalue;
-    private String reference;
-
-    public Signature(Element signature) {
-        this.signature = signature;
-        this.parseSignatureInfo();
-        this.parseSignatureValue();
-        this.parseCertificate();
-    }
-
-    private void parseSignatureInfo() {
-        Element signatureinfo = this.signature.getChild("SignedInfo",
-                Namespaces.XML_SIG_NS);
-        if (signatureinfo != null) {
-            Element signaturemethod = signatureinfo.getChild("SignatureMethod",
-                    Namespaces.XML_SIG_NS);
-            String algorithm = signaturemethod.getAttributeValue("Algorithm");
-            if (!algorithm.equals(XML_SIG_SIGNATURE_RSA_SHA1)) {
-                logger.warn("Unkown signature alorithm " + algorithm);
-            }
-
-            // There could be several references in XML-Sig spec but for me it
-            // doesn't make sense to have more then one in a SAML Assertion
-            Element reference = signatureinfo.getChild("Reference",
-                    Namespaces.XML_SIG_NS);
-            // reference must be present but its better to check
-            if (reference != null) {
-                String digestvalue = reference.getChildText("DigestValue",
-                        Namespaces.XML_SIG_NS);
-                String digestmethod = reference.getChildText("DigestMethod",
-                        Namespaces.XML_SIG_NS);
-                if (!digestmethod.equals(XML_SIG_DIGEST_SHA1)) {
-                    logger.warn("Unknown digest method " + digestmethod);
-                }
-                this.digestvalue = Base64.decodeBase64(digestvalue);
-
-                String referenceuri = reference.getAttributeValue("URI");
-                if (referenceuri.startsWith("#")) {
-                    this.reference = referenceuri.substring(1);
-                }
-                else {
-                    logger.warn("Unkown reference type " + referenceuri);
-                    this.reference = referenceuri;
-                }
-            }
-        }
-    }
-
-    private void parseSignatureValue() {
-        String signaturevalue = this.signature.getChildText("SignatureValue",
-                Namespaces.XML_SIG_NS);
-        this.value = Base64.decodeBase64(signaturevalue);
-    }
-
-    private void parseCertificate() {
-        Element keyinfo = this.signature.getChild("KeyInfo",
-                Namespaces.XML_SIG_NS);
-        if (keyinfo != null) {
-            Element data = keyinfo.getChild("X509Data", Namespaces.XML_SIG_NS);
-            if (data != null) {
-                String base64cert = data.getChildText("X509Certificate",
-                        Namespaces.XML_SIG_NS);
-                if (base64cert != null) {
-                    byte[] bytes = Base64.decodeBase64(base64cert);
-                    try {
-                        CertificateFactory cf = CertificateFactory.getInstance(
-                                "X.509");
-                        this.cert = cf.generateCertificate(
-                                new ByteArrayInputStream(bytes));
-                    }
-                    catch(CertificateException e) {
-                        // should never occur
-                        logger.error(e);
-                    }
-                }
-            }
-        }
-    }
-
-    public Certificate getCertificate() {
-        return this.cert;
-    }
-
-    public byte[] getValue() {
-        return this.value;
-    }
-
-    public String getReference() {
-        // In theory there could be several references with digestvalues, ...
-        return this.reference;
-    }
-
-    public byte[] getDigestValue() {
-        return this.digestvalue;
-    }
-}
-// vim: set si et fileencoding=utf-8 ts=4 sw=4 tw=80:
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/User.java	Wed May 08 18:23:41 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
- * Software engineering by Intevation GmbH
- *
- * This file is Free Software under the GNU AGPL (>=v3) 
- * and comes with ABSOLUTELY NO WARRANTY! Check out the
- * documentation coming with Dive4Elements River for details. 
- */
-
-package org.dive4elements.river.client.server.auth.was;
-
-import java.util.Date;
-import java.util.List;
-
-import org.dive4elements.river.client.server.auth.DefaultUser;
-
-public class User
-extends DefaultUser
-implements org.dive4elements.river.client.server.auth.User {
-
-    private Assertion assertion;
-
-    public User(String name,
-        String       password,
-        String       account,
-        List<String> roles,
-        Assertion    assertion,
-        List<String> features
-    ) {
-        this.setName(name);
-        this.setPassword(password);
-        this.setRoles(roles);
-        this.assertion = assertion;
-        this.setAllowedFeatures(features);
-        this.setAccount(account);
-    }
-
-    @Override
-    public boolean hasExpired() {
-        Date until = this.assertion.getUntil();
-        if (until != null) {
-            Date current = new Date();
-            return !current.after(until);
-        }
-        return false;
-    }
-}
-
-// vim:set ts=4 sw=4 si et fenc=utf8 tw=80:
--- a/gwt-client/src/main/java/org/dive4elements/river/client/server/filter/GGInAFilter.java	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/java/org/dive4elements/river/client/server/filter/GGInAFilter.java	Wed May 08 18:23:50 2013 +0200
@@ -46,6 +46,7 @@
 
     private static final String LOGIN_JSP     = "/login.jsp";
     private static final String LOGIN_SERVLET = "/flys/login";
+    private static final String SAML_SERVLET = "/flys/saml";
     private static final String FLYS_CSS      = "/FLYS.css";
 
 
@@ -108,6 +109,7 @@
         String path = this.sc.getContextPath();
         if (requesturi.equals(path + LOGIN_JSP)
                 || requesturi.equals(path + LOGIN_SERVLET)
+                || requesturi.equals(path + SAML_SERVLET)
                 || requesturi.equals(path + FLYS_CSS)) {
             logger.debug("Request for login " + requesturi);
             chain.doFilter(req, resp);
@@ -192,7 +194,7 @@
         throws AuthenticationException, IOException {
         Features features = (Features)sc.getAttribute(Features.CONTEXT_ATTRIBUTE);
         return AuthenticationFactory.getInstance(this.authmethod).auth(
-                user.getName(), user.getPassword(), encoding, features);
+                user.getName(), user.getPassword(), encoding, features, sc);
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gwt-client/src/main/webapp/WEB-INF/saml-signer-cert.pem	Wed May 08 18:23:50 2013 +0200
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIICDzCCAXigAwIBAgIETQYOHzANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJERTEQMA4GA1UE
+BxMHS29ibGVuejENMAsGA1UEChMEYmFmZzENMAsGA1UECxMEYmFmZzENMAsGA1UEAxMEYmFmZzAe
+Fw0xMDEyMTMxMjE0MjNaFw0xNTExMTcxMjE0MjNaMEwxCzAJBgNVBAYTAkRFMRAwDgYDVQQHEwdL
+b2JsZW56MQ0wCwYDVQQKEwRiYWZnMQ0wCwYDVQQLEwRiYWZnMQ0wCwYDVQQDEwRiYWZnMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIg0iXqjbFVDCuwiHR06U8+8IZmTl5mVDeH34zjqcCWEKf
+T2W2VS9SJhxyjP+DGDfDS9obqEZkYi8i4kLe533e+CQBWKud5mCHguVEiydOkUpUo0En4jFk9DBb
+kEDK8HV/CFzc1zX/6UJWGI8QL4ewQWhfhJSN/aoWweo0Sjr7mQIDAQABMA0GCSqGSIb3DQEBBQUA
+A4GBALpDcIQ0s+qhP+WvSkiVYiyGS9lnJWkrTMY89Q8kWbeiLr3/YA2fAQFJDqZ1Q8JxgUwadzv9
+6DClAW+6nPCnx3zQuP+tV9jZwQ5MSykHtwK+GAIwpLktx88b3BtIEOvMQjACoaPt3Z/Epm+VYT72
+rrxc7eejd9GfGIrCi4q2j4/j
+-----END CERTIFICATE-----
--- a/gwt-client/src/main/webapp/WEB-INF/web.xml	Wed May 08 18:23:41 2013 +0200
+++ b/gwt-client/src/main/webapp/WEB-INF/web.xml	Wed May 08 18:23:50 2013 +0200
@@ -22,6 +22,11 @@
     </context-param>
 
     <context-param>
+        <param-name>saml-trusted-public-key</param-name>
+        <param-value>/WEB-INF/saml-signer-cert.pem</param-value>
+    </context-param>
+
+    <context-param>
         <param-name>features-file</param-name>
         <param-value>/WEB-INF/features.xml</param-value>
     </context-param>
@@ -553,6 +558,16 @@
   </servlet-mapping>
 
   <servlet>
+    <servlet-name>saml</servlet-name>
+    <servlet-class>org.dive4elements.river.client.server.SamlServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>saml</servlet-name>
+    <url-pattern>/flys/saml</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
     <servlet-name>modules</servlet-name>
     <servlet-class>org.dive4elements.river.client.server.ModuleServiceImpl</servlet-class>
   </servlet>

http://dive4elements.wald.intevation.org