bh@5940: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde bh@5940: * Software engineering by Intevation GmbH bh@5940: * bh@5940: * This file is Free Software under the GNU AGPL (>=v3) bh@5940: * and comes with ABSOLUTELY NO WARRANTY! Check out the bh@5940: * documentation coming with Dive4Elements River for details. bh@5940: */ bh@5940: bh@5940: package org.dive4elements.river.client.server.auth.saml; bh@5940: bh@5940: import java.text.ParseException; bh@5940: import java.text.SimpleDateFormat; bh@5940: import java.util.Calendar; bh@5940: import java.util.Date; bh@5940: import java.util.List; bh@5940: import java.util.LinkedList; teichmann@5958: bh@5940: import javax.xml.namespace.QName; bh@5940: import javax.xml.xpath.XPathConstants; bh@5940: bh@5940: import org.apache.log4j.Logger; bh@5940: bh@5940: import org.w3c.dom.Element; bh@5940: import org.w3c.dom.NodeList; bh@5940: bh@5940: /** bh@5940: * Represents a SAML assertion about a user. bh@5940: */ bh@5940: public class Assertion { bh@5940: teichmann@8203: private static Logger log = Logger.getLogger(Assertion.class); bh@5940: bh@5940: private Element assertion; bh@5940: private LinkedList roles; bh@5940: private String user_id; bh@5940: private String name_id; bh@5940: private String group_id; bh@5940: private String group_name; bh@5940: private Date notbefore; bh@5940: private Date notonorafter; bh@5940: bh@5940: private static final String ATTR_CONT_USER_ID = bh@5940: "urn:conterra:names:sdi-suite:policy:attribute:user-id"; bh@5940: private static final String ATTR_CONT_GROUP_ID = bh@5940: "urn:conterra:names:sdi-suite:policy:attribute:group-id"; bh@5940: private static final String ATTR_CONT_GROUP_NAME = bh@5940: "urn:conterra:names:sdi-suite:policy:attribute:group-name"; bh@5940: private static final String ATTR_CONT_ROLE = bh@5940: "urn:conterra:names:sdi-suite:policy:attribute:role"; bh@5940: bh@5940: public Assertion(Element assertion) { bh@5940: this.assertion = assertion; bh@5940: this.roles = new LinkedList(); bh@5940: this.parseCondition(); bh@5940: this.parseAttributeStatement(); bh@5940: } bh@5940: bh@5940: private void parseCondition() { bh@5940: Element conditions = (Element)XPathUtils.xpathNode(this.assertion, bh@5940: "saml:Conditions"); bh@5940: if (conditions == null) { teichmann@8203: log.error("Cannot find Assertion conditions element"); bh@5940: return; bh@5940: } bh@5940: bh@5940: this.notbefore = parseDateAttribute(conditions, "NotBefore"); bh@5940: if (this.notbefore == null) { teichmann@8203: log.warn("Could not extract NotBefore date."); bh@5940: } bh@5940: this.notonorafter = parseDateAttribute(conditions, "NotOnOrAfter"); bh@5940: if (this.notonorafter == null) { teichmann@8203: log.warn("Could not extract NotOnOrAfter date."); bh@5940: } bh@5940: } bh@5940: bh@5940: private Date parseDateAttribute(Element element, String name) { bh@5940: SimpleDateFormat dateformat = new SimpleDateFormat(); bh@5940: // format should be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" but that's bh@5940: // only available in java 7+. However, parsing without the bh@5940: // time-zone yields Date values in the local time-zone, bh@5940: // therefore we need to convert to GMT ourselves. bh@5940: dateformat.applyPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); bh@5940: bh@5940: String value = element.getAttribute(name); bh@5940: try { bh@5940: return toGMT(dateformat.parse(value)); bh@5940: } bh@5940: catch(ParseException e) { teichmann@8203: log.error("Cannot parse Condition attribute " bh@5940: + name + " with value " + value bh@5940: + " (" + e.getLocalizedMessage() + ")"); bh@5940: } bh@5940: return null; bh@5940: } bh@5940: bh@5940: private Date toGMT(Date date) { bh@5940: Calendar cal = Calendar.getInstance(); bh@5940: cal.setTime(date); bh@5940: cal.set(Calendar.ZONE_OFFSET, 0); bh@5940: cal.set(Calendar.DST_OFFSET, 0); bh@5940: return cal.getTime(); bh@5940: } bh@5940: bh@5940: private void parseAttributeStatement() { bh@5940: Element attrstatement = (Element)XPathUtils.xpathNode(this.assertion, bh@5940: "saml:AttributeStatement"); bh@5940: if (attrstatement == null) { teichmann@8203: log.error("Cannot find Assertion AttributeStatement element"); bh@5940: return; bh@5940: } bh@5940: bh@5940: this.name_id = XPathUtils.xpathString(attrstatement, bh@5940: "saml:Subject" bh@5940: + "/saml:NameIdentifier"); bh@5940: bh@5940: this.user_id = getAttrValue(attrstatement, ATTR_CONT_USER_ID); bh@5940: this.group_id = getAttrValue(attrstatement, ATTR_CONT_GROUP_ID); bh@5940: this.group_name = getAttrValue(attrstatement, ATTR_CONT_GROUP_NAME); bh@5940: this.roles = getAttrValues(attrstatement, ATTR_CONT_ROLE); bh@5940: } bh@5940: bh@5940: static Object getAttrObject(Element attrs, String name, QName returnType) { bh@5940: return XPathUtils.xpath(attrs, bh@5940: "saml:Attribute[@AttributeName='" + name + "']" bh@5940: + "/saml:AttributeValue", bh@5940: returnType); bh@5940: } bh@5940: bh@5940: static String getAttrValue(Element attrs, String name) { bh@5940: return (String)getAttrObject(attrs, name, XPathConstants.STRING); bh@5940: } bh@5940: bh@5940: static LinkedList getAttrValues(Element attrs, String name) { bh@5940: LinkedList strings = new LinkedList(); bh@5940: NodeList nodes = (NodeList)getAttrObject(attrs, name, bh@5940: XPathConstants.NODESET); bh@5940: for (int i = 0; i < nodes.getLength(); i++) { bh@5940: strings.add(nodes.item(i).getTextContent()); bh@5940: } bh@5940: bh@5940: return strings; bh@5940: } bh@5940: bh@5940: public List getRoles() { bh@5940: return this.roles; bh@5940: } bh@5940: bh@5940: public String getUserID() { bh@5940: return this.user_id; bh@5940: } bh@5940: bh@5940: public String getNameID() { bh@5940: return this.name_id; bh@5940: } bh@5940: bh@5940: public String getGroupID() { bh@5940: return this.group_id; bh@5940: } bh@5940: bh@5940: public String getGroupName() { bh@5940: return this.group_name; bh@5940: } bh@5940: bh@5940: public Date getFrom() { bh@5940: return this.notbefore; bh@5940: } bh@5940: bh@5940: public Date getUntil() { bh@5940: return this.notonorafter; bh@5940: } bh@5940: bh@5940: /** bh@5940: * Returns whether the ticket to which the assertion belongs is bh@5940: * valid at the time the method is called. The method returns true, bh@5940: * if both dates (notbefore and notonorafter) have been determined tom@8839: * successfully and the current date/time is between both (with given tom@8839: * tolerance). bh@5940: * @return Whether the ticket is valid now. bh@5940: */ tom@8839: public boolean isValidNow(int timeEps) { bh@5940: Date now = new Date(); bh@5940: return (this.notbefore != null && this.notonorafter != null tom@8839: && now.after(new Date(this.notbefore.getTime() - timeEps)) tom@8839: && now.before(new Date(this.notonorafter.getTime() + timeEps))); bh@5940: } bh@5940: } bh@5940: // vim: set fileencoding=utf-8 ts=4 sw=4 et si tw=80: