# HG changeset patch # User Tom Gottfried # Date 1368030230 -7200 # Node ID 259ed81d47e9d74ab7ee8420f97827fc50c18d04 # Parent b819209732a0f6befe4f275f3042842dc9a5debe# Parent 24dc13ac8e6ce2f8024a3080cb96680bc06231d7 merge diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/AuthenticationServlet.java --- /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); + } +} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/LoginServlet.java --- 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); } } diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/SamlServlet.java --- /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); + } +} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/Authenticator.java --- 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; } diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/plain/Authenticator.java --- 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 { diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/Assertion.java --- /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 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(); + 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 getAttrValues(Element attrs, String name) { + LinkedList strings = new LinkedList(); + 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 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/SamlNamespaceContext.java --- /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(); + } +} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/TicketValidator.java --- /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; + } +} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/User.java --- /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 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/saml/XPathUtils.java --- /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); + } +} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Assertion.java --- 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 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(); - - 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 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Authenticator.java --- 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) { diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Namespaces.java --- 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#"); - -} diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Response.java --- 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/Signature.java --- 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/auth/was/User.java --- 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 roles, - Assertion assertion, - List 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: diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/java/org/dive4elements/river/client/server/filter/GGInAFilter.java --- 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); } /** diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/webapp/WEB-INF/saml-signer-cert.pem --- /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----- diff -r b819209732a0 -r 259ed81d47e9 gwt-client/src/main/webapp/WEB-INF/web.xml --- 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 @@ + saml-trusted-public-key + /WEB-INF/saml-signer-cert.pem + + + features-file /WEB-INF/features.xml @@ -553,6 +558,16 @@ + saml + org.dive4elements.river.client.server.SamlServlet + + + + saml + /flys/saml + + + modules org.dive4elements.river.client.server.ModuleServiceImpl