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: FSLogoutNotification.java,v 1.4 2008/06/25 05:46:44 qcheng Exp $
026 *
027 */
028
029package com.sun.identity.federation.message;
030
031import com.sun.identity.shared.xml.XMLUtils;
032import com.sun.identity.shared.encode.Base64;
033import com.sun.identity.shared.encode.URLEncDec;
034import com.sun.identity.shared.DateUtils;
035import com.sun.identity.saml.common.SAMLConstants;
036import com.sun.identity.saml.common.SAMLUtils;
037import com.sun.identity.saml.common.SAMLException;
038import com.sun.identity.saml.common.SAMLResponderException;
039import com.sun.identity.saml.protocol.AbstractRequest;
040import com.sun.identity.saml.assertion.NameIdentifier;
041import com.sun.identity.saml.xmlsig.XMLSignatureManager;
042
043import com.sun.identity.federation.message.common.FSMsgException;
044import com.sun.identity.federation.common.FSUtils;
045import com.sun.identity.federation.common.IFSConstants;
046
047import javax.servlet.http.HttpServletRequest;
048
049import java.text.ParseException;
050
051import java.util.ArrayList;
052import java.util.Collections;
053import java.util.Date;
054import java.util.Iterator;
055import java.util.List;
056
057import org.w3c.dom.Element;
058import org.w3c.dom.Node;
059import org.w3c.dom.NodeList;
060import org.w3c.dom.Document;
061
062/**
063 * This class contains methods to construct a <code>LogoutRequest</code>
064 * object.
065 *
066 * @supported.all.api
067 */
068
069public class FSLogoutNotification extends AbstractRequest {
070    private String providerId;
071    private NameIdentifier nameIdentifier;
072    protected String sessionIndex;
073    protected String xmlString = null;
074    protected String signatureString = null;
075    protected String id = null;
076    private String relayState = null;
077    protected Date notOnOrAfter = null;
078    
079    /**
080     * Default Constructor.
081     */
082    public FSLogoutNotification() {
083        setIssueInstant(new Date());
084    }
085    
086    /**
087     * Constructor creates <code>FSLogoutNotification</code> object
088     * from Document Element.
089     *
090     * @param root the Document Element object.
091     * @throws FSMsgException if there is an error creating this
092     *         object.
093     */
094    public FSLogoutNotification(Element root)  throws FSMsgException {
095        String tag = null;
096        if (root == null) {
097            FSUtils.debug.message("FSLogoutNotification(Element): null input.");
098            throw new FSMsgException("nullInput",null);
099        }
100        if (((tag = root.getLocalName()) == null) ||
101                (!tag.equals(IFSConstants.LOGOUT_REQUEST))) {
102            FSUtils.debug.message("FSLogoutNotification(Element): wrong input");
103            throw new FSMsgException("wrongInput",null);
104        }
105        
106        // get the IssueInstant Attribute
107        String instantString = root.getAttribute(IFSConstants.ISSUE_INSTANT);
108        if ((instantString == null) || (instantString.length() == 0)) {
109            FSUtils.debug.message(
110                    "LogoutRequest(Element): missing IssueInstant");
111            String[] args = { IFSConstants.ISSUE_INSTANT };
112            throw new FSMsgException("missingAttribute",args);
113        } else {
114            try {
115                issueInstant = DateUtils.stringToDate(instantString);
116            } catch (Exception e) {
117                if (FSUtils.debug.messageEnabled()) {
118                    FSUtils.debug.message("LogoutRequest(Element): could not "
119                            + "parse IssueInstant:" + e.getMessage());
120                }
121                throw new FSMsgException("wrongInput",null);
122            }
123        }
124        // get the NotOnOrAfter Attribute
125        String notAfter = root.getAttribute(IFSConstants.NOT_ON_OR_AFTER);
126        if (notAfter != null && notAfter.length() != 0) {
127            try {
128                notOnOrAfter = DateUtils.stringToDate(notAfter);
129            } catch (Exception ex) {
130                if(FSUtils.debug.messageEnabled()) {
131                    FSUtils.debug.message("LogoutRequest(Element): unable to" +
132                            "parse not on or after", ex);
133                }
134            }
135        }
136        int length = 0;
137        id = root.getAttribute(IFSConstants.ID);
138        requestID = root.getAttribute(IFSConstants.REQUEST_ID);
139        parseMajorVersion(root.getAttribute(IFSConstants.MAJOR_VERSION));
140        parseMinorVersion(root.getAttribute(IFSConstants.MINOR_VERSION));
141        NodeList contentnl = root.getChildNodes();
142        Node child;
143        String nodeName;
144        length = contentnl.getLength();
145        for (int i = 0; i < length; i++) {
146            child = contentnl.item(i);
147            if ((nodeName = child.getLocalName()) != null) {
148                if (nodeName.equals(IFSConstants.RESPONDWITH)) {
149                    if (respondWiths == Collections.EMPTY_LIST) {
150                        respondWiths = new ArrayList();
151                    }
152                    respondWiths.add(
153                            XMLUtils.getElementValue((Element) child));
154                } else if (nodeName.equals(IFSConstants.SIGNATURE)) {
155                } else if (nodeName.equals(IFSConstants.PROVIDER_ID)) {
156                    if (providerId != null) {
157                        if (FSUtils.debug.messageEnabled()) {
158                            FSUtils.debug.message("FSLogoutNotification "
159                                    + "(Element): should contain only " 
160                                    + "one ProviderID.");
161                        }
162                        throw new FSMsgException("wrongInput",null);
163                    }
164                    providerId = XMLUtils.getElementValue((Element) child);
165                } else if (nodeName.equals(IFSConstants.RELAY_STATE)) {
166                    if (relayState != null) {
167                        if (FSUtils.debug.messageEnabled()) {
168                            FSUtils.debug.message("FSLogoutNotification "
169                                    + "(Element): should contain only one "
170                                    + "relayState.");
171                        }
172                        throw new FSMsgException("wrongInput",null);
173                    }
174                    relayState = XMLUtils.getElementValue((Element) child);
175                } else if (nodeName.equals(IFSConstants.NAME_IDENTIFIER)) {
176                    try {
177                        this.nameIdentifier =
178                                new NameIdentifier((Element) child);
179                    } catch(SAMLException ex){
180                        if (FSUtils.debug.messageEnabled()) {
181                            FSUtils.debug.message("FSLogoutNotification "
182                                    + "(Element): SAMLException while "
183                                    + "nconstructing ameidentifier");
184                        }
185                        throw new FSMsgException("nameIdentifierCreateError",
186                                null,ex);
187                    }
188                } else if (nodeName.equals(IFSConstants.SESSION_INDEX)) {
189                    if (sessionIndex != null) {
190                        if (FSUtils.debug.messageEnabled()) {
191                            FSUtils.debug.message(
192                                    "FSLogoutNotification(Element): "
193                                    + "should contain only one SessionIndex.");
194                        }
195                        throw new FSMsgException("wrongInput",null);
196                    }
197                    sessionIndex = XMLUtils.getElementValue((Element) child);
198                } else {
199                    if (FSUtils.debug.messageEnabled()) {
200                        FSUtils.debug.message("FSLogoutNotification(Element): "
201                                + "invalid node" + nodeName);
202                    }
203                    throw new FSMsgException("wrongInput",null);
204                }
205            }
206        }
207        
208        List signs = XMLUtils.getElementsByTagNameNS1(root,
209                SAMLConstants.XMLSIG_NAMESPACE_URI,
210                SAMLConstants.XMLSIG_ELEMENT_NAME);
211        int signsSize = signs.size();
212        if (signsSize == 1) {
213            Element elem = (Element)signs.get(0);
214            setSignature(elem);
215            xmlString = XMLUtils.print(root);
216            signed = true;
217        } else if (signsSize != 0) {
218            if (FSUtils.debug.messageEnabled()) {
219                FSUtils.debug.message(
220                        "FSLogoutNotification(Element): included more than"
221                        + " one Signature element.");
222            }
223            throw new FSMsgException("moreElement",null);
224        }
225        //end check for signature
226    }
227    
228    /**
229     * Consturctor creates <code>FSLogoutNotification</code> object.
230     *
231     * @param requestId the <code>RequestId</code> attribute.
232     * @param providerID the <code>ProviderID</code> attribute.
233     * @param nameId the <code>NameIdentifier</code> object.
234     * @param relayState the <code>RelayState</code> attribute.
235     * @throws FSMsgException if there is an error creating
236     *         this object.
237     */
238    public FSLogoutNotification(String requestId,String providerID,
239            NameIdentifier nameId, String relayState)
240            throws FSMsgException {
241        setIssueInstant(new Date());
242        if ((requestId != null) && (requestId.length() != 0)) {
243            requestID = requestId;
244        } else {
245            requestID = SAMLUtils.generateID();
246            if (requestID == null) {
247                FSUtils.debug.error(
248                        "FSLogoutNotification: couldn't generate RequestID.");
249                throw new FSMsgException("errorGenerateID",null);
250            }
251        }
252        this.relayState = relayState;
253        this.providerId = providerID;
254        this.nameIdentifier = nameId;
255    }
256    
257    /**
258     * Returns the value of <code>id</code> attribute.
259     *
260     * @return the value of <code>id</code> attribute.
261     * @see #setID(String)
262     */
263    public String getID(){
264        return id;
265    }
266    /**
267     * Sets the value of <code>id</code> attribute.
268     *
269     * @param id the value of <code>id</code> attribute.
270     * @see #getID()
271     */
272    public void setID(String id){
273        this.id = id;
274    }
275    
276    /**
277     * Sets the value of <code>RelayState</code> attribute.
278     *
279     * @param relayState the value of <code>RelayState</code> attribute.
280     */
281    public void setRelayState(String relayState) {
282        this.relayState = relayState;
283    }
284    
285    /**
286     * Returns the value of <code>RelayState</code> attribute.
287     *
288     * @return the value of <code>RelayState</code> attribute.
289     */
290    public String getRelayState() {
291        return this.relayState;
292    }
293    
294    /**
295     * Returns a signed <code>XML</code> string.
296     *
297     * @return a signed <code>XML</code> string.
298     */
299    public String getSignatureString(){
300        return signatureString;
301    }
302    
303    /**
304     * Returns the value of <code>MinorVersion</code> attribute.
305     *
306     * @return the value of <code>MinorVersion</code> attribute.
307     * @see #setMinorVersion(int)
308     */
309    public int getMinorVersion() {
310        return minorVersion;
311    }
312    
313    /**
314     * Sets the value of <code>MinorVersion</code> attribute.
315     *
316     * @param version the value of <code>MinorVersion</code> attribute.
317     * @see #getMinorVersion()
318     */
319    public void setMinorVersion(int version) {
320        minorVersion = version;
321    }
322    
323    /**
324     * Returns the string representation of this object.
325     *
326     * @param includeNS  determines whether or not the namespace qualifier
327     *        is prepended to the Element when converted
328     * @param declareNS : Determines whether or not the namespace is declared
329     *        within the Element.
330     * @return a string containing the valid <code>XML</code> for this element
331     * @throws FSMsgException if there is an error creating
332     *         <code>XML</code> string from this object.
333     */
334    
335    public String toXMLString(boolean includeNS, boolean declareNS)
336    throws FSMsgException {
337        return toXMLString(includeNS, declareNS, false);
338    }
339    
340    /**
341     * Returns the string representation of this object.
342     *
343     * @param includeNS determines whether or not the namespace qualifier
344     *        is prepended to the Element when converted
345     * @param declareNS Determines whether or not the namespace is declared
346     *        within the Element.
347     * @param includeHeader Determines whether the output include the xml
348     *        declaration header.
349     * @return a string containing the valid <code>XML</code> for this element
350     * @throws FSMsgException if there is an error creating
351     *         <code>XML</code> string from this object.
352     */
353    public String toXMLString(boolean includeNS,boolean declareNS,
354            boolean includeHeader) throws FSMsgException {
355        if((providerId == null) || (providerId.length() == 0)){
356            FSUtils.debug.error("FSLogoutNotification.toXMLString: "
357                    + "providerId is null in the request with requestId:"
358                    + requestID);
359            String[] args = { requestID };
360            throw new FSMsgException("nullProviderIdWRequestId" , args);
361        }
362        if ((requestID == null) || (requestID.length() == 0)){
363            requestID = SAMLUtils.generateID();
364            if (requestID == null) {
365                FSUtils.debug.error("FSLogoutNotification.toXMLString: "
366                        + "couldn't generate RequestID.");
367                throw new FSMsgException("errorGenerateID",null);
368            }
369        }
370        StringBuffer xml = new StringBuffer(300);
371        if (includeHeader) {
372            xml.append(IFSConstants.XML_PREFIX)
373            .append(IFSConstants.DEFAULT_ENCODING)
374            .append(IFSConstants.QUOTE)
375            .append(IFSConstants.SPACE)
376            .append(IFSConstants.QUESTION_MARK)
377            .append(IFSConstants.RIGHT_ANGLE)
378            .append(IFSConstants.NL);
379        }
380        String prefix = "";
381        String uri = "";
382        String uriSAML = "";
383        if (includeNS) {
384            prefix = IFSConstants.LIB_PREFIX;
385        }
386        if (declareNS) {
387            if(minorVersion == IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
388                uri = IFSConstants.LIB_12_NAMESPACE_STRING;
389            } else {
390                uri = IFSConstants.LIB_NAMESPACE_STRING;
391            }
392            uriSAML = IFSConstants.assertionDeclareStr;
393        }
394        
395        String instantString = DateUtils.toUTCDateFormat(issueInstant);
396        if (notOnOrAfter == null) {
397            notOnOrAfter = new Date(issueInstant.getTime() +
398                    IFSConstants.ASSERTION_TIMEOUT_ALLOWED_DIFFERENCE);
399        }
400        String notAfter = DateUtils.toUTCDateFormat(notOnOrAfter);
401        
402        if (requestID != null){
403            xml.append(IFSConstants.LEFT_ANGLE)
404            .append(prefix)
405            .append(IFSConstants.LOGOUT_REQUEST)
406            .append(uri)
407            .append(uriSAML);
408            
409            if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION &&
410                    id != null && !(id.length() == 0)) {
411                xml.append(IFSConstants.SPACE)
412                .append(IFSConstants.ID)
413                .append(IFSConstants.EQUAL_TO)
414                .append(IFSConstants.QUOTE)
415                .append(id)
416                .append(IFSConstants.QUOTE)
417                .append(IFSConstants.SPACE);
418            }
419            xml.append(IFSConstants.SPACE)
420            .append(IFSConstants.REQUEST_ID)
421            .append(IFSConstants.EQUAL_TO)
422            .append(IFSConstants.QUOTE)
423            .append(requestID)
424            .append(IFSConstants.QUOTE)
425            .append(IFSConstants.SPACE)
426            .append(IFSConstants.SPACE)
427            .append(IFSConstants.MAJOR_VERSION)
428            .append(IFSConstants.EQUAL_TO)
429            .append(IFSConstants.QUOTE)
430            .append(majorVersion)
431            .append(IFSConstants.QUOTE)
432            .append(IFSConstants.SPACE)
433            .append(IFSConstants.SPACE)
434            .append(IFSConstants.MINOR_VERSION)
435            .append(IFSConstants.EQUAL_TO)
436            .append(IFSConstants.QUOTE)
437            .append(minorVersion)
438            .append(IFSConstants.QUOTE)
439            .append(IFSConstants.SPACE)
440            .append(IFSConstants.SPACE)
441            .append(IFSConstants.ISSUE_INSTANT)
442            .append(IFSConstants.EQUAL_TO)
443            .append(IFSConstants.QUOTE)
444            .append(instantString)
445            .append(IFSConstants.QUOTE);
446            
447            if (minorVersion == IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
448                xml.append(IFSConstants.SPACE)
449                .append(IFSConstants.NOT_ON_OR_AFTER)
450                .append(IFSConstants.EQUAL_TO)
451                .append(IFSConstants.QUOTE)
452                .append(notAfter)
453                .append(IFSConstants.QUOTE);
454            }
455            xml.append(IFSConstants.RIGHT_ANGLE);
456            
457            if((respondWiths != null) &&
458                    (respondWiths != Collections.EMPTY_LIST)) {
459                Iterator i = respondWiths.iterator();
460                while (i.hasNext()) {
461                    xml.append(IFSConstants.LEFT_ANGLE)
462                    .append(prefix)
463                    .append(IFSConstants.RESPONDWITH)
464                    .append(IFSConstants.RIGHT_ANGLE)
465                    .append((String) i.next())
466                    .append(IFSConstants.START_END_ELEMENT)
467                    .append(prefix)
468                    .append(IFSConstants.RESPONDWITH)
469                    .append(IFSConstants.RIGHT_ANGLE);
470                }
471            }
472            
473            if (signed) {
474                if (signatureString != null) {
475                    xml.append(signatureString);
476                } else if (signature != null) {
477                    signatureString = XMLUtils.print(signature);
478                    xml.append(signatureString);
479                }
480            }
481            
482            xml.append(IFSConstants.LEFT_ANGLE)
483            .append(prefix)
484            .append(IFSConstants.PROVIDER_ID)
485            .append(uri)
486            .append(IFSConstants.RIGHT_ANGLE)
487            .append(providerId)
488            .append(IFSConstants.START_END_ELEMENT)
489            .append(prefix)
490            .append(IFSConstants.PROVIDER_ID)
491            .append(IFSConstants.RIGHT_ANGLE);
492            
493            if (nameIdentifier != null) {
494                xml.append(nameIdentifier.toString());
495            }
496            
497            if ((sessionIndex != null) && sessionIndex.length() != 0){
498                xml.append("<").append(prefix).append("SessionIndex").
499                        append(uri).
500                        append(">").append(sessionIndex).append("</").
501                        append(prefix).append("SessionIndex").append(">");
502            }
503            
504            if (relayState != null && relayState.length() != 0) {
505                xml.append(IFSConstants.LEFT_ANGLE)
506                .append(prefix)
507                .append(IFSConstants.RELAY_STATE)
508                .append(uri)
509                .append(IFSConstants.RIGHT_ANGLE)
510                .append(relayState)
511                .append(IFSConstants.START_END_ELEMENT)
512                .append(prefix)
513                .append(IFSConstants.RELAY_STATE)
514                .append(IFSConstants.RIGHT_ANGLE);
515            }
516            xml.append(IFSConstants.START_END_ELEMENT)
517               .append(prefix)
518               .append(IFSConstants.LOGOUT_REQUEST)
519               .append(IFSConstants.RIGHT_ANGLE);
520        } else {
521            if (FSUtils.debug.messageEnabled()) {
522                FSUtils.debug.message("FSLogoutNotification.toString: "
523                        + "requestID is null ");
524            }
525            throw new FSMsgException("nullRequestID",null);
526        }
527        return xml.toString();
528    }
529    
530    /**
531     * Returns the string representation of this object.
532     *
533     * @return a string containing the valid <code>XML</code> for this element
534     * @throws FSMsgException if there is an error creating
535     *         <code>XML</code> string from this object.
536     */
537    public String toXMLString() throws FSMsgException {
538        return toXMLString(true, true);
539    }
540    
541    /**
542     * Constructor create <code>FSLogoutNotification</code> from a
543     * <code>XML</code> string.
544     *
545     * @param xml the <code>XML</code> string.
546     * @throws FSMsgException if there is an error creating
547     *         this object.
548     */
549    public static FSLogoutNotification parseXML(String xml)
550    throws FSMsgException {
551        Document doc = XMLUtils.toDOMDocument(xml, FSUtils.debug);
552        if (doc == null) {
553            if (FSUtils.debug.messageEnabled()) {
554                FSUtils.debug.message("FSLogoutNotification.parseXML:Error "
555                        + "while parsing input xml string");
556            }
557            throw new FSMsgException("parseError",null);
558        }
559        Element root = doc.getDocumentElement();
560        return new FSLogoutNotification(root);
561    }
562    
563    /**
564     * Returns value of <code>ProviderID</code> attribute.
565     *
566     * @return value of <code>ProviderID</code> attribute.
567     * @see #setProviderId(String)
568     */
569    public String getProviderId() {
570        return providerId;
571    }
572    
573    /**
574     * Sets value of <code>ProviderID</code> attribute.
575     *
576     * @param providerID value of <code>ProviderID</code> attribute.
577     * @see #getProviderId()
578     */
579    public void setProviderId(String providerID) {
580        this.providerId = providerID;
581    }
582    
583    /**
584     * Returns value of <code>SessionIndex</code> attribute.
585     *
586     * @return value of <code>SessionIndex</code> attribute.
587     * @see #setSessionIndex(String)
588     */
589    public String getSessionIndex() {
590        return sessionIndex;
591    }
592    
593    /**
594     * Sets value of <code>SessionIndex</code> attribute.
595     *
596     * @param sessionIndex value of <code>SessionIndex</code> attribute.
597     * @see #getSessionIndex
598     */
599    public void setSessionIndex(String sessionIndex) {
600        this.sessionIndex = sessionIndex;
601    }
602    
603    /**
604     * Returns the <code>NameIdentifier</code> object.
605     *
606     * @return the <code>NameIdentifier</code> object.
607     * @see #setNameIdentifier(NameIdentifier)
608     */
609    public NameIdentifier getNameIdentifier() {
610        return nameIdentifier;
611    }
612    
613    /**
614     * Sets the <code>NameIdentifier</code> object.
615     *
616     * @param nameId the <code>NameIdentifier</code> object.
617     * @see #getNameIdentifier
618     */
619    public void setNameIdentifier(NameIdentifier nameId) {
620        this.nameIdentifier = nameId;
621    }
622    
623    /**
624     * Returns an URL Encoded String.
625     *
626     * @return a url encoded query string.
627     * @throws FSMsgException if there is an error.
628     */
629    public String toURLEncodedQueryString() throws FSMsgException {
630        if((providerId == null) || (providerId.length() == 0)){
631            FSUtils.debug.error("FSLogoutNotification.toURLEncodedQueryString: "
632                    + "providerId is null in the request with requestId:"
633                    + requestID);
634            String[] args = { requestID };
635            throw new FSMsgException("nullProviderIdWRequestId",args);
636        }
637        if ((requestID == null) || (requestID.length() == 0)){
638            requestID = SAMLUtils.generateID();
639            if (requestID == null) {
640                FSUtils.debug.error(
641                        "FSLogoutNotification.toURLEncodedQueryString: "
642                        + "couldn't generate RequestID.");
643                throw new FSMsgException("errorGenerateID",null);
644            }
645        }
646        StringBuffer urlEncodedAuthnReq = new StringBuffer(300);
647        urlEncodedAuthnReq.append(IFSConstants.REQUEST_ID)
648        .append(IFSConstants.EQUAL_TO)
649        .append(URLEncDec.encode(requestID))
650        .append(IFSConstants.AMPERSAND)
651        .append(IFSConstants.MAJOR_VERSION)
652        .append(IFSConstants.EQUAL_TO)
653        .append(majorVersion)
654        .append(IFSConstants.AMPERSAND)
655        .append(IFSConstants.MINOR_VERSION)
656        .append(IFSConstants.EQUAL_TO)
657        .append(minorVersion)
658        .append(IFSConstants.AMPERSAND);
659        
660        if(issueInstant != null){
661            urlEncodedAuthnReq.append(IFSConstants.ISSUE_INSTANT)
662            .append(IFSConstants.EQUAL_TO)
663            .append(URLEncDec.encode(
664                    DateUtils.toUTCDateFormat(issueInstant)))
665                    .append(IFSConstants.AMPERSAND);
666            
667            if(minorVersion == IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
668                notOnOrAfter = new Date(issueInstant.getTime() +
669                        IFSConstants.ASSERTION_TIMEOUT_ALLOWED_DIFFERENCE);
670                urlEncodedAuthnReq.append(IFSConstants.NOT_ON_OR_AFTER)
671                .append(IFSConstants.EQUAL_TO)
672                .append(URLEncDec.encode(
673                        DateUtils.toUTCDateFormat(notOnOrAfter)))
674                        .append(IFSConstants.AMPERSAND);
675            }
676        } else {
677            FSUtils.debug.error("FSLogoutNotification."
678                    + "toURLEncodedQueryString: issueInstant missing");
679            String[] args = { IFSConstants.ISSUE_INSTANT };
680            throw new FSMsgException("missingAttribute",args);
681        }
682        if (providerId != null && providerId.length() != 0) {
683            urlEncodedAuthnReq.append(IFSConstants.PROVIDER_ID)
684            .append(IFSConstants.EQUAL_TO)
685            .append(URLEncDec.encode(providerId))
686            .append(IFSConstants.AMPERSAND);
687        }
688        
689        if (sessionIndex != null && sessionIndex.length() != 0) {
690            urlEncodedAuthnReq.append(IFSConstants.SESSION_INDEX)
691            .append(IFSConstants.EQUAL_TO)
692            .append(URLEncDec.encode(sessionIndex))
693            .append(IFSConstants.AMPERSAND);
694        }
695        
696        if (relayState != null && relayState.length() != 0) {
697            urlEncodedAuthnReq.append(IFSConstants.RELAY_STATE)
698            .append(IFSConstants.EQUAL_TO)
699            .append(URLEncDec.encode(relayState))
700            .append(IFSConstants.AMPERSAND);
701        }
702        
703        if (nameIdentifier != null) {
704            if (nameIdentifier.getName() != null &&
705                    nameIdentifier.getName().length() != 0) {
706                urlEncodedAuthnReq.append(IFSConstants.NAME)
707                .append(IFSConstants.EQUAL_TO)
708                .append(URLEncDec.encode(
709                        nameIdentifier.getName()))
710                        .append(IFSConstants.AMPERSAND)
711                        .append(IFSConstants.NAME_IDENTIFIER)
712                        .append(IFSConstants.EQUAL_TO)
713                        .append(URLEncDec.encode(
714                        nameIdentifier.getName()))
715                        .append(IFSConstants.AMPERSAND);
716            }
717            if (nameIdentifier.getNameQualifier() != null &&
718                    nameIdentifier.getNameQualifier().length() != 0) {
719                urlEncodedAuthnReq.append(IFSConstants.NAME_QUALIFIER)
720                .append(IFSConstants.EQUAL_TO)
721                .append(URLEncDec.encode(
722                        nameIdentifier.getNameQualifier()))
723                        .append(IFSConstants.AMPERSAND);
724            }
725            if (nameIdentifier.getFormat() != null &&
726                    nameIdentifier.getFormat().length() != 0) {
727                urlEncodedAuthnReq.append(IFSConstants.NAME_FORMAT)
728                .append(IFSConstants.EQUAL_TO)
729                .append(URLEncDec.encode(
730                        nameIdentifier.getFormat()))
731                        .append(IFSConstants.AMPERSAND);
732            }
733        }
734        return urlEncodedAuthnReq.toString();
735    }
736    
737    /**
738     * Returns a Base64 Encoded String.
739     *
740     * @return a Base64 Encoded String.
741     * @throws FSMsgException if there is an error encoding
742     *         the string.
743     */
744    public String toBASE64EncodedString() throws FSMsgException {
745        if((providerId == null) || (providerId.length() == 0)){
746            FSUtils.debug.error("FSLogoutNotification.toURLEncodedQueryString: "
747                    + "providerId is null in the request with requestId:"
748                    + requestID);
749            String[] args = { requestID };
750            throw new FSMsgException("nullProviderIdWRequestId",args);
751        }
752        if ((requestID == null) || (requestID.length() == 0)){
753            requestID = SAMLUtils.generateID();
754            if (requestID == null) {
755                FSUtils.debug.error(
756                        "FSLogoutNotification.toURLEncodedQueryString: "
757                        + "couldn't generate RequestID.");
758                throw new FSMsgException("errorGenerateID",null);
759            }
760        }
761        return Base64.encode(this.toXMLString().getBytes());
762    }
763    
764    /**
765     * Returns <code>FSLogoutNotification</code> object. The
766     * object is created by parsing the <code>HttpServletRequest</code>
767     * object.
768     *
769     * @param request the <code>HttpServletRequest</code> object.
770     * @return <code>FSLogoutNotification</code> object.
771     * @throws FSMsgException if there is an error
772     *         creating <code>FSAuthnRequest</code> object.
773     */
774    
775    public static FSLogoutNotification parseURLEncodedRequest(
776            HttpServletRequest request) throws FSMsgException {
777        try {
778            FSLogoutNotification retLogoutNotification =
779                    new FSLogoutNotification();
780            String requestID = request.getParameter("RequestID");
781            if(requestID != null) {
782                retLogoutNotification.requestID = requestID;
783            } else {
784                String[] args = { IFSConstants.REQUEST_ID };
785                throw new FSMsgException("missingAttribute",args);
786            }
787            try{
788                retLogoutNotification.majorVersion =
789                        Integer.parseInt(request.getParameter(
790                        IFSConstants.MAJOR_VERSION));
791                FSUtils.debug.message("Majorversion : "
792                        + retLogoutNotification.majorVersion);
793                retLogoutNotification.minorVersion =
794                        Integer.parseInt(request.getParameter(
795                        IFSConstants.MINOR_VERSION));
796                FSUtils.debug.message("Minorversion : "
797                        + retLogoutNotification.minorVersion);
798            } catch (NumberFormatException ex) {
799                FSUtils.debug.message("FSLogoutNotification. "
800                        + "parseURLEncodedRequest:Major/Minor version problem");
801                throw new FSMsgException("invalidNumber",null);
802            }
803            String instantString =
804                    request.getParameter(IFSConstants.ISSUE_INSTANT);
805            if (instantString == null || instantString.length() == 0) {
806                String[] args = { IFSConstants.ISSUE_INSTANT };
807                throw new FSMsgException("missingAttribute",args);
808            }
809            try{
810                retLogoutNotification.issueInstant =
811                        DateUtils.stringToDate(instantString);
812            } catch (ParseException e){
813                throw new FSMsgException("parseError",null);
814            }
815            String notAfter =
816                    request.getParameter(IFSConstants.NOT_ON_OR_AFTER);
817            if (notAfter != null && notAfter.length() != 0) {
818                try {
819                    retLogoutNotification.notOnOrAfter =
820                            DateUtils.stringToDate(notAfter);
821                } catch (ParseException pe) {
822                    FSUtils.debug.message("FSLogoutNotification.parseURLEncoded"
823                            +  "Request: parsing exception", pe);
824                }
825            }
826            
827            String providerId = request.getParameter(IFSConstants.PROVIDER_ID);
828            if (providerId != null) {
829                retLogoutNotification.providerId = providerId;
830            } else {
831                throw new FSMsgException("missingElement",null);
832            }
833            
834            String sessionIndex =
835                    request.getParameter(IFSConstants.SESSION_INDEX);
836            if (sessionIndex != null) {
837                retLogoutNotification.sessionIndex = sessionIndex;
838            }
839            
840            String relayState = request.getParameter(IFSConstants.RELAY_STATE);
841            if (relayState != null) {
842                retLogoutNotification.relayState = relayState;
843            }
844            
845            String nameFormat = request.getParameter(IFSConstants.NAME_FORMAT);
846            String nameQualifier =
847                    request.getParameter(IFSConstants.NAME_QUALIFIER);
848            String name = request.getParameter(IFSConstants.NAME);
849            
850            if (name == null) {
851                name = request.getParameter(IFSConstants.NAME_IDENTIFIER);
852            }
853            
854            if (name == null) {
855                throw new FSMsgException("missingElement",null);
856            }
857            
858            retLogoutNotification.nameIdentifier =
859                    new NameIdentifier(name, nameQualifier, nameFormat);
860            
861            FSUtils.debug.message("Returning Logout Object");
862            return retLogoutNotification;
863        } catch(Exception e) {
864            throw new FSMsgException("parseError",null);
865        }
866    }
867    
868    /**
869     * Sets the <code>MajorVersion</code> by parsing the version string.
870     *
871     * @param majorVer a String representing the <code>MajorVersion</code> to
872     *        be set.
873     * @throws FSMsgException when the version mismatchs.
874     */
875    private void parseMajorVersion(String majorVer) throws FSMsgException {
876        try {
877            majorVersion = Integer.parseInt(majorVer);
878        } catch (NumberFormatException e) {
879            if (FSUtils.debug.messageEnabled()) {
880                FSUtils.debug.message("FSLogoutNotification(Element): invalid "
881                        + "MajorVersion", e);
882            }
883            throw new FSMsgException("wrongInput",null);
884        }
885        
886        if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) {
887            if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) {
888                if (FSUtils.debug.messageEnabled()) {
889                    FSUtils.debug.message("FSLogoutNotification(Element): "
890                            + "MajorVersion of the LogoutRequest is too high.");
891                }
892                throw new FSMsgException("requestVersionTooHigh",null);
893            } else {
894                if (FSUtils.debug.messageEnabled()) {
895                    FSUtils.debug.message("FSLogoutNotification(Element): "
896                            + "MajorVersion of the LogoutRequest is too low.");
897                }
898                throw new FSMsgException("requestVersionTooLow",null);
899            }
900        }
901    }
902    
903    /**
904     * Sets the <code>MinorVersion</code> by parsing the version string.
905     *
906     * @param minorVer a String representing the <code>MinorVersion</code> to
907     *        be set.
908     * @throws FSMsgException when the version mismatchs.
909     */
910    
911    private void parseMinorVersion(String minorVer) throws FSMsgException {
912        try {
913            minorVersion = Integer.parseInt(minorVer);
914        } catch (NumberFormatException e) {
915            if (FSUtils.debug.messageEnabled()) {
916                FSUtils.debug.message("FSLogoutNotification(Element): invalid "
917                        + "MinorVersion", e);
918            }
919            throw new FSMsgException("wrongInput",null);
920        }
921        
922        if (minorVersion > IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
923            FSUtils.debug.error("FSLogoutNotification(Element): "
924                    + "MinorVersion of the LogoutRequest is too high.");
925            throw new FSMsgException("requestVersionTooHigh",null);
926        } else if (minorVersion < IFSConstants.FF_11_PROTOCOL_MINOR_VERSION) {
927            FSUtils.debug.error("FSLogoutNotification(Element): "
928                    + "MinorVersion of the LogoutRequest is too low.");
929            throw new FSMsgException("requestVersionTooLow",null);
930        }
931    }
932    
933    
934    /**
935     * Unsupported operation.
936     */
937    public void signXML() throws SAMLException {
938        throw new SAMLException(FSUtils.BUNDLE_NAME,
939                                "unsupportedOperation",null);
940    }
941    
942    
943    /**
944     * Signs the <code>FSLogoutNotification</code> object.
945     *
946     * @param certAlias the Certificate Alias
947     * @throws SAMLException if
948     *         <code>FSFederationTerminationNotification</code>
949     *         cannot be signed.
950     */
951    public void signXML(String certAlias) throws SAMLException {
952        FSUtils.debug.message("FSLogoutNotification.signXML: Called");
953        if (signed) {
954            if (FSUtils.debug.messageEnabled()) {
955                FSUtils.debug.message("FSLogoutNotification.signXML: "
956                        + "the assertion is already signed.");
957            }
958            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
959                    "alreadySigned",null);
960        }
961        if (certAlias == null || certAlias.length() == 0) {
962            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
963                    "cannotFindCertAlias",null);
964        }
965        try{
966            XMLSignatureManager manager = XMLSignatureManager.getInstance();
967            if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION) {
968                signatureString = manager.signXML(this.toXMLString(true, true),
969                        certAlias,null,IFSConstants.ID,
970                        this.id, false);
971            } else if (minorVersion ==
972                    IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) {
973                signatureString = manager.signXML(
974                        this.toXMLString(true, true),
975                        certAlias, null,
976                        IFSConstants.REQUEST_ID,
977                        this.getRequestID(), false);
978            } else {
979                if (FSUtils.debug.messageEnabled()) {
980                    FSUtils.debug.message("invalid minor version.");
981                }
982            }
983            
984            signature =
985                    XMLUtils.toDOMDocument(signatureString, FSUtils.debug)
986                    .getDocumentElement();
987            
988            signed = true;
989            xmlString = this.toXMLString(true, true);
990        } catch(Exception e){
991            throw new SAMLResponderException(FSUtils.BUNDLE_NAME,
992                    "signFailed",null);
993        }
994    }
995    
996    /**
997     * Sets the <code>Element</code> signature.
998     *
999     * @param elem the <code>Element</code> object
1000     * @return true if signature is set otherwise false
1001     */
1002    
1003    public boolean setSignature(Element elem) {
1004        signatureString = XMLUtils.print(elem);
1005        return super.setSignature(elem);
1006    }
1007}