/*
* 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 org.w3c.dom.*;
/**
* Represents a SAML protocol response
*
* @author Scott Cantor
* @created March 18, 2002
*/
public class SAMLResponse extends SAMLSignedObject
{
protected int minor = config.getBooleanProperty("gov.nih.nci.cagrid.opensaml.compatibility-mode") ? 0 : 1;
protected String responseId = null;
protected String inResponseTo = null;
protected Date issueInstant = new Date();
protected String recipient = null;
protected ArrayList assertions = new ArrayList();
protected SAMLException e = null;
/**
* Places the signature into the object's DOM to prepare for signing<p>
* @throws SAMLException Thrown if an error occurs while placing the signature
*/
protected void insertSignature() throws SAMLException {
root.insertBefore(getSignatureElement(),root.getFirstChild());
}
/**
* Default constructor
*/
public SAMLResponse() {
}
/**
* Builds a SAML response out of its component parts<P>
*
* @param inResponseTo The request ID that prompted the response, if any
* @param recipient The URL of the intended recipient of the response
* @param assertions The SAML assertion(s) to return in the response, if any
* @param e The SAML error status information to return in the response, if any
* @exception SAMLException Raised if a response cannot be constructed
* from the supplied information
*/
public SAMLResponse(String inResponseTo, String recipient, Collection assertions, SAMLException e) throws SAMLException {
this(SAMLConfig.instance().getDefaultIDProvider().getIdentifier(),new Date(),inResponseTo,recipient,assertions,e);
}
/**
* Builds a SAML response out of its component parts<P>
*
* @param responseId Unique identifier for response
* @param issueInstant Time of issuance
* @param inResponseTo The request ID that prompted the response, if any
* @param recipient The URL of the intended recipient of the response
* @param assertions The SAML assertion(s) to return in the response, if any
* @param e The SAML error status information to return in the response, if any
* @exception SAMLException Raised if a response cannot be constructed
* from the supplied information
*/
public SAMLResponse(
String responseId,
Date issueInstant,
String inResponseTo,
String recipient,
Collection assertions,
SAMLException e
) throws SAMLException {
this.responseId = XML.assign(responseId);
this.issueInstant = issueInstant;
this.inResponseTo = XML.assign(inResponseTo);
this.recipient = XML.assign(recipient);
if (e != null)
this.e = e.setParent(this);
if (assertions != null) {
for (Iterator i = assertions.iterator(); i.hasNext(); )
this.assertions.add(((SAMLAssertion)i.next()).setParent(this));
}
}
/**
* Reconstructs a response from a DOM tree
*
* @param e The root of a DOM tree
* @exception SAMLException Thrown if the object cannot be constructed
*/
public SAMLResponse(Element e) throws SAMLException {
fromDOM(e);
}
/**
* Reconstructs a response from a stream
*
* @param in A stream containing XML
* @exception SAMLException Raised if an exception occurs while constructing
* the object.
*/
public SAMLResponse(InputStream in) throws SAMLException {
fromDOM(fromStream(in));
}
/**
* Reconstructs a response of a particular minor version from a stream
*
* @param in A stream containing XML
* @param minor The minor version of the incoming response
* @exception SAMLException Raised if an exception occurs while constructing
* the object.
*/
public SAMLResponse(InputStream in, int minor) throws SAMLException {
fromDOM(fromStream(in,minor));
}
/**
* @see gov.nih.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.SAMLP_NS,"Response"))
throw new MalformedException(SAMLException.RESPONDER,"SAMLResponse.fromDOM() requires samlp:Response at root");
if (Integer.parseInt(e.getAttributeNS(null, "MajorVersion")) != 1)
throw new MalformedException(SAMLException.VERSION,
"SAMLResponse() detected incompatible response major version of " + e.getAttributeNS(null, "MajorVersion"));
minor = Integer.parseInt(e.getAttributeNS(null, "MinorVersion"));
responseId = XML.assign(e.getAttributeNS(null, "ResponseID"));
if (minor>0)
e.setIdAttributeNode(e.getAttributeNodeNS(null, "ResponseID"), true);
inResponseTo = XML.assign(e.getAttributeNS(null, "InResponseTo"));
recipient = XML.assign(e.getAttributeNS(null, "Recipient"));
try {
SimpleDateFormat formatter = null;
String dateTime = XML.assign(e.getAttributeNS(null, "IssueInstant"));
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"));
issueInstant = formatter.parse(dateTime);
}
catch (java.text.ParseException ex) {
throw new MalformedException(SAMLException.RESPONDER, "SAMLResponse() detected an invalid datetime while parsing response", ex);
}
Element n = XML.getFirstChildElement(e, XML.SAMLP_NS, "Status");
// Process status, and toss out any errors.
this.e = SAMLException.getInstance(n);
Iterator it=this.e.getCodes();
if (it.hasNext())
if (!it.next().equals(SAMLException.SUCCESS))
throw this.e;
n = XML.getNextSiblingElement(n, XML.SAML_NS, "Assertion");
while (n != null) {
assertions.add(new SAMLAssertion(n).setParent(this));
n = XML.getNextSiblingElement(n, XML.SAML_NS, "Assertion");
}
checkValidity();
}
/**
* Gets the MinorVersion of the response.
*
* @return The minor version
*/
public int getMinorVersion() {
return minor;
}
/**
* Sets the MinorVersion of the response
*
* @param minor The minor version
*/
public void setMinorVersion(int minor) {
this.minor = minor;
setDirty(true);
}
/**
* Gets the response ID
*
* @return The response ID
*/
public String getId() {
return responseId;
}
/**
* Sets the response ID
*
* <b>NOTE:</b> Use this method with caution. Responses must contain unique identifiers
* and only specialized applications should need to explicitly assign an identifier.
*
* @param id The response ID
*/
public void setId(String id) {
if (XML.isEmpty(id))
throw new IllegalArgumentException("id cannot be null");
responseId = XML.assign(id);
setDirty(true);
}
/**
* Gets the InResponseTo attribute
*
* @return The InResponseTo value
*/
public String getInResponseTo() {
return inResponseTo;
}
/**
* Sets the InResponseTo attribute
*
* @param inResponseTo The InResponseTo value
*/
public void setInResponseTo(String inResponseTo) {
this.inResponseTo = XML.assign(inResponseTo);
setDirty(true);
}
/**
* Gets the issue timestamp of the SAML response
*
* @return The issue timestamp
*/
public Date getIssueInstant() {
return issueInstant;
}
/**
* Sets the issue timestamp of the response
*
* @param issueInstant The issue timestamp
*/
public void setIssueInstant(Date issueInstant) {
if (issueInstant == null)
throw new IllegalArgumentException("issueInstant cannot be null");
this.issueInstant = issueInstant;
setDirty(true);
}
/**
* Gets the Recipient attribute of the SAML response
*
* @return The Recipient value
*/
public String getRecipient() {
return recipient;
}
/**
* Sets the Recipient attribute
*
* @param recipient The Recipient value
*/
public void setRecipient(String recipient) {
this.recipient = XML.assign(recipient);
setDirty(true);
}
/**
* Gets the SAML assertions contained in the response, if any
*
* @return The assertions in the response
*/
public Iterator getAssertions() {
return assertions.iterator();
}
/**
* Sets the SAML assertions to include in the response
*
* @param assertions The assertions to include
* @exception SAMLException Raised if the assertions are invalid
*/
public void setAssertions(Collection assertions) throws SAMLException {
this.assertions.clear();
if (assertions != null) {
for (Iterator i = assertions.iterator(); i.hasNext(); )
this.assertions.add(((SAMLAssertion)i.next()).setParent(this));
}
setDirty(true);
}
/**
* Adds an assertion to the response
*
* @param assertion The assertion to add
* @exception SAMLException Raised if the assertion is invalid
*/
public void addAssertion(SAMLAssertion assertion) throws SAMLException {
if (assertion != null) {
assertions.add(assertion.setParent(this));
setDirty(true);
}
else
throw new IllegalArgumentException("assertion cannot be null");
}
/**
* Removes assertion by position (zero-based)
*
* @param index The position of the assertion to remove
*/
public void removeAssertion(int index) throws IndexOutOfBoundsException {
assertions.remove(index);
setDirty(true);
}
/**
* Gets the SAML Status contained in the response, if any (a SAMLException object is
* used to express the information, even in a successful case)
*
* @return The status information in the response
*/
public SAMLException getStatus() {
return e;
}
/**
* Sets the SAML status to include in the response
*
* @param assertions The status to include
* @exception SAMLException Raised if the status cannot be set or is invalid
*/
public void setStatus(SAMLException e) throws SAMLException {
this.e = e.setParent(this);
setDirty(true);
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLObject#buildRoot(org.w3c.dom.Document,boolean)
*/
protected Element buildRoot(Document doc, boolean xmlns) {
Element r = doc.createElementNS(XML.SAMLP_NS, "Response");
if (xmlns) {
r.setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SAMLP_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:saml", XML.SAML_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:samlp", XML.SAMLP_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:xsi", XML.XSI_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:xsd", XML.XSD_NS);
}
return r;
}
/**
* @see gov.nih.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 r = (Element)root;
if (dirty) {
if (responseId == null)
responseId = config.getDefaultIDProvider().getIdentifier();
if (issueInstant == null)
issueInstant = new Date();
r.setAttributeNS(null, "MajorVersion", "1");
r.setAttributeNS(null, "MinorVersion", String.valueOf(minor));
r.setAttributeNS(null, "ResponseID", responseId);
if (minor > 0)
r.setIdAttributeNS(null, "ResponseID", true);
if (!XML.isEmpty(inResponseTo))
r.setAttributeNS(null, "InResponseTo", inResponseTo);
if (!XML.isEmpty(recipient))
r.setAttributeNS(null, "Recipient", recipient);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
r.setAttributeNS(null, "IssueInstant", formatter.format(issueInstant));
// Fill in a status.
if (e!=null)
r.appendChild(e.toDOM(doc, false));
else {
Element status = doc.createElementNS(XML.SAMLP_NS, "Status");
Element code = doc.createElementNS(XML.SAMLP_NS, "StatusCode");
code.setAttributeNS(null, "Value", "samlp:" + SAMLException.SUCCESS.getLocalPart());
status.appendChild(code);
r.appendChild(status);
}
// Embed the assertions.
Iterator i = assertions.iterator();
while (i.hasNext())
r.appendChild(((SAMLAssertion)i.next()).toDOM(doc));
setDirty(false);
}
else if (xmlns) {
r.setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SAMLP_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:saml", XML.SAML_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:samlp", XML.SAMLP_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:xsi", XML.XSI_NS);
r.setAttributeNS(XML.XMLNS_NS, "xmlns:xsd", XML.XSD_NS);
}
return root;
}
/**
* @see gov.nih.gov.nih.nci.cagrid.opensaml.SAMLObject#checkValidity()
*/
public void checkValidity() throws SAMLException {
if (responseId == null)
throw new MalformedException("Response is invalid, must have an ID");
}
/**
* 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 {
SAMLResponse dup=(SAMLResponse)super.clone();
// Clone the embedded objects.
try {
if (e != null)
dup.e = (SAMLException)((SAMLException)e.clone()).setParent(dup);
dup.assertions = new ArrayList();
for (Iterator i = assertions.iterator(); i.hasNext(); )
dup.assertions.add(((SAMLAssertion)((SAMLAssertion)i.next()).clone()).setParent(dup));
}
catch (SAMLException e) {
throw new CloneNotSupportedException(e.getMessage());
}
return dup;
}
}