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: Response.java,v 1.3 2009/02/13 04:05:10 bina Exp $
026 *
027 */
028
029/**
030 * Portions Copyrighted 2014 ForgeRock AS
031 */
032package com.sun.identity.saml.protocol;
033
034import com.sun.identity.common.SystemConfigurationUtil;
035import com.sun.identity.shared.xml.XMLUtils; 
036
037import com.sun.identity.shared.DateUtils;
038
039import com.sun.identity.saml.assertion.Assertion;
040
041import com.sun.identity.saml.common.SAMLConstants;
042import com.sun.identity.saml.common.SAMLException;
043import com.sun.identity.saml.common.SAMLRequesterException;
044import com.sun.identity.saml.common.SAMLRequestVersionTooHighException;
045import com.sun.identity.saml.common.SAMLRequestVersionTooLowException;
046import com.sun.identity.saml.common.SAMLResponderException;
047import com.sun.identity.saml.common.SAMLUtils;
048import com.sun.identity.saml.common.SAMLVersionMismatchException;
049
050import com.sun.identity.saml.xmlsig.XMLSignatureManager;
051
052import java.io.ByteArrayOutputStream;
053import java.io.InputStream;
054
055import java.text.ParseException;
056
057import java.util.ArrayList;
058import java.util.Collections;
059import java.util.Date;
060import java.util.Iterator;
061import java.util.List;
062
063import org.w3c.dom.Document;
064import org.w3c.dom.Element;
065import org.w3c.dom.Node;
066import org.w3c.dom.NodeList;
067
068/**
069 * This <code>Response</code> class represents a Response XML document.
070 * The schema of Response is defined as the following:
071 *
072 * @supported.all.api
073 */
074public class Response extends AbstractResponse {
075
076    protected Status    status          = null;
077    protected List      assertions      = Collections.EMPTY_LIST;
078    protected String    xmlString       = null;
079    protected String    signatureString = null;
080    protected String    issuer          = null; 
081
082    // Response ID attribute name
083    private static final String RESPONSE_ID_ATTRIBUTE = "ResponseID";
084
085    /** default constructor */
086    protected Response() {}
087    
088     /**
089     * Return whether the signature on the object is valid or not.
090     * @return true if the signature on the object is valid; false otherwise.
091     */
092    public boolean isSignatureValid() {
093        if (signed & ! validationDone) {
094            valid = SAMLUtils.checkSignatureValid(
095                xmlString, RESPONSE_ID_ATTRIBUTE, issuer); 
096                
097            validationDone = true;
098        }
099        return valid; 
100    }
101
102    /**
103     * Method that signs the Response.
104     *
105     * @exception SAMLException if could not sign the Response.
106     */
107    public void signXML() throws SAMLException {
108        if (signed) {
109            if (SAMLUtils.debug.messageEnabled()) {
110                SAMLUtils.debug.message("Response.signXML: the response is "
111                    + "already signed.");
112            }
113            throw new SAMLException(
114                SAMLUtils.bundle.getString("alreadySigned"));
115        }
116        String certAlias =    
117            SystemConfigurationUtil.getProperty(
118            "com.sun.identity.saml.xmlsig.certalias");
119        if (certAlias == null) {
120            if (SAMLUtils.debug.messageEnabled()) {
121                SAMLUtils.debug.message("Response.signXML: couldn't obtain "
122                    + "this site's cert alias.");
123            }
124            throw new SAMLResponderException(
125                SAMLUtils.bundle.getString("cannotFindCertAlias"));
126        }
127        XMLSignatureManager manager = XMLSignatureManager.getInstance();
128        if ((majorVersion == 1) && (minorVersion == 0)) {
129            SAMLUtils.debug.message("Request.signXML: sign with version 1.0");
130            signatureString = manager.signXML(this.toString(true, true),
131                              certAlias);
132            // this block is used for later return of signature element by
133            // getSignature() method
134            signature =
135                XMLUtils.toDOMDocument(signatureString, SAMLUtils.debug)
136                        .getDocumentElement();
137        } else {
138            Document doc = XMLUtils.toDOMDocument(this.toString(true, true),
139                                                  SAMLUtils.debug);
140            // sign with SAML 1.1 spec & include cert in KeyInfo
141            signature = manager.signXML(doc, certAlias, null,
142                RESPONSE_ID_ATTRIBUTE, getResponseID(), true, null);
143            signatureString = XMLUtils.print(signature);
144        }
145        signed = true;
146        xmlString = this.toString(true, true);
147    }
148
149    private void buildResponse(String responseID,
150                    String inResponseTo,
151                    Status status,
152                    String recipient,
153                    List contents) throws SAMLException
154    {
155        if ((responseID == null) || (responseID.length() == 0)) {
156            // generate one
157            this.responseID = SAMLUtils.generateID();
158            if (this.responseID == null) {
159                throw new SAMLRequesterException(
160                        SAMLUtils.bundle.getString("errorGenerateID"));
161            }
162        } else {
163            this.responseID = responseID;
164        }
165
166        this.inResponseTo = inResponseTo;
167
168        this.recipient = recipient;
169
170        issueInstant = new Date();
171
172        if (status == null) {
173            SAMLUtils.debug.message("Response: missing <Status>.");
174            throw new SAMLRequesterException(
175                        SAMLUtils.bundle.getString("missingElement"));
176        }
177        this.status = status;
178
179        if ((contents != null) &&
180            (contents != Collections.EMPTY_LIST)) {
181            int length = contents.size();
182            for (int i = 0; i < length; i++) {
183                Object temp = contents.get(i);
184                if (!(temp instanceof Assertion)) {
185                    if (SAMLUtils.debug.messageEnabled()) {
186                        SAMLUtils.debug.message("Response: Wrong input "
187                                + "for Assertion.");
188                    }
189                    throw new SAMLRequesterException(
190                                SAMLUtils.bundle.getString("wrongInput"));
191                }
192            }
193            assertions = contents;
194        }
195    }
196
197    /**
198     * This constructor shall only be used at the server side to construct
199     * a Response object.
200     * NOTE: The content here is just the body for the Response. The
201     * constructor will add the unique <code>ResponseID</code>,
202     * <code>MajorVersion</code>, etc. to form a complete Response object.
203     *
204     * @param responseID If it's null, the constructor will create one.
205     * @param inResponseTo the <code>RequestID</code> that this response is
206     *        corresponding. It could be null or empty string "".
207     * @param status The status of the response.
208     * @param contents A List of Assertions that are the content of the
209     *        Response. It could be null when there is no Assertion.
210     * @throws SAMLException if error occurs.
211     */
212    public Response(String responseID,
213                    String inResponseTo,
214                    Status status,
215                    List contents) throws SAMLException
216    {
217        buildResponse(responseID, inResponseTo, status, null, contents);
218    }
219
220    /**
221     * This constructor shall only be used at the server side to construct
222     * a Response object.
223     * NOTE: The content here is just the body for the Response. The
224     * constructor will add the unique <code>ResponseID</code>,
225     * <code>MajorVersion</code>, etc. to form a complete Response object.
226     *
227     * @param responseID If it's null, the constructor will create one.
228     * @param inResponseTo the <code>RequestID</code> that this response is
229     *        corresponding. It could be null or empty string "".
230     * @param status The status of the response.
231     * @param recipient The intended recipient of the response. It could be
232     *        null or empty string since it's optional.
233     * @param contents A List of Assertions that are the content of the
234     *        Response. It could be null when there is no Assertion.
235     * @throws SAMLException if error occurs.
236     */
237    public Response(String responseID,
238                    String inResponseTo,
239                    Status status,
240                    String recipient,
241                    List contents) throws SAMLException
242    {
243        buildResponse(responseID, inResponseTo, status, recipient, contents);
244    }
245
246    /**
247     * This constructor shall only be used at the server side to construct
248     * a Response object.
249     * NOTE: The content here is just the body for the Response. The
250     * constructor will add the unique <code>ResponseID</code>,
251     * <code>MajorVersion</code>, etc. to form a complete Response object.
252     *
253     * @param responseID If it's null, the constructor will create one.
254     * @param status The status of the response.
255     * @param recipient The intended recipient of the response. It could be
256     *        null or empty string since it's optional.
257     * @param contents A List of Assertions that are the content of the
258     *        Response. It could be null when there is no Assertion.
259     * @throws SAMLException if error occurs.
260     */
261    public Response(String responseID,
262                    Status status,
263                    String recipient,
264                    List contents) throws SAMLException
265    {
266        buildResponse(responseID, null, status, recipient, contents);
267    }
268
269    /**
270     * This constructor shall only be used at the server side to construct
271     * a Response object.
272     * NOTE: The content here is just the body for the Response. The
273     * constructor will add the unique <code>ResponseID</code>,
274     * <code>MajorVersion</code>, etc. to form a complete Response object.
275     *
276     * @param responseID If it's null, the constructor will create one.
277     * @param status The status of the response.
278     * @param contents A List of Assertions that are the content of the
279     *        Response. It could be null when there is no Assertion.
280     * @throws SAMLException if error occurs.
281     */
282    public Response(String responseID,
283                    Status status,
284                    List contents) throws SAMLException
285    {
286        buildResponse(responseID, null, status, null, contents);
287    }
288
289    /**
290     * Returns Response object based on the XML document received from server.
291     * This method is used primarily at the client side. The schema of the XML
292     * document is describe above.
293     *
294     * @param xml The Response XML document String.
295     *          NOTE: this is a complete SAML response XML string with
296     *          <code>ResponseID</code>, <code>MajorVersion</code>, etc.
297     * @return Response object based on the XML document received from server.
298     * @exception SAMLException if XML parsing failed
299     */
300    public static Response parseXML(String xml) throws SAMLException {
301        // parse the xml string
302        Document doc = XMLUtils.toDOMDocument(xml, SAMLUtils.debug);
303        Element root = doc.getDocumentElement();
304
305        return new Response(root);
306    }
307
308    /**
309     * Returns Response object based on the XML document received from server.
310     * This method is used primarily at the client side. The schema of the XML
311     * document is describe above.
312     *
313     * @param is The Response XML <code>InputStream</code>.
314     *         NOTE: The <code>InputStream</code> contains a complete 
315     *         SAML response with
316     *         <code>ResponseID</code>, <code>MajorVersion</code>, etc.
317     * @return Response object based on the XML document received from server.
318     * @exception SAMLException if XML parsing failed
319     */
320    public static Response parseXML(InputStream is) throws SAMLException {
321        Document doc = XMLUtils.toDOMDocument(is, SAMLUtils.debug);
322        Element root = doc.getDocumentElement();
323
324        return new Response(root);
325    }
326
327    /**
328     * Constructor.
329     *
330     * @param root <code>Response</code> element
331     * @throws SAMLException if error occurs.
332     */
333    public Response(Element root) throws SAMLException {
334        // Make sure this is a Response
335        if (root == null) {
336            SAMLUtils.debug.message("Response(Element): null input.");
337            throw new SAMLRequesterException(
338                SAMLUtils.bundle.getString("nullInput"));
339        }
340        String tag = null;
341        if (((tag = root.getLocalName()) == null) ||
342            (!tag.equals("Response"))) {
343            SAMLUtils.debug.message("Response(Element): wrong input.");
344            throw new SAMLRequesterException(
345                SAMLUtils.bundle.getString("wrongInput"));
346        }
347
348        List signs = XMLUtils.getElementsByTagNameNS1(root,
349                                        SAMLConstants.XMLSIG_NAMESPACE_URI,
350                                        SAMLConstants.XMLSIG_ELEMENT_NAME);
351        int signsSize = signs.size();
352        if (signsSize == 1) {
353            xmlString = XMLUtils.print(root);
354            signed = true;
355        } else if (signsSize != 0) {
356            if (SAMLUtils.debug.messageEnabled()) {
357                SAMLUtils.debug.message("Response(Element): included more than"
358                    + " one Signature element.");
359            }
360            throw new SAMLRequesterException(
361                SAMLUtils.bundle.getString("moreElement"));
362        }
363
364        // Attribute ResponseID
365        responseID = root.getAttribute("ResponseID");
366        if ((responseID == null) || (responseID.length() == 0)) {
367            if (SAMLUtils.debug.messageEnabled()) {
368                SAMLUtils.debug.message("Response.parseXML: "
369                                + "Reponse doesn't have ResponseID.");
370            }
371            throw new SAMLRequesterException(
372                SAMLUtils.bundle.getString("missingAttribute"));
373        }
374
375        // Attribute InResponseTo
376        if (root.hasAttribute("InResponseTo")) {
377            inResponseTo = root.getAttribute("InResponseTo");
378        }
379
380        // Attribute MajorVersion
381        parseMajorVersion(root.getAttribute("MajorVersion"));
382
383        parseMinorVersion(root.getAttribute("MinorVersion"));
384
385        if (root.hasAttribute("Recipient")) {
386            recipient = root.getAttribute("Recipient");
387        }
388
389        // Attribute IssueInstant
390        String instantString = root.getAttribute("IssueInstant");
391        if ((instantString == null) || (instantString.length() == 0)) {
392            SAMLUtils.debug.message("Response(Element): missing IssueInstant");
393            throw new SAMLRequesterException(
394                SAMLUtils.bundle.getString("missingAttribute"));
395        } else {
396            try {
397                issueInstant = DateUtils.stringToDate(instantString);
398            } catch (ParseException e) {
399                SAMLUtils.debug.message(
400                    "Resposne(Element): could not parse IssueInstant", e);
401                throw new SAMLRequesterException(SAMLUtils.bundle.getString(
402                        "wrongInput"));
403            }
404        }
405
406        NodeList nl = root.getChildNodes();
407        Node child;
408        String childName;
409        int length = nl.getLength();
410        for (int i = 0; i < length; i++) {
411            child = nl.item(i);
412            if ((childName = child.getLocalName()) != null) {
413                if (childName.equals("Signature")) {
414                    signature = (Element) child;
415                } else if (childName.equals("Status")) {
416                    if (status != null) {
417                        if (SAMLUtils.debug.messageEnabled()) {
418                            SAMLUtils.debug.message("Response: included more"
419                                + " than one <Status>");
420                        }
421                        throw new SAMLRequesterException(
422                            SAMLUtils.bundle.getString("moreElement"));
423                    }
424                    status = new Status((Element) child);
425                } else if (childName.equals("Assertion")) {
426                    if (assertions == Collections.EMPTY_LIST) {
427                        assertions = new ArrayList();
428                    }
429                    Element canoEle = SAMLUtils.getCanonicalElement(child);
430                    if (canoEle == null) {
431                        throw new SAMLRequesterException(
432                            SAMLUtils.bundle.getString("errorCanonical"));
433                    }
434
435                    Assertion oneAssertion= new Assertion(canoEle);
436                    issuer = oneAssertion.getIssuer(); 
437                    assertions.add(oneAssertion);
438                } else {
439                    if (SAMLUtils.debug.messageEnabled()) {
440                        SAMLUtils.debug.message("Response: included wrong "
441                            + "element:" + childName);
442                    }
443                    throw new SAMLRequesterException(
444                        SAMLUtils.bundle.getString("wrongInput"));
445                }
446            } // end if childName != null
447        } // end for loop
448
449        if (status == null) {
450            SAMLUtils.debug.message("Response: missing element <Status>.");
451            throw new SAMLRequesterException(
452                SAMLUtils.bundle.getString("oneElement"));
453        }
454    }
455
456    /**
457     * Parse the input and set the majorVersion accordingly.
458     * @param majorVer a String representing the MajorVersion to be set.
459     * @exception SAMLException when the version mismatchs.
460     */
461    private void parseMajorVersion(String majorVer) throws SAMLException {
462        try {
463            majorVersion = Integer.parseInt(majorVer);
464        } catch (NumberFormatException e) {
465            if (SAMLUtils.debug.messageEnabled()) {
466                SAMLUtils.debug.message("Response(Element): invalid "
467                    + "MajorVersion", e);
468            }
469            throw new SAMLRequesterException(
470                SAMLUtils.bundle.getString("wrongInput"));
471        }
472
473        if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) {
474            if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) {
475                if (SAMLUtils.debug.messageEnabled()) {
476                    SAMLUtils.debug.message("Response(Element):MajorVersion of"
477                        + " the Response is too high.");
478                }
479                throw new SAMLVersionMismatchException(
480                    SAMLUtils.bundle.getString("responseVersionTooHigh"));
481            } else {
482                if (SAMLUtils.debug.messageEnabled()) {
483                    SAMLUtils.debug.message("Response(Element):MajorVersion of"
484                        + " the Response is too low.");
485                }
486                throw new SAMLVersionMismatchException(
487                    SAMLUtils.bundle.getString("responseVersionTooLow"));
488            }
489        }
490    }
491
492    /**
493     * Parse the input and set the minorVersion accordingly.
494     * @param minorVer a String representing the MinorVersion to be set.
495     * @exception SAMLException when the version mismatchs.
496     */
497    private void parseMinorVersion(String minorVer) throws SAMLException {
498        try {
499            minorVersion = Integer.parseInt(minorVer);
500        } catch (NumberFormatException e) {
501            if (SAMLUtils.debug.messageEnabled()) {
502                SAMLUtils.debug.message("Response(Element): invalid "
503                    + "MinorVersion", e);
504            }
505            throw new SAMLRequesterException(
506                SAMLUtils.bundle.getString("wrongInput"));
507        }
508
509        if (minorVersion > SAMLConstants.PROTOCOL_MINOR_VERSION_ONE) {
510            if (SAMLUtils.debug.messageEnabled()) {
511                SAMLUtils.debug.message("Response(Element): MinorVersion"
512                                + " of the Response is too high.");
513            }
514            throw new SAMLRequestVersionTooHighException(
515                         SAMLUtils.bundle.getString("responseVersionTooHigh"));    
516        } else if (minorVersion < SAMLConstants.PROTOCOL_MINOR_VERSION_ZERO) { 
517            if (SAMLUtils.debug.messageEnabled()) {
518                SAMLUtils.debug.message("Response(Element): MinorVersion"
519                                + " of the Response is too low.");
520            }
521            throw new SAMLRequestVersionTooLowException( 
522                         SAMLUtils.bundle.getString("responseVersionTooLow"));
523        }
524    }
525
526    /** 
527     * This method returns the set of Assertions that is the content of
528     * the response.
529     * @return The set of Assertions that is the content of the response.
530     *          It could be Collections.EMPTY_LIST when there is no Assertion
531     *          in the response.
532     */
533    public List getAssertion() {
534        return assertions;
535    }
536
537    /**
538     * Add an assertion to the Response.
539     * @param assertion The assertion to be added.
540     * @return A boolean value: true if the operation is successful;
541     *          false otherwise.
542     */
543    public boolean addAssertion(Assertion assertion) {
544        if (signed) {
545            return false;
546        }
547        if (assertion == null) {
548            return false;
549        }
550        if ((assertions == null) || (assertions == Collections.EMPTY_LIST)) {
551            assertions = new ArrayList();
552        }
553        assertions.add(assertion);
554        return true;
555    }
556
557    /**
558     * Gets the Status of the Response.
559     * @return The Status of the response.
560     */
561    public Status getStatus() {
562        return status;
563    }
564
565    /**
566     * Set the Status of the Response.
567     *
568     * @param status The Status of the Response to be set.
569     * @return true if the operation is successful.
570     */
571    public boolean setStatus(Status status) {
572        if (signed) {
573            return false;
574        }
575        if (status == null) {
576            return false;
577        }
578        this.status = status;
579        return true;
580    }
581
582    /**
583     * Set the signature for the Response.
584     * @param elem ds:Signature element
585     * @return A boolean value: true if the operation succeeds; false otherwise.
586     */
587    public boolean setSignature(Element elem) {
588        signatureString = XMLUtils.print(elem); 
589        return super.setSignature(elem); 
590    }
591    
592    /**
593     * This method translates the response to an XML document String based on
594     * the Response schema described above.
595     * @return An XML String representing the response. NOTE: this is a
596     *          complete SAML response XML string with <code>ResponseID</code>,
597     *          <code>MajorVersion</code>, etc.
598     */
599    public String toString() {
600        return this.toString(true, true);
601    }
602
603    /**
604     * Creates a String representation of the
605     * <code>&lt;samlp:Response&gt;</code> element.
606     *
607     * @param includeNS Determines whether or not the namespace qualifier
608     *        is prepended to the Element when converted
609     * @param declareNS Determines whether or not the namespace is declared
610     *        within the Element.
611     * @return A string containing the valid XML for this element
612     */   
613    public String toString(boolean includeNS, boolean declareNS) {
614        return toString(includeNS, declareNS, false);
615    }
616
617    /**
618     * Creates a String representation of the
619     * <code>&lt;samlp:Response&gt;</code> element.
620     *
621     * @param includeNS Determines whether or not the namespace qualifier
622     *        is prepended to the Element when converted
623     * @param declareNS Determines whether or not the namespace is declared
624     *        within the Element.
625     * @param includeHeader Determines whether the output include the XML
626     *        declaration header.
627     * @return A string containing the valid XML for this element
628     */   
629    public String toString(boolean includeNS,
630                        boolean declareNS,
631                        boolean includeHeader) {
632        if (signed && (xmlString != null)) {
633            return xmlString;
634        }
635
636        StringBuffer xml = new StringBuffer(300);
637        if (includeHeader) {
638            xml.append("<?xml version=\"1.0\" encoding=\"").
639                append(SAMLConstants.DEFAULT_ENCODING).append("\" ?>\n");
640        }
641        String prefix = "";
642        String uri = "";
643        if (includeNS) {
644            prefix = SAMLConstants.PROTOCOL_PREFIX;
645        }
646
647        if (declareNS) {
648            uri = SAMLConstants.PROTOCOL_NAMESPACE_STRING;
649        }
650
651        String instantString = DateUtils.toUTCDateFormat(issueInstant);
652
653        xml.append("<").append(prefix).append("Response").append(uri).
654            append(" ResponseID=\"").append(responseID).append("\"");
655        if (inResponseTo != null) {
656            xml.append(" InResponseTo=\"").append(inResponseTo).append("\"");
657        }
658        xml.append(" MajorVersion=\"").append(majorVersion).append("\"").
659            append(" MinorVersion=\"").append(minorVersion).append("\"").
660            append(" IssueInstant=\"").append(instantString).append("\"");
661        if (recipient != null) {
662            xml.append(" Recipient=\"").append(XMLUtils.escapeSpecialCharacters(recipient)).append("\"");
663        }
664        xml.append(">\n");
665
666        if (signed) {
667            if (signatureString != null) {
668                xml.append(signatureString);
669            } else if (signature != null) {
670                signatureString = XMLUtils.print(signature);
671                xml.append(signatureString);
672            }
673        }
674
675        xml.append(status.toString(includeNS, false));
676        if ((assertions != null) && (assertions != Collections.EMPTY_LIST)) {
677            Iterator j = assertions.iterator();
678            while (j.hasNext()) {
679                xml.append(((Assertion) j.next()).toString(true, true));
680            }
681        }
682
683        xml.append("</").append(prefix).append("Response>\n");
684        return xml.toString();
685    }
686}




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.