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




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.