/* * Copyright 2001-2005 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gov.nih.nci.cagrid.opensaml; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.TimeZone; import javax.xml.namespace.QName; import org.w3c.dom.*; /** * Represents a SAML Authentication Statement * * @author Scott Cantor * @created March 25, 2002 */ public class SAMLAuthenticationStatement extends SAMLSubjectStatement implements Cloneable { protected String subjectIP = null; protected String subjectDNS = null; protected String authMethod = null; protected Date authInstant = null; protected ArrayList bindings = new ArrayList(); /** The authentication was performed by means of a password. */ public static final String AuthenticationMethod_Password = "urn:oasis:names:tc:SAML:1.0:am:password"; /** The authentication was performed by means of the Kerberos protocol [RFC 1510], * an instantiation of the Needham-Schroeder symmetric key authentication mechanism [Needham78]. */ public static final String AuthenticationMethod_Kerberos = "urn:ietf:rfc:1510"; /** The authentication was performed by means of Secure Remote Password protocol as specified in [RFC 2945]. */ public static final String AuthenticationMethod_SRP = "urn:ietf:rfc:2945"; /** The authentication was performed by means of an unspecified hardware token. */ public static final String AuthenticationMethod_HardwareToken = "urn:oasis:names:tc:SAML:1.0:am:HardwareToken"; /** The authentication was performed using either the SSL or TLS protocol with certificate based client * authentication. TLS is described in [RFC 2246]. */ public static final String AuthenticationMethod_SSL_TLS_Client = "urn:ietf:rfc:2246"; /** The authentication was performed by some (unspecified) mechanism on a key authenticated by means of an * X.509 PKI [X.500][PKIX]. It may have been one of the mechanisms for which a more specific identifier * has been defined. */ public static final String AuthenticationMethod_X509_PublicKey = "urn:oasis:names:tc:SAML:1.0:am:X509-PKI"; /** The authentication was performed by some (unspecified) mechanism on a key authenticated by means of * a PGP web of trust [PGP]. It may have been one of the mechanisms for which a more specific identifier * has been defined. */ public static final String AuthenticationMethod_PGP_PublicKey = "urn:oasis:names:tc:SAML:1.0:am:PGP"; /** The authentication was performed by some (unspecified) mechanism on a key authenticated by means of a * SPKI PKI [SPKI]. It may have been one of the mechanisms for which a more specific identifier has been * defined. */ public static final String AuthenticationMethod_SPKI_PublicKey = "urn:oasis:names:tc:SAML:1.0:am:SPKI"; /** The authentication was performed by some (unspecified) mechanism on a key authenticated by means of a * XKMS trust service [XKMS]. It may have been one of the mechanisms for which a more specific identifier * has been defined. */ public static final String AuthenticationMethod_XKMS_PublicKey = "urn:oasis:names:tc:SAML:1.0:am:XKMS"; /** The authentication was performed by means of an XML digital signature [RFC 3075]. */ public static final String AuthenticationMethod_XML_DSig = "urn:ietf:rfc:3075"; /** The authentication was performed by an unspecified means. */ public static final String AuthenticationMethod_Unspecified = "urn:oasis:names:tc:SAML:1.0:am:unspecified"; /** * Default constructor */ public SAMLAuthenticationStatement() { } /** * Builds a statement out of its component parts * * @param subject Subject of statement * @param authMethod URI of authentication method * @param authInstant Datetime of authentication * @param subjectIP IP address of subject in dotted decimal * notation (optional) * @param subjectDNS DNS address of subject (optional) * @param bindings Collection of SAMLAuthorityBinding objects to * reference SAML responders (optional) * @exception SAMLException Raised if a statement cannot be constructed * from the supplied information */ public SAMLAuthenticationStatement( SAMLSubject subject, String authMethod, Date authInstant, String subjectIP, String subjectDNS, Collection bindings ) throws SAMLException { super(subject); this.subjectIP = XML.assign(subjectIP); this.subjectDNS = XML.assign(subjectDNS); this.authMethod = XML.assign(authMethod); this.authInstant = authInstant; if (bindings != null) { for (Iterator i = bindings.iterator(); i.hasNext(); ) this.bindings.add(((SAMLAuthorityBinding)i.next()).setParent(this)); } } /** * Builds a statement out of its component parts * * @param subject Subject of statement * @param authInstant Datetime of authentication * @param subjectIP IP address of subject in dotted decimal * notation (optional) * @param subjectDNS DNS address of subject (optional) * @param bindings Collection of SAMLAuthorityBinding objects to * reference SAML responders (optional) * @exception SAMLException Raised if a statement cannot be constructed * from the supplied information */ public SAMLAuthenticationStatement( SAMLSubject subject, Date authInstant, String subjectIP, String subjectDNS, Collection bindings ) throws SAMLException { this(subject,SAMLAuthenticationStatement.AuthenticationMethod_Unspecified,authInstant,subjectIP,subjectDNS, bindings); } /** * Reconstructs a statement from a DOM tree * * @param e The root of a DOM tree * @exception SAMLException Thrown if the object cannot be constructed */ public SAMLAuthenticationStatement(Element e) throws SAMLException { fromDOM(e); } /** * Reconstructs a statement from a stream * * @param in A stream containing XML * @exception SAMLException Raised if an exception occurs while constructing * the object. */ public SAMLAuthenticationStatement(InputStream in) throws SAMLException { fromDOM(fromStream(in)); } /** * @see gov.nih.nci.cagrid.opensaml.SAMLObject#fromDOM(org.w3c.dom.Element) */ public void fromDOM(Element e) throws SAMLException { super.fromDOM(e); if (config.getBooleanProperty("gov.nih.nci.cagrid.opensaml.strict-dom-checking") && !XML.isElementNamed(e,XML.SAML_NS,"AuthenticationStatement")) { QName q=XML.getQNameAttribute(e,XML.XSI_NS,"type"); if (!XML.isElementNamed(e,XML.SAML_NS,"Statement") || q==null || !XML.SAML_NS.equals(q.getNamespaceURI()) || !"AuthenticationStatementType".equals(q.getLocalPart())) throw new MalformedException(SAMLException.RESPONDER, "SAMLAuthenticationStatement() requires saml:AuthenticationStatement at root"); } authMethod = XML.assign(e.getAttributeNS(null,"AuthenticationMethod")); try { SimpleDateFormat formatter = null; String dateTime = XML.assign(e.getAttributeNS(null, "AuthenticationInstant")); int dot = dateTime.indexOf('.'); if (dot > 0) { formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } else { formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); } formatter.setTimeZone(TimeZone.getTimeZone("UTC")); authInstant = formatter.parse(dateTime); } catch (java.text.ParseException ex) { throw new MalformedException(SAMLException.RESPONDER, "SAMLAuthenticationStatement() detected an invalid datetime while parsing statement", ex); } // Check for locality Element n = XML.getFirstChildElement(root, XML.SAML_NS, "SubjectLocality"); if (n != null) { subjectIP = XML.assign(n.getAttributeNS(null, "IPAddress")); subjectDNS = XML.assign(n.getAttributeNS(null, "DNSAddress")); n = XML.getNextSiblingElement(n); } // Extract bindings. n = XML.getFirstChildElement(root, XML.SAML_NS, "AuthorityBinding"); while (n != null) { bindings.add(new SAMLAuthorityBinding(n).setParent(this)); n = XML.getNextSiblingElement(n, XML.SAML_NS, "AuthorityBinding"); } checkValidity(); } /** * Gets the subject's IP address * * @return The subject's IP address in dotted decimal notation */ public String getSubjectIP() { return subjectIP; } /** * Sets the subject's IP address * * @param subjectIP The subject's IP address */ public void setSubjectIP(String subjectIP) { this.subjectIP = XML.assign(subjectIP); setDirty(true); } /** * Gets the subject's DNS address * * @return The subject's DNS address */ public String getSubjectDNS() { return subjectDNS; } /** * Sets the subject's DNS address * * @param subjectDNS The subject's DNS address */ public void setSubjectDNS(String subjectDNS) { this.subjectDNS = XML.assign(subjectDNS); setDirty(true); } /** * Gets the authentication method * * @return The authentication method URI */ public String getAuthMethod() { return authMethod; } /** * Sets the authentication method * * @param authMethod The authentication method URI */ public void setAuthMethod(String authMethod) { if (XML.isEmpty(authMethod)) throw new IllegalArgumentException("authMethod cannot be null"); this.authMethod = authMethod; setDirty(true); } /** * Gets the datetime of authentication * * @return The date and time of authentication */ public Date getAuthInstant() { return authInstant; } /** * Sets the datetime of authentication * * @param authInstant The date and time of authentication */ public void setAuthInstant(Date authInstant) { if (authInstant == null) throw new IllegalArgumentException("authInstant cannot be null"); this.authInstant = authInstant; setDirty(true); } /** * Gets SAML authority binding information * * @return An iterator of bindings */ public Iterator getBindings() { return bindings.iterator(); } /** * Sets SAML authority binding information * * @param bindings The bindings to include * @throws SAMLException Raised if any of the bindings are invalid */ public void setBindings(Collection bindings) throws SAMLException { this.bindings.clear(); if (bindings != null) { for (Iterator i = bindings.iterator(); i.hasNext(); ) this.bindings.add(((SAMLAuthorityBinding)i.next()).setParent(this)); } setDirty(true); } /** * Adds SAML authority binding information * * @param binding The binding to add * @exception SAMLException Raised if the binding is invalid */ public void addBinding(SAMLAuthorityBinding binding) throws SAMLException { if (binding != null) { bindings.add(binding.setParent(this)); setDirty(true); } else throw new IllegalArgumentException("binding cannot be null"); } /** * Removes a binding by position (zero-based) * * @param index The position of the binding to remove */ public void removeBinding(int index) { bindings.remove(index); setDirty(true); } /** * @see gov.nih.nci.cagrid.opensaml.SAMLObject#buildRoot(org.w3c.dom.Document,boolean) */ protected Element buildRoot(Document doc, boolean xmlns) { Element s = doc.createElementNS(XML.SAML_NS, "AuthenticationStatement"); if (xmlns) s.setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SAML_NS); return s; } /** * @see gov.nih.nci.cagrid.opensaml.SAMLObject#toDOM(org.w3c.dom.Document,boolean) */ public Node toDOM(Document doc, boolean xmlns) throws SAMLException { // Let the base build/verify the DOM root. super.toDOM(doc, xmlns); Element statement = (Element)root; if (dirty) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); statement.setAttributeNS(null, "AuthenticationInstant", formatter.format(authInstant)); statement.setAttributeNS(null, "AuthenticationMethod", authMethod); if (!XML.isEmpty(subjectIP) || !XML.isEmpty(subjectDNS)) { Element locality = doc.createElementNS(XML.SAML_NS, "SubjectLocality"); if (!XML.isEmpty(subjectIP)) locality.setAttributeNS(null,"IPAddress", subjectIP); if (!XML.isEmpty(subjectDNS)) locality.setAttributeNS(null,"DNSAddress", subjectDNS); statement.appendChild(locality); } for (Iterator i=bindings.iterator(); i.hasNext(); ) statement.appendChild(((SAMLAuthorityBinding)i.next()).toDOM(doc, false)); setDirty(false); } else if (xmlns) { statement.setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SAML_NS); } return root; } /** * @see gov.nih.nci.cagrid.opensaml.SAMLObject#checkValidity() */ public void checkValidity() throws SAMLException { super.checkValidity(); if (XML.isEmpty(authMethod) || authInstant == null) throw new MalformedException("AuthenticationStatement is invalid, requires AuthenticationMethod and AuthenticationInstant"); } /** * Copies a SAML object such that no dependencies exist between the original * and the copy * * @return The new object * @see java.lang.Object#clone() */ public Object clone() throws CloneNotSupportedException { SAMLAuthenticationStatement dup=(SAMLAuthenticationStatement)super.clone(); try { // Clone the embedded objects. dup.bindings = new ArrayList(); for (Iterator i = bindings.iterator(); i.hasNext(); ) dup.bindings.add(((SAMLAuthorityBinding)((SAMLAuthorityBinding)i.next()).clone()).setParent(dup)); } catch (SAMLException e) { throw new CloneNotSupportedException(e.getMessage()); } return dup; } }