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: FSResponse.java,v 1.2 2008/06/25 05:46:45 qcheng Exp $
026 *
027 */
028
029package com.sun.identity.federation.message;
030
031import java.text.ParseException;
032import java.util.List;
033import java.util.Collections;
034import java.util.ArrayList;
035import java.util.Iterator;
036
037import java.io.ByteArrayInputStream;
038import java.io.IOException;
039
040import org.w3c.dom.Element;
041import org.w3c.dom.Node;
042import org.w3c.dom.NodeList;
043import org.w3c.dom.Document;
044
045import com.sun.identity.shared.encode.Base64;
046import com.sun.identity.shared.xml.XMLUtils;
047import com.sun.identity.shared.DateUtils;
048import com.sun.identity.saml.common.SAMLConstants;
049import com.sun.identity.saml.common.SAMLException;
050import com.sun.identity.saml.common.SAMLResponderException;
051import com.sun.identity.saml.common.SAMLVersionMismatchException;
052import com.sun.identity.saml.xmlsig.XMLSignatureManager;
053import com.sun.identity.saml.protocol.Response;
054import com.sun.identity.saml.protocol.Status;
055
056import com.sun.identity.federation.message.common.FSMsgException;
057import com.sun.identity.federation.common.FSUtils;
058import com.sun.identity.federation.common.IFSConstants;
059
060/**
061 * This class contains methods for creating a Liberty <code>Response</code>.
062 *
063 * @supported.all.api
064 */
065public class FSResponse extends Response {
066    protected String id = null;
067    
068    /**
069     * Returns the value of <code>id</code> attribute.
070     *
071     * @return the value of <code>id</code> attribute.
072     * @see #setID(String)
073     */
074    public String getID() {
075        return id;
076    }
077    
078    /**
079     * Sets the value of <code>id</code> attribute.
080     *
081     * @param id the value of <code>id</code> attribute.
082     * @see #getID()
083     */
084    public void setID(String id) {
085        this.id = id;
086    }
087    
088    /**
089     * Returns the signed <code>XML</code> string.
090     *
091     * @return the signed <code>XML</code> string.
092     */
093    public String getSignatureString(){
094        return signatureString;
095    }
096    
097    /**
098     * Returns the <code>MinorVersion</code>.
099     *
100     * @return the <code>MinorVersion</code>.
101     * @see #setMinorVersion(int)
102     */
103    
104    public int getMinorVersion() {
105        return minorVersion;
106    }
107    
108    /**
109     * Sets the <code>MinorVersion</code>.
110     *
111     * @param version the <code>MinorVersion</code>.
112     * @see #getMinorVersion()
113     */
114    public void setMinorVersion(int version) {
115        minorVersion = version;
116    }
117    
118    /**
119     * Constructor creates <code>FSResponse</code> object.
120     *
121     * @param responseID value of <code>ResponseId</code> attribute.
122     * @param inResponseTo value of <code>inResponseTo</code> attribute.
123     * @param status the <code>Status</code> object.
124     * @param contents list containing response elements.
125     * @throws SAMLException it there is an error creating this object.
126     * @throws FSMsgException it there is an error creating this object.
127     */
128    public FSResponse(String responseID,
129            String inResponseTo,
130            Status status,
131            List contents) throws SAMLException, FSMsgException {
132        super( responseID, inResponseTo, status, contents);
133    }
134    
135    public static FSResponse parseResponseXML(
136            String xml
137            ) throws SAMLException, FSMsgException {
138        // parse the xml string
139        FSUtils.debug.message("FSResponse.parseResponseXML: Called");
140        Element root;
141        Document doc = XMLUtils.toDOMDocument(xml, FSUtils.debug);
142        if (doc == null) {
143            FSUtils.debug.error("FSResponse.parseXML:Error "
144                    + "while parsing input xml string");
145            throw new FSMsgException("parseError",null);
146        }
147        root = doc.getDocumentElement();
148        return new FSResponse(root);
149    }
150    
151    /**
152     * Constructor creates <code>FSResponse</code> object form
153     * a Document Element.
154     *
155     * @param root the Document Element object.
156     * @throws SAMLException if there is an error creating this object.
157     * @throws FSMsgException if there is an error creating this object.
158     */
159    public FSResponse(Element root) throws SAMLException, FSMsgException {
160        FSUtils.debug.message("FSResponse(Element): Called");
161        if (root == null) {
162            FSUtils.debug.message("FSResponse(Element): "
163                    + "Input paramenter (root) is null");
164            throw new FSMsgException("nullInput",null);
165        }
166        String tag = null;
167        if (((tag = root.getLocalName()) == null) ||
168                (!tag.equals("Response"))) {
169            FSUtils.debug.message("FSResponse(Element): "
170                    + "Root element name is not Response");
171            throw new FSMsgException("wrongInput",null);
172        }
173        id = root.getAttribute("id");
174        responseID = root.getAttribute("ResponseID");
175        if ((responseID == null) || (responseID.length() == 0)) {
176            if (FSUtils.debug.messageEnabled()) {
177                FSUtils.debug.message("FSResponse(Element): "
178                        + "Response doesn't have ResponseID attribute");
179            }
180            String[] args = { IFSConstants.RESPONSE_ID };
181            throw new FSMsgException("missingAttribute",args);
182        }
183        
184        inResponseTo = root.getAttribute("InResponseTo");
185        if (inResponseTo == null) {
186            if (FSUtils.debug.messageEnabled()) {
187                FSUtils.debug.message("FSResponse(Element): "
188                        + "Response doesn't have InResponseTo attribute");
189            }
190            String[] args = { IFSConstants.IN_RESPONSE_TO };
191            throw new FSMsgException("missingAttribute",args);
192        }
193        
194        // Attribute IssueInstant
195        String instantString = root.getAttribute("IssueInstant");
196        if ((instantString == null) || (instantString.length() == 0)) {
197            FSUtils.debug.message("FSResponse(Element): missing IssueInstant");
198            String[] args = { IFSConstants.ISSUE_INSTANT };
199            throw new FSMsgException("missingAttribute",args);
200        } else {
201            try {
202                issueInstant = DateUtils.stringToDate(instantString);
203            } catch (ParseException e) {
204                if (FSUtils.debug.messageEnabled()) {
205                    FSUtils.debug.message("FSResponse(Element): could not "
206                            + "parse IssueInstant:", e);
207                }
208                throw new FSMsgException("wrongInput", null);
209            }
210        }
211        parseMajorVersion(root.getAttribute("MajorVersion"));
212        parseMinorVersion(root.getAttribute("MinorVersion"));
213        setRecipient(root.getAttribute("Recipient"));
214        NodeList nl = root.getChildNodes();
215        Node child;
216        String childName;
217        int length = nl.getLength();
218        for (int i = 0; i < length; i++) {
219            child = nl.item(i);
220            if ((childName = child.getLocalName()) != null) {
221                if (childName.equals("Status")) {
222                    if (status != null) {
223                        if (FSUtils.debug.messageEnabled()) {
224                            FSUtils.debug.message(
225                                    "FSResponse(Element): included more"
226                                    + " than one <Status>");
227                        }
228                        throw new FSMsgException("moreElement",null);
229                    }
230                    status = new Status((Element) child);
231                } else if (childName.equals("Assertion")) {
232                    if (assertions == Collections.EMPTY_LIST) {
233                        assertions = new ArrayList();
234                    }
235                    assertions.add(new FSAssertion((Element) child));
236                }else {
237                    if (FSUtils.debug.messageEnabled()) {
238                        FSUtils.debug.message(
239                                "FSResponse(Element): included wrong "
240                                + "element: " + childName);
241                    }
242                    throw new FSMsgException("wrongInput",null);
243                }
244            } // end if childName != null
245        } // end for loop
246        
247        if (status == null) {
248            FSUtils.debug.message(
249                    "FSResponse(Element): missing element <Status>.");
250            throw new FSMsgException("missingElement",null);
251        }
252        
253        //check for signature
254        List signs = XMLUtils.getElementsByTagNameNS1(root,
255                SAMLConstants.XMLSIG_NAMESPACE_URI,
256                SAMLConstants.XMLSIG_ELEMENT_NAME);
257        int signsSize = signs.size();
258        if (signsSize == 1) {
259            Element elem = (Element)signs.get(0);
260            setSignature(elem);
261            xmlString = XMLUtils.print(root);
262            signed = true;
263        } else if (signsSize != 0) {
264            if (FSUtils.debug.messageEnabled()) {
265                FSUtils.debug.message("FSResponse(Element): included more than"
266                        + " one Signature element.");
267            }
268            throw new FSMsgException("moreElement",null);
269        }
270        //end check for signature
271    }
272    
273    /**
274     * Sets the <code>MajorVersion</code> by parsing the version string.
275     *
276     * @param majorVer a String representing the <code>MajorVersion</code> to
277     *        be set.
278     * @throws SAMLException on error.
279     * @throws FSMsgException if there is an error parsing the version string.
280     */
281    private void parseMajorVersion(String majorVer)
282    throws SAMLException, FSMsgException {
283        try {
284            majorVersion = Integer.parseInt(majorVer);
285        } catch (NumberFormatException e) {
286            if (FSUtils.debug.messageEnabled()) {
287                FSUtils.debug.message("FSResponse(Element): invalid "
288                        + "MajorVersion", e);
289            }
290            throw new FSMsgException("wrongInput",null);
291        }
292        
293        if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) {
294            if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) {
295                if (FSUtils.debug.messageEnabled()) {
296                    FSUtils.debug.message("FSResponse(Element):MajorVersion of"
297                            + " the Response is too high.");
298                }
299                throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME,
300                        "responseVersionTooHigh",null);
301            } else {
302                if (FSUtils.debug.messageEnabled()) {
303                    FSUtils.debug.message("FSResponse(Element):MajorVersion of"
304                            + " the Response is too low.");
305                }
306                throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME,
307                        "responseVersionTooLow",null);
308            }
309        }
310    }
311    
312    /**
313     * Sets the <code>MinorVersion</code> by parsing the version string.
314     *
315     * @param minorVer a String representing the <code>MinorVersion</code> to
316     *        be set.
317     * @throws SAMLException when the version mismatchs.
318     * @throws FSMsgException if there is an error
319     *          parsing the version string.
320     */
321    private void parseMinorVersion(String minorVer)
322    throws SAMLException, FSMsgException {
323        try {
324            minorVersion = Integer.parseInt(minorVer);
325        } catch (NumberFormatException e) {
326            if (FSUtils.debug.messageEnabled()) {
327                FSUtils.debug.message("FSResponse(Element): invalid "
328                        + "MinorVersion", e);
329            }
330            throw new FSMsgException("wrongInput",null);
331        }
332        
333        if (minorVersion > IFSConstants.FF_12_SAML_PROTOCOL_MINOR_VERSION) {
334            FSUtils.debug.error("FSResponse(Element):MinorVersion of"
335                    + " the Response is too high.");
336            throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME,
337                    "responseVersionTooHigh",null);
338        } else if (minorVersion <
339                IFSConstants.FF_11_SAML_PROTOCOL_MINOR_VERSION) {
340            FSUtils.debug.error("FSResponse(Element):MinorVersion of"
341                    + " the Response is too low.");
342            throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME,
343                    "responseVersionTooLow",null);
344        }
345    }
346    
347    /**
348     * Returns a String representation of the Logout Response.
349     *
350     * @return a string containing the valid XML for this element
351     * @throws FSMsgException if there is an error converting
352     *         this object to a string.
353     */
354    public String toXMLString() throws FSMsgException {
355        return this.toXMLString(true, true);
356    }
357    
358    /**
359     * Returns a String representation of the Logout Response.
360     *
361     * @param includeNS : Determines whether or not the namespace qualifier
362     *        is prepended to the Element when converted
363     * @param declareNS : Determines whether or not the namespace is declared
364     *        within the Element.
365     * @return a string containing the valid XML for this element
366     * @throws FSMsgException if there is an error converting
367     *         this object ot a string.
368     */
369    
370    public String toXMLString(boolean includeNS, boolean declareNS)
371    throws FSMsgException {
372        return toXMLString(includeNS, declareNS, false);
373    }
374    
375    public String toXMLString(boolean includeNS,boolean declareNS,
376            boolean includeHeader)  throws FSMsgException {
377        FSUtils.debug.message("FSResponse.toXMLString(3): Called");
378        StringBuffer xml = new StringBuffer(500);
379        if (includeHeader) {
380            xml.append("<?xml version=\"1.0\" encoding=\"").
381                    append(SAMLConstants.DEFAULT_ENCODING).append("\" ?>");
382        }
383        String prefixSAML=null;
384        String prefixLIB=null;
385        String prefixSAML_PROTOCOL = "";
386        String uriSAML_PROTOCOL = "";
387        String uriSAML = "";
388        String uriLIB = "";
389        String uriDS="";
390        String uriXSI="";
391        
392        if (includeNS) {
393            prefixLIB = IFSConstants.LIB_PREFIX;
394            prefixSAML = IFSConstants.ASSERTION_PREFIX;
395            prefixSAML_PROTOCOL = IFSConstants.PROTOCOL_PREFIX;
396        }
397        if (declareNS) {
398            if(minorVersion == IFSConstants.FF_12_SAML_PROTOCOL_MINOR_VERSION){
399                uriLIB = IFSConstants.LIB_12_NAMESPACE_STRING;
400            } else {
401                uriLIB = IFSConstants.LIB_NAMESPACE_STRING;
402            }
403            uriSAML = IFSConstants.assertionDeclareStr;
404            uriSAML_PROTOCOL = IFSConstants.PROTOCOL_NAMESPACE_STRING;
405            uriDS = IFSConstants.DSSAMLNameSpace;
406            uriXSI = IFSConstants.XSI_NAMESPACE_STRING;
407        }
408        
409        String instantString = DateUtils.toUTCDateFormat(issueInstant);
410        
411        if((responseID != null) && (inResponseTo != null)){
412            xml.append("<").append(prefixSAML_PROTOCOL).append("Response").
413                    append(uriLIB).
414                    append(uriSAML).append(uriSAML_PROTOCOL).append(" ").
415                    append(uriDS).
416                    append(" ").append(uriXSI).append(" ResponseID=\"").
417                    append(responseID).append("\" ");
418            if ((inResponseTo != null) && (inResponseTo.length() != 0)) {
419                xml.append(" InResponseTo=\"").append(inResponseTo).
420                        append("\" ");
421            }
422            if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION &&
423                    id != null && !(id.length() == 0)){
424                xml.append(" id=\"").append(id).append("\"");
425            }
426            xml.append(" MajorVersion=\"").
427                    append(majorVersion).append("\" ").
428                    append(" MinorVersion=\"").append(minorVersion).
429                    append("\" ").
430                    append(" IssueInstant=\"").append(instantString).
431                    append("\"");
432            if ((recipient != null) && (recipient.length() != 0)) {
433                xml.append(" Recipient=\"").append(recipient).append("\" ");
434            }
435            xml.append(">");
436        }
437        
438        if (signed) {
439            if (signatureString != null) {
440                xml.append(signatureString);
441            } else if (signature != null) {
442                signatureString = XMLUtils.print(signature);
443                xml.append(signatureString);
444            }
445        }
446        
447        if(status != null)
448            xml.append(status.toString(includeNS, false));
449        
450        if ((assertions != null) && (assertions != Collections.EMPTY_LIST)) {
451            Iterator j = assertions.iterator();
452            while (j.hasNext()) {
453                xml.append(((FSAssertion) j.next()).
454                        toXMLString(true,declareNS));
455            }
456        }
457        
458        xml.append("</").append(prefixSAML_PROTOCOL).append("Response>");
459        return xml.toString();
460    }
461    
462    /**
463     * Returns <code>FSResponse</code> object. The object
464     * is created by parsing an Base64 encoded response string.
465     *
466     * @param encodedRes the encoded response string
467     * @throws FSMsgException if there is an error creating
468     *            <code>FSResponse</code> object.
469     * @throws FSMsgException if there is an error creating
470     *            <code>FSResponse</code> object.
471     */
472    public static FSResponse parseBASE64EncodedString(
473            String encodedRes) throws FSMsgException, SAMLException {
474        FSUtils.debug.message("FSResponse.parseBASE64EncodedString:Called new");
475        if (encodedRes != null) {
476            String decodedAuthnRes = new String(Base64.decode(encodedRes));
477            if (FSUtils.debug.messageEnabled()) {
478                FSUtils.debug.message("FSResponse.parseBASE64EncodedString:"
479                        + "Decoded AuthnResponse message: "
480                        + decodedAuthnRes);
481            }
482            return parseResponseXML(decodedAuthnRes);
483        } else{
484            if (FSUtils.debug.messageEnabled()) {
485                FSUtils.debug.message("FSResponse.parseBASE64EncodedString:"
486                        + "null String passed in as argument.");
487            }
488            throw new FSMsgException("nullInput",null);
489        }
490    }
491    
492    /**
493     * Returns a Base64 Encoded String.
494     *
495     * @return a Base64 Encoded String.
496     * @throws FSMsgException if there is an error encoding the string.
497     */
498    public String toBASE64EncodedString() throws FSMsgException {
499        FSUtils.debug.message("FSResponse.toBASE64EncodedString: Called");
500        if ((responseID == null) || (responseID.length() == 0)){
501            responseID = FSUtils.generateID();
502            if (responseID == null) {
503                FSUtils.debug.error("FSResponse.toBASE64EncodedString: "
504                        + "couldn't generate ResponseID.");
505                throw new FSMsgException("errorGenerateID",null);
506            }
507        }
508        return Base64.encode(this.toXMLString().getBytes());
509    }
510    
511    /**
512     * Signs the Response.
513     *
514     * @param certAlias the Certificate Alias.
515     * @throws XMLSignatureException if <code>FSAuthnRequest</code>
516     *         cannot be signed.
517     */
518    public void signXML(String certAlias) throws SAMLException {
519        FSUtils.debug.message("FSResponse.signXML: Called");
520        if (signed) {
521            if (FSUtils.debug.messageEnabled()) {
522                FSUtils.debug.message("FSResponse.signXML: the assertion is "
523                        + "already signed.");
524            }
525            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
526                    "alreadySigned",null);
527        }
528        if (certAlias == null || certAlias.length() == 0) {
529            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
530                    "cannotFindCertAlias",null);
531        }
532        try {
533            XMLSignatureManager manager = XMLSignatureManager.getInstance();
534            if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION) {
535                signatureString = manager.signXML(
536                        this.toXMLString(true, true),
537                        certAlias, IFSConstants.DEF_SIG_ALGO,
538                        IFSConstants.ID,
539                        this.id, false);
540            } else if (minorVersion ==
541                    IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
542                signatureString = manager.signXML(
543                        this.toXMLString(true, true),
544                        certAlias, IFSConstants.DEF_SIG_ALGO,
545                        IFSConstants.RESPONSE_ID,
546                        this.getResponseID(), false);
547            } else {
548                if (FSUtils.debug.messageEnabled()) {
549                    FSUtils.debug.message("invalid minor version.");
550                }
551            }
552            
553            signature =
554                    XMLUtils.toDOMDocument(signatureString, FSUtils.debug)
555                    .getDocumentElement();
556            
557            signed = true;
558            xmlString = this.toXMLString(true, true);
559        } catch(Exception e){
560            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
561                    "signFailed",null);
562        }
563    }
564    
565    /**
566     * Unsupported operation.
567     */
568    public void signXML() throws SAMLException {
569        throw new SAMLException(FSUtils.BUNDLE_NAME,
570                "unsupportedOperation",null);
571    }
572}