/*
* 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 gov.nih.nci.cagrid.opensaml.artifact.Artifact;
import gov.nih.nci.cagrid.opensaml.artifact.ArtifactParseException;
import gov.nih.nci.cagrid.opensaml.artifact.ArtifactParserException;
import gov.nih.nci.cagrid.opensaml.artifact.SAMLArtifact;
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 protocol request
*
* @author Scott Cantor
* @created March 30, 2002
*/
public class SAMLRequest extends SAMLSignedObject implements Cloneable
{
protected int minor = config.getBooleanProperty("gov.nih.nci.cagrid.opensaml.compatibility-mode") ? 0 : 1;
protected String requestId = null;
protected Date issueInstant = new Date();
protected ArrayList respondWiths = new ArrayList();
protected SAMLQuery query = null;
protected ArrayList assertionIdRefs = new ArrayList();
protected ArrayList artifacts = new ArrayList();
/**
* 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 {
// Goes after any RespondWith elements.
Element n=XML.getFirstChildElement(root);
while (n != null && XML.isElementNamed(n, XML.SAMLP_NS, "RespondWith"))
n = XML.getNextSiblingElement(n);
root.insertBefore(getSignatureElement(),n);
}
/**
* Default constructor
*/
public SAMLRequest() {
}
/**
* Builds a SAML request using a query
*
* @param query A query to place in the request
* @exception SAMLException Thrown if a request cannot be constructed from
* the supplied information
*/
public SAMLRequest(SAMLQuery query)
throws SAMLException {
this(query,SAMLConfig.instance().getDefaultIDProvider().getIdentifier(),new Date());
}
/**
* Builds a SAML request using a query
*
* @param query A query to place in the request
* @param requestId Unique identifier for request
* @param issueInstant Time of issuance
* @exception SAMLException Thrown if a request cannot be constructed from
* the supplied information
*/
public SAMLRequest(SAMLQuery query, String requestId, Date issueInstant)
throws SAMLException {
this.requestId = XML.assign(requestId);
this.issueInstant = issueInstant;
if (query != null)
this.query = (SAMLQuery)query.setParent(this);
}
/**
* Builds a SAML request using artifacts or assertion references
*
* @param artifactsOrIdRefs A collection of Artifacts or Strings (but not both)
* @exception SAMLException Thrown if a request cannot be constructed from
* the supplied information
*/
public SAMLRequest(Collection artifactsOrIdRefs)
throws SAMLException {
this(artifactsOrIdRefs,SAMLConfig.instance().getDefaultIDProvider().getIdentifier(),new Date());
}
/**
* Builds a SAML request using artifacts or assertion references
*
* @param artifactsOrIdRefs A collection of Artifacts or Strings (but not both)
* @param requestId Unique identifier for request
* @param issueInstant Time of issuance
* @exception SAMLException Thrown if a request cannot be constructed from
* the supplied information
*/
public SAMLRequest(Collection artifactsOrIdRefs, String requestId, Date issueInstant)
throws SAMLException {
this.requestId = XML.assign(requestId);
this.issueInstant = issueInstant;
if (artifactsOrIdRefs != null && !artifactsOrIdRefs.isEmpty()) {
Iterator i = artifactsOrIdRefs.iterator();
Object first = i.next();
if (first instanceof Artifact) {
artifacts.add(first);
while (i.hasNext())
artifacts.add((Artifact)i.next());
}
else if (first instanceof String) {
assertionIdRefs.add(first);
while (i.hasNext())
assertionIdRefs.add((String)i.next());
}
else
throw new MalformedException("SAMLRequest() collection parameter must contain Artifacts or Strings");
}
}
/**
* Reconstructs a request from a DOM tree
*
* @param e The root of a DOM tree
* @exception SAMLException Thrown if the object cannot be constructed
*/
public SAMLRequest(Element e) throws SAMLException {
fromDOM(e);
}
/**
* Reconstructs a request from a stream
*
* @param in A stream containing XML
* @exception SAMLException Raised if an exception occurs while constructing
* the object.
*/
public SAMLRequest(InputStream in) throws SAMLException {
fromDOM(fromStream(in));
}
/**
* Reconstructs a request of a particular minor version from a stream
*
* @param in A stream containing XML
* @param minor The minor version of the incoming request
* @exception SAMLException Raised if an exception occurs while constructing
* the object.
*/
public SAMLRequest(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,"Request"))
throw new MalformedException(SAMLException.RESPONDER,"SAMLRequest.fromDOM() requires samlp:Request at root");
if (Integer.parseInt(e.getAttributeNS(null, "MajorVersion")) != 1)
throw new MalformedException(SAMLException.VERSION, "SAMLRequest.fromDOM() detected incompatible request major version of " +
e.getAttributeNS(null, "MajorVersion"));
minor = Integer.parseInt(e.getAttributeNS(null, "MinorVersion"));
requestId = XML.assign(e.getAttributeNS(null, "RequestID"));
if (minor>0)
e.setIdAttributeNode(e.getAttributeNodeNS(null, "RequestID"), true);
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.REQUESTER, "SAMLRequest.fromDOM() detected an invalid datetime while parsing request", ex);
}
// Process RespondWith elements.
Element n = XML.getFirstChildElement(e);
while (n != null && XML.isElementNamed(n, XML.SAMLP_NS, "RespondWith")) {
respondWiths.add(XML.getQNameTextNode((Text)n.getFirstChild()));
n = XML.getNextSiblingElement(n);
}
// Skip signature.
if (XML.isElementNamed(n, XML.XMLSIG_NS, "Signature"))
n = XML.getNextSiblingElement(n);
// We're pointed at one of the request content options...
if (XML.isElementNamed(n, XML.SAML_NS, "AssertionIDReference")) {
while (n != null && n.hasChildNodes()) {
assertionIdRefs.add(n.getFirstChild().getNodeValue());
n = XML.getNextSiblingElement(n, XML.SAML_NS, "AssertionIDReference");
}
}
else if (XML.isElementNamed(n, XML.SAMLP_NS, "AssertionArtifact")) {
while (n != null && n.hasChildNodes()) {
try {
artifacts.add(
SAMLArtifact.getTypeCode(n.getFirstChild().getNodeValue()).getParser().parse(
n.getFirstChild().getNodeValue()
)
);
}
catch (ArtifactParseException ex) {
throw new MalformedException(SAMLException.REQUESTER, "SAMLRequest.fromDOM() unable to parse artifact", ex);
}
catch (ArtifactParserException ex) {
throw new MalformedException(SAMLException.REQUESTER, "SAMLRequest.fromDOM() unable to parse artifact", ex);
}
n = XML.getNextSiblingElement(n, XML.SAMLP_NS, "AssertionArtifact");
}
}
else {
query = (SAMLQuery)SAMLQuery.getInstance(n).setParent(this);
}
checkValidity();
}
/**
* Gets the MinorVersion of the request.
*
* @return The minor version
*/
public int getMinorVersion() {
return minor;
}
/**
* Sets the MinorVersion of the request
*
* @param minor The minor version
*/
public void setMinorVersion(int minor) {
this.minor = minor;
setDirty(true);
}
/**
* Gets the request ID
*
* @return The request ID
*/
public String getId() {
return requestId;
}
/**
* Sets the request ID
*
* <b>NOTE:</b> Use this method with caution. Requests must contain unique identifiers
* and only specialized applications should need to explicitly assign an identifier.
*
* @param id The request ID
*/
public void setId(String id) {
if (XML.isEmpty(id))
throw new IllegalArgumentException("id cannot be null");
requestId = XML.assign(id);
setDirty(true);
}
/**
* Gets the issue timestamp of the request
*
* @return The issue timestamp
*/
public Date getIssueInstant() {
return issueInstant;
}
/**
* Sets the issue timestamp of the request
*
* @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 types of statements the requester is prepared to accept
*
* @return An iterator of QNames representing statement types
*/
public Iterator getRespondWiths() {
return respondWiths.iterator();
}
/**
* Sets the types of statements the requester is prepared to accept
*
* @param respondWiths An iterator of QNames representing statement types
*/
public void setRespondWiths(Collection respondWiths) {
this.respondWiths.clear();
if (respondWiths != null) {
for (Iterator i = respondWiths.iterator(); i.hasNext(); )
addRespondWith((QName)i.next());
}
setDirty(true);
}
/**
* Adds a statement type to the request
*
* @param respondWith The type to add
*/
public void addRespondWith(QName respondWith) {
if (respondWith != null) {
respondWiths.add(respondWith);
setDirty(true);
}
else
throw new IllegalArgumentException("respondWith cannot be null");
}
/**
* Removes a statement type by position (zero-based)
*
* @param index The position of the statement type to remove
*/
public void removeRespondWith(int index) throws IndexOutOfBoundsException {
respondWiths.remove(index);
setDirty(true);
}
/**
* Gets the query contained within the request
*
* @return The query in the request
*/
public SAMLQuery getQuery() {
return query;
}
/**
* Sets the query contained within the request
*
* @param query The query for the request
* @exception SAMLException Raised if the query is invalid
*/
public void setQuery(SAMLQuery query) throws SAMLException {
if (query != null) {
query.setParent(this);
setAssertionIdRefs(null);
setArtifacts(null);
}
this.query = query;
setDirty(true);
}
/**
* Gets the assertion ID references contained within the request
*
* @return An iterator over the references
*/
public Iterator getAssertionIdRefs() {
return assertionIdRefs.iterator();
}
/**
* Adds an assertion ID reference to the request
*
* @param ref The reference to add
*/
public void addAssertionIdRef(String ref) {
if (XML.isEmpty(ref))
throw new IllegalArgumentException("ref cannot be null or empty");
try {
setQuery(null);
}
catch (SAMLException e) {
}
setArtifacts(null);
assertionIdRefs.add(ref);
}
/**
* Sets the assertion ID references contained within the request
*
* @param refs The references to include
*/
public void setAssertionIdRefs(Collection refs) {
this.assertionIdRefs.clear();
if (refs != null) {
for (Iterator i = refs.iterator(); i.hasNext(); )
addAssertionIdRef((String)i.next());
}
}
/**
* Removes an assertion reference by position (zero-based)
*
* @param index The position of the reference to remove
*/
public void removeAssertionIdRef(int index) throws IndexOutOfBoundsException {
assertionIdRefs.remove(index);
setDirty(true);
}
/**
* Gets the artifacts contained within the request
*
* @return An iterator over the artifacts
*/
public Iterator getArtifacts() {
return artifacts.iterator();
}
/**
* Sets the artifacts contained within the request
*
* @param refs The artifacts to include
*/
public void setArtifacts(Collection artifacts) {
this.artifacts.clear();
if (artifacts != null) {
for (Iterator i = artifacts.iterator(); i.hasNext(); )
addArtifact((Artifact)i.next());
}
}
/**
* Adds an artifact to the request
*
* @param artifact The artifact to add
*/
public void addArtifact(Artifact artifact) {
if (artifact == null)
throw new IllegalArgumentException("artifact cannot be null or empty");
try {
setQuery(null);
}
catch (SAMLException e) {
}
setAssertionIdRefs(null);
artifacts.add(artifact);
}
/**
* Removes an artifact by position (zero-based)
*
* @param index The position of the artifact to remove
*/
public void removeArtifact(int index) throws IndexOutOfBoundsException {
artifacts.remove(index);
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, "Request");
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 (requestId == null)
requestId = config.getDefaultIDProvider().getIdentifier();
if (issueInstant == null)
issueInstant = new Date();
r.setAttributeNS(null, "MajorVersion", "1");
r.setAttributeNS(null, "MinorVersion", String.valueOf(minor));
r.setAttributeNS(null, "RequestID", requestId);
if (minor > 0)
r.setIdAttributeNS(null, "RequestID", true);
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));
Iterator i=respondWiths.iterator();
while (i.hasNext()) {
QName qn=(QName)i.next();
Element rw = doc.createElementNS(XML.SAMLP_NS, "RespondWith");
String rwns = qn.getNamespaceURI();
if (rwns==null)
rwns="";
if (!XML.SAML_NS.equals(rwns)) {
rw.setAttributeNS(XML.XMLNS_NS, "xmlns:rw", rwns);
rwns="rw:";
}
else
rwns="saml:";
rw.appendChild(doc.createTextNode(rwns + qn.getLocalPart()));
r.appendChild(rw);
}
if (query != null)
r.appendChild(query.toDOM(doc, false));
else if (assertionIdRefs.size() > 0) {
i=assertionIdRefs.iterator();
while (i.hasNext())
r.appendChild(doc.createElementNS(XML.SAML_NS,"saml:AssertionIDReference")).appendChild(doc.createTextNode((String)i.next()));
}
else {
i=artifacts.iterator();
while (i.hasNext()) {
r.appendChild(
doc.createElementNS(XML.SAMLP_NS,"AssertionArtifact")).appendChild(doc.createTextNode(((Artifact)i.next()).encode())
);
}
}
setDirty(false);
}
else if (xmlns) {
((Element)root).setAttributeNS(XML.XMLNS_NS, "xmlns", XML.SAMLP_NS);
((Element)root).setAttributeNS(XML.XMLNS_NS, "xmlns:saml", XML.SAML_NS);
((Element)root).setAttributeNS(XML.XMLNS_NS, "xmlns:samlp", XML.SAMLP_NS);
((Element)root).setAttributeNS(XML.XMLNS_NS, "xmlns:xsi", XML.XSI_NS);
((Element)root).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 (requestId == null || (query == null && assertionIdRefs.size() == 0 && artifacts.size() == 0))
throw new MalformedException("Request is invalid, must have an ID and query, assertion references, or artifacts");
}
/**
* 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 {
SAMLRequest dup=(SAMLRequest)super.clone();
try {
dup.respondWiths = (ArrayList)respondWiths.clone();
dup.query = (SAMLQuery)((SAMLQuery)query.clone()).setParent(dup);
dup.assertionIdRefs = (ArrayList)assertionIdRefs.clone();
dup.artifacts = new ArrayList();
for (Iterator i=artifacts.iterator(); i.hasNext();) {
String a = ((Artifact)i.next()).encode();;
try {
dup.artifacts.add(SAMLArtifact.getTypeCode(a).getParser().parse(a));
}
catch (ArtifactParseException e) {
throw new RuntimeException("Unable to clone artifact");
}
catch (ArtifactParserException e) {
throw new RuntimeException("Unable to clone artifact");
}
}
}
catch (SAMLException e) {
throw new CloneNotSupportedException(e.getMessage());
}
return dup;
}
}