# HG changeset patch # User Bernhard Herzog # Date 1368028573 -7200 # Node ID 05da3cfa4054439c68e46af45611c86f8fa0f8e0 # Parent 407cff3b03ab38e76e0065b9f9ee3132a8777cbf Add new SAML Assertion class based on WAS Assertion. diff -r 407cff3b03ab -r 05da3cfa4054 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 17:56:13 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: