001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: Request.java,v 1.2 2008/06/25 05:47:37 qcheng Exp $
026 *
027 * Portions Copyrighted 2016 ForgeRock AS.
028 */
029
030package com.sun.identity.saml.protocol;
031
032import static org.forgerock.openam.utils.Time.*;
033
034import com.sun.identity.common.SystemConfigurationUtil;
035import com.sun.identity.shared.xml.XMLUtils;
036import com.sun.identity.shared.DateUtils;
037import com.sun.identity.saml.assertion.AssertionIDReference;
038import com.sun.identity.saml.common.SAMLConstants;
039import com.sun.identity.saml.common.SAMLException;
040import com.sun.identity.saml.common.SAMLRequesterException;
041import com.sun.identity.saml.common.SAMLRequestVersionTooHighException;
042import com.sun.identity.saml.common.SAMLRequestVersionTooLowException;
043import com.sun.identity.saml.common.SAMLResponderException;
044import com.sun.identity.saml.common.SAMLUtils;
045
046import com.sun.identity.saml.xmlsig.XMLSignatureManager;
047
048import java.io.ByteArrayOutputStream;
049
050import java.text.ParseException;
051
052import java.util.ArrayList;
053import java.util.Collections;
054import java.util.Date;
055import java.util.Iterator;
056import java.util.List;
057import java.util.StringTokenizer;
058
059import org.w3c.dom.Attr;
060import org.w3c.dom.Document;
061import org.w3c.dom.Element;
062import org.w3c.dom.NamedNodeMap;
063import org.w3c.dom.Node;
064import org.w3c.dom.NodeList;
065
066/**
067 * This <code>Request</code> class represents a Request XML document.
068 * It extends from the abstract base class <code>AbstractRequest</code>.
069 *
070 * @supported.all.api
071 */
072public class Request extends AbstractRequest {
073    /*
074     * data members
075     */
076
077    protected Query     query           = null;
078    protected List      assertionIDRefs = Collections.EMPTY_LIST;
079    protected List      artifacts       = Collections.EMPTY_LIST;
080    protected int       contentType     = NOT_SUPPORTED;
081    protected String    xmlString       = null;
082    protected String    signatureString = null;
083
084    // Request ID attribute name
085    private static final String REQUEST_ID_ATTRIBUTE = "RequestID";
086
087    /**
088     * The request is not supported.
089     */
090    public final static int NOT_SUPPORTED                       = -1;
091
092    /**
093     * The request is an Authentication Query.
094     */
095    public final static int AUTHENTICATION_QUERY                = 0;
096
097    /**
098     * The request is an Authorization Decision Query.
099     */
100    public final static int AUTHORIZATION_DECISION_QUERY        = 1;
101
102    /**
103     * The request is an Assertion ID Reference.
104     */
105    public final static int ASSERTION_ID_REFERENCE              = 2;
106
107    /**
108     * The request is an Assertion Artifact.
109     */
110    public final static int ASSERTION_ARTIFACT                  = 3;
111
112    /**
113     * The request is an Attribute Query.
114     */
115    public final static int ATTRIBUTE_QUERY                     = 4;
116
117    /*
118     * Constructors
119     */
120    protected Request() {}
121
122    /**
123     * Method to sign the Request.
124     * @exception SAMLException if could not sign the Request.
125     */
126    public void signXML() throws SAMLException {
127        if (signed) {
128            if (SAMLUtils.debug.messageEnabled()) {
129                SAMLUtils.debug.message("Request.signXML: the request is "
130                    + "already signed.");
131            }
132            throw new SAMLException(
133                SAMLUtils.bundle.getString("alreadySigned"));
134        }
135        String certAlias =    
136            SystemConfigurationUtil.getProperty(
137            "com.sun.identity.saml.xmlsig.certalias");
138        if (certAlias == null) {
139            if (SAMLUtils.debug.messageEnabled()) {
140                SAMLUtils.debug.message("Request.signXML: couldn't obtain "
141                    + "this site's cert Alias.");
142            }
143            throw new SAMLResponderException(
144                SAMLUtils.bundle.getString("cannotFindCertAlias"));
145        }
146        XMLSignatureManager manager = XMLSignatureManager.getInstance();
147        if ((majorVersion == 1) && (minorVersion == 0)) {
148            SAMLUtils.debug.message("Request.signXML: sign with version 1.0");
149            signatureString = manager.signXML(this.toString(true, true),
150                              certAlias);
151            // this block is used for later return of signature element by
152            // getSignature() method
153            signature =
154                XMLUtils.toDOMDocument(signatureString, SAMLUtils.debug)
155                        .getDocumentElement();
156        } else {
157            Document doc = XMLUtils.toDOMDocument(this.toString(true, true),
158                                                  SAMLUtils.debug);
159            // sign with SAML 1.1 spec & include cert in KeyInfo
160            signature = manager.signXML(doc, certAlias, null,
161                REQUEST_ID_ATTRIBUTE, getRequestID(), true, null);
162            signatureString = XMLUtils.print(signature);
163        }
164        signed = true;
165        xmlString = this.toString(true, true);
166    }
167
168    /**
169     * This constructor shall only be used at the client side to construct a
170     * Request object.
171     * NOTE: The content here is just the body for the Request. The 
172     * constructor will add <code>MajorVersion</code>,
173     * <code>MinorVersion</code>, etc. to form a complete Request.
174     * @param respondWiths A List of Strings representing
175     *        <code>RespondWith</code> elements. It could be null when there is
176     *        no <code>&lt;RespondWith&gt;</code>. Each string could be prefixed
177     *        by <code>saml:</code>. If it is not prefixed, or prefixed by a
178     *        prefix other than <code>saml:</code>, <code>saml:</code> will be
179     *        used instead.
180     * @param requestId If it's null, the constructor will create one.
181     * @param contents A List of objects that are the contents of Request that
182     *        the client wants to send to the server. It could be an
183     *        <code>AuthenticationQuery</code>,
184     *        <code>AuthorizationDecisionQuery</code>,
185     *        <code>AttributeQuery</code>, 1 or more
186     *        <code>AssertionIDReference</code>, or 1 or more of
187     *        <code>AssertionArtifact</code>.
188     * @exception SAMLException if an error occurs.
189     */
190    public Request(List respondWiths,
191                        String requestId,
192                        List contents) throws SAMLException {
193        Object temp = null;
194
195        if ((respondWiths != null) &&
196            (respondWiths != Collections.EMPTY_LIST)) {
197            for (int i = 0, length = respondWiths.size(); i < length; i++) {
198                temp = respondWiths.get(i);
199                if (!(temp instanceof String)) {
200                    if (SAMLUtils.debug.messageEnabled()) {
201                        SAMLUtils.debug.message("Request: wrong input for "
202                                + "RespondWith");
203                    }
204                    throw new SAMLRequesterException(
205                        SAMLUtils.bundle.getString("wrongInput"));
206                }
207                if ((this.respondWiths == null) ||
208                    (this.respondWiths.size() == 0)) {
209                    this.respondWiths = new ArrayList();
210                }
211                (this.respondWiths).add(checkAndGetRespondWith((String)temp));
212            }
213        }
214
215        if ((requestId != null) && (requestId.length() != 0)) {
216            requestID = requestId;
217        } else {
218            // random generate one
219            requestID = SAMLUtils.generateID();
220            if (requestID == null) {
221                SAMLUtils.debug.error("Request: couldn't generate RequestID.");
222                throw new SAMLRequesterException(
223                    SAMLUtils.bundle.getString("errorGenerateID"));
224            }
225        }
226
227        parseContents(contents);
228                issueInstant = newDate();
229    }
230
231    private String checkAndGetRespondWith(String respondWith)
232                                                throws SAMLException
233    {
234        if ((respondWith == null) || (respondWith.length() == 0)) {
235            SAMLUtils.debug.message("Request: empty RespondWith Value.");
236            throw new SAMLRequesterException(
237                SAMLUtils.bundle.getString("wrongInput"));
238        }
239
240        if (respondWith.indexOf(":") == -1) {
241            return (SAMLConstants.ASSERTION_PREFIX + respondWith);
242        } else {
243            StringTokenizer st = new StringTokenizer(respondWith, ":");
244            if (st.countTokens() != 2) {
245                SAMLUtils.debug.message("Request: wrong RespondWith value.");
246                throw new SAMLRequesterException(
247                        SAMLUtils.bundle.getString("wrongInput"));
248            }
249            st.nextToken();
250            String temp = st.nextToken().trim();
251            if (temp.length() == 0) {
252                SAMLUtils.debug.message("Request: wrong RespondWith value.");
253                throw new SAMLRequesterException(
254                        SAMLUtils.bundle.getString("wrongInput"));
255            }
256            return (SAMLConstants.ASSERTION_PREFIX + temp);
257        }
258    }
259
260    /**
261     * Checks the contents of the Request and set the class members accordingly.
262     *
263     * Used by this class only.
264     * @param contents A List that contains the contents of the request. 
265     *        it could be a query, 1 or more <code>AssertionIDReference</code>,
266     *        or 1 or more <code>AssertionArtifact</code>.
267     * @exception SAMLException when an error occurs during the process.
268     */
269    private void parseContents(List contents) throws SAMLException {
270        // check contents and set the contentType appropriately
271        int length = 0;
272        int i = 0;
273        if ((contents == null) ||
274            ((length = contents.size()) == 0)) {
275            SAMLUtils.debug.message("Request: empty content.");
276            throw new SAMLRequesterException(
277                        SAMLUtils.bundle.getString("wrongInput"));
278        }
279        for (i = 0; i < length; i++) {
280            Object temp = contents.get(i);
281            if (temp instanceof AuthenticationQuery) {
282                // make sure this is the first one on the list
283                if ((contentType != NOT_SUPPORTED) ||
284                    // and make sure there is no other elements on the list
285                    (i != (length - 1))) {
286                    if (SAMLUtils.debug.messageEnabled()) {
287                        SAMLUtils.debug.message("Request: should contain only"
288                                + " one AuthenticationQuery.");
289                    }
290                    throw new SAMLRequesterException(
291                        SAMLUtils.bundle.getString("wrongInput"));
292                }
293                contentType = AUTHENTICATION_QUERY;
294                query = (AuthenticationQuery) temp;
295            } else if (temp instanceof AuthorizationDecisionQuery) {
296                // make sure this is the first one on the list
297                if ((contentType != NOT_SUPPORTED) ||
298                    // and make sure there is no other elements on the list
299                    (i != (length - 1))) {
300                    if (SAMLUtils.debug.messageEnabled()) {
301                        SAMLUtils.debug.message("Request: should contain only"
302                                + " one AuthorizationDecisionQuery.");
303                    }
304                    throw new SAMLRequesterException(
305                        SAMLUtils.bundle.getString("wrongInput"));
306                }
307                contentType = AUTHORIZATION_DECISION_QUERY;
308                query = (AuthorizationDecisionQuery) temp;
309            } else if (temp instanceof AttributeQuery) {
310                // make sure this is the first one on the list
311                if ((contentType != NOT_SUPPORTED) ||
312                    // and make sure there is no other elements on the list
313                    (i != (length - 1))) {
314                    if (SAMLUtils.debug.messageEnabled()) {
315                        SAMLUtils.debug.message("Request: should contain only"
316                                + " one AttributeQuery.");
317                    }
318                    throw new SAMLRequesterException(
319                        SAMLUtils.bundle.getString("wrongInput"));
320                }
321                contentType = ATTRIBUTE_QUERY;
322                query = (AttributeQuery) temp;
323            } else if (temp instanceof AssertionIDReference) {
324                // if this is not the first element on the list , and if the
325                // the previously assigned elements are not AssertionIDReference
326                if ((contentType != NOT_SUPPORTED) &&
327                    (contentType != ASSERTION_ID_REFERENCE)) {
328                    if (SAMLUtils.debug.messageEnabled()) {
329                        SAMLUtils.debug.message("Request: should contain"
330                                + " one or more AssertionIDReference.");
331                    }
332                    throw new SAMLRequesterException(
333                        SAMLUtils.bundle.getString("wrongInput"));
334                }
335                contentType = ASSERTION_ID_REFERENCE;
336                if (assertionIDRefs == Collections.EMPTY_LIST) {
337                    assertionIDRefs = new ArrayList();
338                }
339                assertionIDRefs.add((AssertionIDReference) temp);
340            } else if (temp instanceof AssertionArtifact) {
341                // if this is not the first element on the list, and if the
342                // previously assigned elements are not AssertionArtifact:
343                if ((contentType != NOT_SUPPORTED) &&
344                    (contentType != ASSERTION_ARTIFACT)) {
345                    if (SAMLUtils.debug.messageEnabled()) {
346                        SAMLUtils.debug.message("Request: should contain "
347                                + " one or more AssertionArtifact.");
348                    }
349                    throw new SAMLRequesterException(
350                        SAMLUtils.bundle.getString("wrongInput"));
351                }
352                contentType = ASSERTION_ARTIFACT;
353                if (artifacts == Collections.EMPTY_LIST) {
354                    artifacts = new ArrayList();
355                }
356                artifacts.add((AssertionArtifact) temp);
357            } else { // everything else
358                SAMLUtils.debug.message("Request: wrong input.");
359                throw new SAMLRequesterException(
360                        SAMLUtils.bundle.getString("wrongInput"));
361            }
362        }
363    }
364
365    /**
366     * This constructor shall only be used at the client side to construct a
367     * Request object.
368     * NOTE: The content here is just the body for the Request. The 
369     * constructor will add <code>MajorVersion</code>,
370     * <code>MinorVersion</code>, etc. to form a complete Request.
371     *
372     * @param requestId If it's null, the constructor will create one.
373     * @param query A Query to be included in the Request.
374     * @throws SAMLException if an error occurs.
375     */
376    public Request(String requestId, Query query) throws SAMLException {
377        if ((requestId != null) && (requestId.length() != 0)) {
378            requestID = requestId;
379        } else {
380            // random generate one 
381            requestID = SAMLUtils.generateID();
382            if (requestID == null) {
383                SAMLUtils.debug.error("Request: couldn't generate RequestID.");
384                throw new SAMLRequesterException(
385                    SAMLUtils.bundle.getString("errorGenerateID"));
386            }
387        }
388
389        if (query == null) {
390            SAMLUtils.debug.message("Request: empty content.");
391            throw new SAMLRequesterException(
392                        SAMLUtils.bundle.getString("nullInput"));
393        }
394
395        if (query instanceof AuthenticationQuery) {
396            contentType = AUTHENTICATION_QUERY;
397        } else if (query instanceof AuthorizationDecisionQuery) {
398            contentType = AUTHORIZATION_DECISION_QUERY;
399        } else if (query instanceof AttributeQuery) {
400            contentType = ATTRIBUTE_QUERY;
401        } else {
402            if (SAMLUtils.debug.messageEnabled()) {
403                SAMLUtils.debug.message("Request: this type of query is not"
404                                + " supported.");
405            }
406            throw new SAMLResponderException(
407                        SAMLUtils.bundle.getString("queryNotSupported"));
408        }
409        this.query = query;
410                issueInstant = newDate();
411    }
412
413    /**
414     * This constructor shall only be used at the client side to construct a
415     * Request object.
416     * NOTE: The content here is just the body for the Request. The 
417     * constructor will add <code>MajorVersion</code>,
418     * <code>MinorVersion</code>, etc. to form a complete Request.
419     *
420     * @param requestId If it's null, the constructor will create one.
421     * @param contents A List of objects that are the contents of Request that
422     *        the client wants to send to the server. It could be an
423     *        <code>AuthenticationQuery</code>,
424     *        <code>AuthorizationDecisionQuery</code>, 
425     *        <code>AttributeQuery</code>, 1 or more
426     *        <code>AssertionIDReference</code>, or 1 or more of
427     *        <code>AssertionArtifact</code>.
428     * @throws SAMLException if an error occurs.
429     */
430    public Request(String requestId, List contents) throws SAMLException {
431        if (requestId != null) {
432            requestID = requestId;
433        } else {
434            // random generate one
435            requestID = SAMLUtils.generateID();
436            if (requestID == null) {
437                throw new SAMLRequesterException(
438                    SAMLUtils.bundle.getString("errorGenerateID"));
439            }
440        }
441        parseContents(contents);
442                issueInstant = newDate();
443    }
444
445    /**
446     * This method shall only be used at the server side to reconstruct
447     * a Request object based on the XML document received from client.
448     * The schema of this XML document is described above.
449     *
450     * @param xml The Request XML String.
451     *          NOTE: this is a complete SAML request XML string with
452     *          <code>RequestID</code>, <code>MajorVersion</code>, etc.
453     * @return Request object
454     * @exception SAMLException if an error occurs.
455     */
456    public static Request parseXML(String xml) throws SAMLException {
457        // parse the xml string
458        Document doc = XMLUtils.toDOMDocument(xml, SAMLUtils.debug);
459        Element root = doc.getDocumentElement();
460
461        return new Request(root);
462    }
463
464    /**
465     * Constructor.
466     *
467     * @param root <code>Request</code> element
468     * @throws SAMLException
469     */
470    public Request(Element root) throws SAMLException {
471        // Make sure this is a Request
472        String tag = null;
473        if (root == null) {
474            SAMLUtils.debug.message("Request(Element): null input.");
475            throw new SAMLRequesterException(
476                                SAMLUtils.bundle.getString("nullInput"));
477        }
478        if (((tag = root.getLocalName()) == null) ||
479            (!tag.equals("Request"))) {
480            SAMLUtils.debug.message("Request(Element): wrong input");
481            throw new SAMLRequesterException(
482                                SAMLUtils.bundle.getString("wrongInput"));
483        }
484
485        List signs = XMLUtils.getElementsByTagNameNS1(root,
486                                        SAMLConstants.XMLSIG_NAMESPACE_URI,
487                                        SAMLConstants.XMLSIG_ELEMENT_NAME);
488        int signsSize = signs.size();
489        if (signsSize == 1) {
490            XMLSignatureManager manager = XMLSignatureManager.getInstance();
491            valid = manager.verifyXMLSignature(root,
492                REQUEST_ID_ATTRIBUTE, null);
493            if (!valid) {
494                if (SAMLUtils.debug.messageEnabled()) {
495                    SAMLUtils.debug.message("Request(Element): couldn't verify"
496                        + " Request's signature.");
497                }
498            }
499            xmlString = XMLUtils.print(root);
500            signed = true;
501        } else if (signsSize != 0) {
502            if (SAMLUtils.debug.messageEnabled()) {
503                SAMLUtils.debug.message("Request(Element): included more than"
504                    + " one Signature element.");
505            }
506            throw new SAMLRequesterException(
507                SAMLUtils.bundle.getString("moreElement"));
508        }
509
510        // Attribute RequestID
511        requestID = root.getAttribute("RequestID");
512        if ((requestID == null) || (requestID.length() == 0)) {
513            if (SAMLUtils.debug.messageEnabled()) {
514                SAMLUtils.debug.message("Request(Element): Request doesn't "
515                                        + "have a RequestID.");
516            }
517            throw new SAMLRequesterException(
518                                SAMLUtils.bundle.getString("missingAttribute"));
519        }
520
521        // Attribute MajorVersion
522        parseMajorVersion(requestID, root.getAttribute("MajorVersion"));
523
524        // Attribute MinorVersion
525        parseMinorVersion(requestID, root.getAttribute("MinorVersion"));
526
527        // Attribute IssueInstant
528        String instantString = root.getAttribute("IssueInstant");
529        if ((instantString == null) || (instantString.length() == 0)) {
530            SAMLUtils.debug.message("Request(Element): missing IssueInstant");
531            throw new SAMLRequesterException(
532                SAMLUtils.bundle.getString("missingAttribute"));
533        } else {
534            try {
535                issueInstant = DateUtils.stringToDate(instantString);
536            } catch (ParseException e) {
537                SAMLUtils.debug.message(
538                    "Request(Element): could not parse IssueInstant", e);
539                throw new SAMLRequesterException(SAMLUtils.bundle.getString(
540                        "wrongInput"));
541            }
542        }
543
544        // get the contents of the request
545        NodeList contentnl = root.getChildNodes();
546        Node child;
547        String nodeName;
548        String respondWith;
549        for (int i = 0, length = contentnl.getLength(); i < length; i++) {
550            child = contentnl.item(i);
551            if ((nodeName = child.getLocalName()) != null) {
552                if (nodeName.equals("RespondWith")) {
553                    respondWith = XMLUtils.getElementValue((Element) child);
554                    if (respondWith.length() == 0) {
555                        if (SAMLUtils.debug.messageEnabled()) {
556                            SAMLUtils.debug.message("Request(Element): wrong "
557                                + "RespondWith value.");
558                        }
559                        throw new SAMLRequesterException(
560                            SAMLUtils.bundle.getString("wrongInput"));
561                    }
562                    if (respondWiths == Collections.EMPTY_LIST) {
563                        respondWiths = new ArrayList();
564                    }
565                    respondWiths.add(respondWith);
566                } else if (nodeName.equals("Signature")) {
567                    signature = (Element) child;
568                } else if (nodeName.equals("AuthenticationQuery")) {
569                    // make sure the content is not assigned already
570                    if (contentType != NOT_SUPPORTED) {
571                        if (SAMLUtils.debug.messageEnabled()) {
572                            SAMLUtils.debug.message("Request(Element): should"
573                                + "contain only one AuthenticationQuery.");
574                        } 
575                        throw new SAMLRequesterException(
576                            SAMLUtils.bundle.getString("wrongInput"));
577                    }
578                    contentType = AUTHENTICATION_QUERY;
579                    query = new AuthenticationQuery((Element) child);
580                } else if (nodeName.equals("AuthorizationDecisionQuery")) {
581                    // make sure content is not assigned already
582                    if (contentType != NOT_SUPPORTED) {
583                        if (SAMLUtils.debug.messageEnabled()) {
584                            SAMLUtils.debug.message("Request(Element): should"
585                                + "contain only one "
586                                + "AuthorizationDecisionQuery.");
587                        } 
588                        throw new SAMLRequesterException(
589                            SAMLUtils.bundle.getString("wrongInput"));
590                    }
591                    contentType = AUTHORIZATION_DECISION_QUERY;
592                    query = new AuthorizationDecisionQuery((Element) child);
593                } else if (nodeName.equals("AttributeQuery")) {
594                    // make sure content is not assigned already
595                    if (contentType != NOT_SUPPORTED) {
596                        if (SAMLUtils.debug.messageEnabled()) {
597                            SAMLUtils.debug.message("Request(Element): should"
598                                + "contain only one AttributeQuery.");
599                        } 
600                        throw new SAMLRequesterException(
601                            SAMLUtils.bundle.getString("wrongInput"));
602                    }
603                    contentType = ATTRIBUTE_QUERY;
604                    query = new AttributeQuery((Element) child);
605                } else if (nodeName.equals("AssertionIDReference")) {
606                    // make sure the content has no other elements assigned
607                    if ((contentType != NOT_SUPPORTED) &&
608                        (contentType != ASSERTION_ID_REFERENCE)) {
609                        if (SAMLUtils.debug.messageEnabled()) {
610                            SAMLUtils.debug.message("Request(Element): "
611                                + "contained mixed contents.");
612                        } 
613                        throw new SAMLRequesterException(
614                            SAMLUtils.bundle.getString("wrongInput"));
615                    }
616                    contentType = ASSERTION_ID_REFERENCE;
617                    if (assertionIDRefs == Collections.EMPTY_LIST) {
618                        assertionIDRefs = new ArrayList();
619                    }
620                    assertionIDRefs.add(new AssertionIDReference(
621                                XMLUtils.getElementValue((Element) child)));
622                } else if (nodeName.equals("AssertionArtifact")) {
623                    // make sure the content has no other elements assigned
624                    if ((contentType != NOT_SUPPORTED) &&
625                        (contentType != ASSERTION_ARTIFACT)) {
626                        if (SAMLUtils.debug.messageEnabled()) {
627                            SAMLUtils.debug.message("Request(Element): "
628                                + "contained mixed contents.");
629                        } 
630                        throw new SAMLRequesterException(
631                            SAMLUtils.bundle.getString("wrongInput"));
632                    }
633                    contentType = ASSERTION_ARTIFACT;
634                    if (artifacts == Collections.EMPTY_LIST) {
635                        artifacts = new ArrayList();
636                    }
637                    artifacts.add(new AssertionArtifact(
638                                XMLUtils.getElementValue((Element) child)));
639                } else if (nodeName.equals("Query") ||
640                            nodeName.equals("SubjectQuery")) {
641                    parseQuery(child);
642                } else {
643                    if (SAMLUtils.debug.messageEnabled()) {
644                        SAMLUtils.debug.message("Request(Element): invalid"
645                                + " node" + nodeName);
646                    }
647                    throw new SAMLRequesterException(
648                        SAMLUtils.bundle.getString("wrongInput"));
649                } // check nodeName
650            } // if nodeName != null
651        } // done for the nodelist loop
652
653        if (contentType == NOT_SUPPORTED) {
654            SAMLUtils.debug.message("Request: empty content.");
655            throw new SAMLRequesterException(
656                        SAMLUtils.bundle.getString("wrongInput"));
657        }
658    }
659
660    /**
661     * Parse the input and set the majorVersion accordingly.
662     * @param majorVer a String representing the MajorVersion to be set.
663     * @exception SAMLException when the version mismatchs.
664     */
665    private void parseMajorVersion(String reqID, String majorVer)
666                                   throws SAMLException {
667        try {
668            majorVersion = Integer.parseInt(majorVer);
669        } catch (NumberFormatException e) {
670            if (SAMLUtils.debug.messageEnabled()) {
671                SAMLUtils.debug.message("Request(Element): invalid "
672                        + "MajorVersion", e);
673            }
674            throw new SAMLRequesterException(
675                SAMLUtils.bundle.getString("wrongInput"));
676        }
677
678        if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) { 
679            if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) {
680                if (SAMLUtils.debug.messageEnabled()) {
681                    SAMLUtils.debug.message("Request(Element):MajorVersion of "
682                                + "the Request is too high.");
683                }
684                throw new SAMLRequestVersionTooHighException(reqID + "|"+
685                        SAMLUtils.bundle.getString("requestVersionTooHigh"));
686            } else {
687                if (SAMLUtils.debug.messageEnabled()) {
688                    SAMLUtils.debug.message("Request(Element):MajorVersion of "
689                                + "the Request is too low.");
690                }
691                throw new SAMLRequestVersionTooLowException(reqID + "|"+
692                        SAMLUtils.bundle.getString("requestVersionTooLow"));
693            }
694        }
695
696    }
697
698    /**
699     * Parse the input and set the minorVersion accordingly.
700     * @param minorVer a String representing the MinorVersion to be set.
701     * @exception SAMLException when the version mismatchs.
702     */
703    private void parseMinorVersion(String reqID, String minorVer) 
704                 throws SAMLException {
705        try {
706            minorVersion = Integer.parseInt(minorVer);
707        } catch (NumberFormatException e) {
708            if (SAMLUtils.debug.messageEnabled()) {
709                SAMLUtils.debug.message("Request(Element): invalid "
710                        + "MinorVersion", e);
711            }
712            throw new SAMLRequesterException(
713                SAMLUtils.bundle.getString("wrongInput"));
714        }
715    
716        if (minorVersion > SAMLConstants.PROTOCOL_MINOR_VERSION_ONE) {
717            if (SAMLUtils.debug.messageEnabled()) {
718                SAMLUtils.debug.message("Request(Element): MinorVersion"
719                                + " of the Request is too high.");
720            }
721            throw new SAMLRequestVersionTooHighException(reqID + "|"+
722                         SAMLUtils.bundle.getString("requestVersionTooHigh"));    
723        } else if (minorVersion < SAMLConstants.PROTOCOL_MINOR_VERSION_ZERO) { 
724            if (SAMLUtils.debug.messageEnabled()) {
725                SAMLUtils.debug.message("Request(Element): MinorVersion"
726                                + " of the Request is too low.");
727            }
728            throw new SAMLRequestVersionTooLowException( reqID + "|"+
729                         SAMLUtils.bundle.getString("requestVersionTooLow"));
730        }
731    }
732
733    /**
734     * This method parses the Query or SubjectQuery represented by a DOM tree
735     * Node. It then checks and sets data members if it is a supported query,
736     * such as AuthenticationQuery, AttributeQeury, or 
737     * <code>AuthorizationDecisionQuery</code>.
738     * @param child A DOM Node to be parsed.
739     * @exception SAMLException if it's not a supported query.
740     */
741    private void parseQuery(Node child) throws SAMLException {
742        NamedNodeMap nm = child.getAttributes();
743        int len = nm.getLength();
744        String attrName;
745        String attrValue;
746        Attr attr;
747        boolean found = false;
748        for (int j = 0; j < len; j++) {
749            attr = (Attr) nm.item(j);
750            attrName = attr.getLocalName();
751            if ((attrName != null) && (attrName.equals("type"))) {
752                attrValue = attr.getNodeValue();
753                if (attrValue.equals("AuthenticationQueryType")) {
754                    if (contentType != NOT_SUPPORTED) {
755                        if (SAMLUtils.debug.messageEnabled()) {
756                            SAMLUtils.debug.message("Request(Element): should"
757                                + " contain only one AuthenticationQuery.");
758                        } 
759                        throw new SAMLRequesterException(
760                            SAMLUtils.bundle.getString("wrongInput"));
761                    }
762                    contentType = AUTHENTICATION_QUERY;
763                    query = new AuthenticationQuery((Element) child);
764                } else if (attrValue.equals(
765                                        "AuthorizationDecisionQueryType")) {
766                    if (contentType != NOT_SUPPORTED) {
767                        if (SAMLUtils.debug.messageEnabled()) {
768                            SAMLUtils.debug.message("Request(Element): should "
769                                + "contain one AuthorizationDecisionQuery.");
770                        }
771                        throw new SAMLRequesterException(SAMLUtils.
772                                bundle.getString("wrongInput"));
773                    }
774                    contentType = AUTHORIZATION_DECISION_QUERY;
775                    query = new AuthorizationDecisionQuery((Element) child);
776                } else if (attrValue.equals("AttributeQueryType")) {
777                    if (contentType != NOT_SUPPORTED) {
778                        if (SAMLUtils.debug.messageEnabled()) {
779                            SAMLUtils.debug.message("Request(Element): should "
780                                + "contain one AttributeQuery.");
781                        }
782                        throw new SAMLRequesterException(SAMLUtils.
783                                bundle.getString("wrongInput"));
784                    }
785                    contentType = ATTRIBUTE_QUERY;
786                    query = new AttributeQuery((Element) child);
787                } else {
788                    if (SAMLUtils.debug.messageEnabled()) {
789                        SAMLUtils.debug.message("Request(Element): This type of"
790                                + " " + attrName + " is not supported.");
791                    }
792                    throw new SAMLResponderException(
793                        SAMLUtils.bundle.getString("queryNotSupported"));
794                } // check typevalue
795                found = true;
796                break;
797            } // if found type attribute
798        } // end attribute for loop
799        // if not found type
800        if (!found) {
801            if (SAMLUtils.debug.messageEnabled()) {
802                SAMLUtils.debug.message("Request(Element): missing"
803                        + " xsi:type definition in " + child.getLocalName());
804            }
805            throw new SAMLRequesterException(
806                SAMLUtils.bundle.getString("wrongInput"));
807        }
808    }
809
810    /**
811     * Gets the query of the Request.
812     *
813     * @return the query included in the request; or null if the
814     *         <code>contentType</code> of the request is not
815     *         <code>AUTHENTICATION_QUERY</code>,
816     *         <code>AUTHORIZATION_DECISION_QUERY</code>, or
817     *         <code>ATTRIBUTE_QUERY</code>.
818     */
819    public Query getQuery() {
820        return query;
821    }
822
823    /**
824     * Gets the <code>AssertionIDReference</code>(s) of the Request.
825     * @return a List of <code>AssertionIDReference</code>s included in the
826     *         request; or <code>Collections.EMPTY_LIST</code> if the
827     *         <code>contentType</code> of the request is not
828     *         <code>ASSERTION_ID_REFERENCE</code>.
829     */
830    public List getAssertionIDReference() {
831        return assertionIDRefs;
832    }
833
834    /**
835     * Gets the <code>AssertionArtifact</code>(s) of the Request.
836     * @return a List of <code>AssertionArtifact</code>s included in the
837     *         request; or <code>Collections.EMPTY_LIST</code> if the
838     *         <code>contentType</code> of the request is not
839     *         <code>ASSERTION_ARTIFACT</code>.
840     */
841    public List getAssertionArtifact() {
842        return artifacts;
843    }
844
845    /**
846     * Returns the type of content this Request has.
847     *
848     * @return The type of the content. The possible values are defined in
849     *         Request.
850     */
851    public int getContentType() {
852        return contentType;
853    }
854    
855    /**
856     * Set the signature for the Response.
857     *
858     * @param elem <code>ds:Signature</code> element
859     * @return true if the operation succeeds.
860     */
861    public boolean setSignature(Element elem) {
862        signatureString = XMLUtils.print(elem); 
863        return super.setSignature(elem); 
864    }
865
866    /**
867     * This method translates the request to an XML document String based on
868     * the Request schema described above.
869     * NOTE: this is a complete SAML request XML string with
870     * <code>RequestID</code>, <code>MajorVersion</code>, etc.
871     *
872     * @return An XML String representing the request.
873     */
874    public String toString() {
875        return toString(true, true);
876    }
877
878    /**
879     * Returns a String representation of the
880     * <code>&lt;samlp:Request&gt;</code> element.
881     *
882     * @param includeNS Determines whether or not the namespace qualifier
883     *        is prepended to the Element when converted
884     * @param declareNS Determines whether or not the namespace is declared
885     *        within the Element.
886     * @return A string containing the valid XML for this element
887     */
888    public String toString(boolean includeNS, boolean declareNS) {
889        return toString(includeNS, declareNS, false);
890    }
891
892    /**
893     * Returns a String representation of the
894     * <code>&lt;samlp:Request&gt;</code> element.
895     *
896     * @param includeNS Determines whether or not the namespace qualifier
897     *        is prepended to the Element when converted
898     * @param declareNS Determines whether or not the namespace is declared
899     *        within the Element.
900     * @param includeHeader Determines whether the output include the XML
901     *        declaration header.
902     * @return A string containing the valid XML for this element
903     */
904    public String toString(boolean includeNS,
905                        boolean declareNS,
906                        boolean includeHeader)
907    {
908        if (signed && (xmlString != null)) {
909            return xmlString;
910        }
911
912        StringBuffer xml = new StringBuffer(300);
913        if (includeHeader) {
914            xml.append("<?xml version=\"1.0\" encoding=\"").
915                append(SAMLConstants.DEFAULT_ENCODING).append("\" ?>\n");
916        }
917        String prefix = "";
918        String uri = "";
919        if (includeNS) {
920            prefix = SAMLConstants.PROTOCOL_PREFIX;
921        }
922        if (declareNS) {
923            uri = SAMLConstants.PROTOCOL_NAMESPACE_STRING;
924        }
925        String instantString = DateUtils.toUTCDateFormat(issueInstant);
926
927        xml.append("<").append(prefix).append("Request").append(uri).
928            append(" RequestID=\"").append(requestID).append("\"").
929            append(" MajorVersion=\"").append(majorVersion).append("\"").
930            append(" MinorVersion=\"").append(minorVersion).append("\"").
931            append(" IssueInstant=\"").append(instantString).append("\"").
932            append(">\n");
933        if((respondWiths != null) && (respondWiths != Collections.EMPTY_LIST)){
934            Iterator i = respondWiths.iterator();
935            String respondWith = null;
936            while (i.hasNext()) {
937                respondWith = (String) i.next();
938                xml.append("<").append(prefix).append("RespondWith>");
939                if (respondWith.startsWith(SAMLConstants.ASSERTION_PREFIX)) {
940                    xml.append(respondWith);
941                } else {
942                    try {
943                        xml.append(checkAndGetRespondWith(respondWith));
944                    } catch (SAMLException e) {
945                        SAMLUtils.debug.error("Request.toString: ", e);
946                        xml.append(respondWith);
947                    }
948                }
949                xml.append("</").append(prefix).append("RespondWith>\n");
950            }
951        }
952
953        if (signed) {
954            if (signatureString != null) {
955                xml.append(signatureString);
956            } else if (signature != null) {
957                signatureString = XMLUtils.print(signature);
958                xml.append(signatureString);
959            }
960        }
961
962        Iterator j;
963        switch (contentType) {
964        case AUTHENTICATION_QUERY:
965            xml.append(((AuthenticationQuery)query).toString(includeNS, false));
966            break;
967        case AUTHORIZATION_DECISION_QUERY:
968            xml.append(((AuthorizationDecisionQuery)query).toString(includeNS,
969                                                                    false));
970            break;
971        case ATTRIBUTE_QUERY:
972            xml.append(((AttributeQuery)query).toString(includeNS, false));
973            break;
974        case ASSERTION_ID_REFERENCE:
975            j = assertionIDRefs.iterator();
976            while (j.hasNext()) {
977                xml.append(((AssertionIDReference) j.next()).
978                                                toString(true, true));
979            }
980            break;
981        case ASSERTION_ARTIFACT:
982            j = artifacts.iterator();
983            while (j.hasNext()) {
984                xml.append(((AssertionArtifact) 
985                                        j.next()).toString(includeNS, false));
986            }
987            break;
988        default:
989            break;
990        }
991
992        xml.append("</").append(prefix).append("Request>\n");
993        return xml.toString();
994    }
995}