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: SAMLUtils.java,v 1.16 2010/01/09 19:41:06 qcheng Exp $
026 *
027 * Portions Copyrighted 2012-2016 ForgeRock AS.
028 */
029
030package com.sun.identity.saml.common;
031
032import static org.forgerock.http.util.Uris.urlEncodeQueryParameterNameOrValue;
033import static org.forgerock.openam.utils.Time.*;
034
035import java.util.Collections;
036import java.util.Date;
037import java.util.Map;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Set;
041import java.util.HashSet;
042import java.util.Enumeration;
043import java.util.ArrayList;
044import java.util.HashMap;
045import java.util.StringTokenizer;
046
047import java.text.StringCharacterIterator;
048import java.text.CharacterIterator;
049import java.io.UnsupportedEncodingException;
050import java.io.PrintWriter;
051import java.io.IOException;
052import java.io.ByteArrayInputStream;
053
054import java.security.MessageDigest;
055
056import java.net.URL;
057import java.net.MalformedURLException;
058
059import org.w3c.dom.*;
060
061import com.sun.identity.common.PeriodicGroupRunnable;
062import com.sun.identity.common.ScheduleableGroupAction;
063import com.sun.identity.common.SystemConfigurationUtil;
064import com.sun.identity.common.SystemConfigurationException;
065import com.sun.identity.common.SystemTimerPool;
066import com.sun.identity.common.TaskRunnable;
067import com.sun.identity.common.TimerPool;
068import com.sun.identity.shared.encode.URLEncDec;
069import com.sun.identity.shared.xml.XMLUtils;
070import com.sun.identity.shared.encode.Base64;
071
072import javax.servlet.ServletException;
073import javax.servlet.http.HttpServletResponse;
074import javax.servlet.http.HttpServletRequest;
075
076import javax.xml.soap.MimeHeaders;
077import javax.xml.soap.MimeHeader;
078
079import com.sun.identity.saml.assertion.SubjectConfirmation;
080import com.sun.identity.saml.assertion.Assertion;
081import com.sun.identity.saml.assertion.Attribute;
082import com.sun.identity.saml.assertion.AttributeStatement;
083import com.sun.identity.saml.assertion.AuthenticationStatement;
084import com.sun.identity.saml.assertion.AudienceRestrictionCondition;
085import com.sun.identity.saml.assertion.Condition;
086import com.sun.identity.saml.assertion.Conditions;
087import com.sun.identity.saml.assertion.Statement;
088import com.sun.identity.saml.assertion.SubjectStatement;
089import com.sun.identity.saml.xmlsig.XMLSignatureManager;
090import com.sun.identity.saml.plugins.PartnerAccountMapper;
091import com.sun.identity.saml.protocol.*;
092import com.sun.identity.saml.servlet.POSTCleanUpRunnable;
093import com.sun.identity.plugin.session.SessionException;
094import com.sun.identity.plugin.session.SessionManager;
095import com.sun.identity.plugin.session.SessionProvider;
096import com.sun.identity.saml.assertion.Subject;
097import com.sun.identity.saml.SAMLClient;
098import com.sun.identity.federation.common.FSUtils;
099
100import javax.xml.parsers.DocumentBuilder;
101
102import org.apache.xml.security.c14n.Canonicalizer;
103
104/**
105 * This class contains some utility methods for processing SAML protocols.
106 *
107 * @supported.api
108 */
109public class SAMLUtils  extends SAMLUtilsCommon {
110
111    /**
112     * Attribute that specifies maximum content length for SAML request in
113     * <code>AMConfig.properties</code> file.
114     */
115    public static final String HTTP_MAX_CONTENT_LENGTH =
116        "com.sun.identity.saml.request.maxContentLength";
117
118    /**
119     * Default maximum content length is set to 16k.
120     */
121    public static final int defaultMaxLength = 16384;
122
123    /**
124     * Default maximum content length in string format.
125     */
126    public static final String DEFAULT_CONTENT_LENGTH =
127    String.valueOf(defaultMaxLength);
128
129    private static final String ERROR_JSP = "/saml2/jsp/autosubmittingerror.jsp";
130    
131    private static int maxContentLength = 0;
132    private static Map idTimeMap = Collections.synchronizedMap(new HashMap());
133    private static TaskRunnable cGoThrough = null;
134    private static TaskRunnable cPeriodic = null;
135    private static Object ssoToken;
136 
137    static {
138        org.apache.xml.security.Init.init();
139        if (SystemConfigurationUtil.isServerMode()) {
140            long period = ((Integer) SAMLServiceManager.getAttribute(
141                        SAMLConstants.CLEANUP_INTERVAL_NAME)).intValue() * 1000;
142            cGoThrough = new POSTCleanUpRunnable(period, idTimeMap);
143            TimerPool timerPool = SystemTimerPool.getTimerPool();
144            timerPool.schedule(cGoThrough, new Date(((currentTimeMillis()
145                    + period) / 1000) * 1000));
146            ScheduleableGroupAction periodicAction = new
147                ScheduleableGroupAction() {
148                public void doGroupAction(Object obj) {
149                    idTimeMap.remove(obj);
150                }
151            };
152            cPeriodic = new PeriodicGroupRunnable(periodicAction, period,
153                180000, true);
154            timerPool.schedule(cPeriodic, new Date(((currentTimeMillis() +
155                    period) / 1000) * 1000));
156        }
157        try {
158            maxContentLength = Integer.parseInt(SystemConfigurationUtil.
159                getProperty(SAMLUtils.HTTP_MAX_CONTENT_LENGTH,
160                SAMLUtils.DEFAULT_CONTENT_LENGTH));
161        } catch (NumberFormatException ne) {
162            SAMLUtils.debug.error("Wrong format of SAML request max content "
163                + "length. Take default value.");
164            maxContentLength=  SAMLUtils.defaultMaxLength;
165        }
166    }
167    
168    /**
169     * Constructor
170     * iPlanet-PRIVATE-DEFAULT-CONSTRUCTOR
171     */
172    private SAMLUtils() {
173    }
174    
175    
176    /**
177     * Generates an ID String with length of SAMLConstants.ID_LENGTH.
178     * @return string the ID String; or null if it fails.
179     */
180    public static String generateAssertionID() {
181        String encodedID = generateID();
182        if (encodedID == null) {
183            return null;
184        }
185        
186        String id = null;
187        try {
188            id = SystemConfigurationUtil.getServerID(
189            SAMLServiceManager.getServerProtocol(),
190            SAMLServiceManager.getServerHost(),
191            Integer.parseInt(SAMLServiceManager.getServerPort()),
192            SAMLServiceManager.getServerURI());
193        } catch (Exception ex) {
194            if (SAMLUtils.debug.messageEnabled()) {
195                SAMLUtils.debug.message("SAMLUtil:generateAssertionID: "
196                + "exception obtain serverID:", ex);
197            }
198        }
199        if (id == null) {
200            return encodedID;
201        } else {
202            return (encodedID + id);
203        }
204    }
205    
206    /**
207     * Verifies if an element is a type of a specific query.
208     * Currently, this method is used by class AuthenticationQuery,
209     * AuthorizationDecisionQuery, and AttributeQuery.
210     * @param element a DOM Element which needs to be verified.
211     * @param queryname A specific name of a query, for example,
212     *          AuthenticationQuery, AuthorizationDecisionQuery, or
213     *                AttributeQuery.
214     * @return true if the element is a type of the specified query; false
215     *                otherwise.
216     */
217    public static boolean checkQuery(Element element, String queryname) {
218        String tag = element.getLocalName();
219        if (tag == null) {
220            return false;
221        } else if (tag.equals("Query") || tag.equals("SubjectQuery")) {
222            NamedNodeMap nm = element.getAttributes();
223            int len = nm.getLength();
224            String attrName;
225            Attr attr;
226            boolean found = false;
227            for (int j = 0; j < len; j++) {
228                attr = (Attr) nm.item(j);
229                attrName = attr.getLocalName();
230                if ((attrName != null) && (attrName.equals("type")) &&
231                (attr.getNodeValue().equals(queryname + "Type"))) {
232                    found = true;
233                    break;
234                }
235            }
236            if (!found) {
237                return false;
238            }
239        } else if (!tag.equals(queryname)) {
240            return false;
241        }
242        return true;
243    }
244    
245    /**
246     * Generates sourceID of a site.
247     * @param siteURL a String that uniquely identifies a site.
248     * @return <code>Base64</code> encoded SHA digest of siteURL.
249     */
250    public static String generateSourceID(String siteURL) {
251        if ((siteURL == null) || (siteURL.length() == 0)) {
252            SAMLUtils.debug.error("SAMLUtils.genrateSourceID: empty siteURL.");
253            return null;
254        }
255        MessageDigest md = null;
256        try {
257            md = MessageDigest.getInstance("SHA");
258        } catch (Exception e) {
259            SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception when"
260            + " generating digest:",e);
261            return null;
262        }
263        md.update(SAMLUtils.stringToByteArray(siteURL));
264        byte byteResult[] = md.digest();
265        String result = null;
266        try {
267            result = Base64.encode(byteResult).trim();
268        } catch (Exception e) {
269            SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception:",e);
270        }
271        return result;
272    }
273
274    /**
275     * Generates assertion handle.
276     * @return 20-byte random string to be used to form an artifact.
277     */
278    public static String generateAssertionHandle() {
279        if (random == null) {
280            return null;
281        }
282        byte bytes[] = new byte[SAMLConstants.ID_LENGTH];
283        random.nextBytes(bytes);
284        String id = null;
285        try {
286            id = SystemConfigurationUtil.getServerID(
287                SAMLServiceManager.getServerProtocol(),
288                SAMLServiceManager.getServerHost(),
289                Integer.parseInt(SAMLServiceManager.getServerPort()),
290                SAMLServiceManager.getServerURI());
291        } catch (Exception ex) {
292            if (SAMLUtils.debug.messageEnabled()) {
293                SAMLUtils.debug.message("SAMLUtil:generateAssertionHandle: "
294                + "exception obtain serverID:", ex);
295            }
296        }
297        if (id != null) {
298            byte idBytes[] = stringToByteArray(id);
299            // TODO: should we check if idBytes.length == 2 ?
300            if (idBytes.length < bytes.length) {
301                for (int i = 1; i <= idBytes.length; i++) {
302                    bytes[bytes.length - i] = idBytes[idBytes.length - i];
303                }
304            }
305        }
306        return byteArrayToString(bytes);
307    }
308
309    /**
310     * Converts a HEX encoded string to a byte array.
311     * @param hexString HEX encoded string
312     * @return byte array.
313     */
314    public static byte[] hexStringToByteArray(String hexString) {
315        int read = hexString.length();
316        byte[] byteArray = new byte[read/2];
317        for (int i=0, j=0; i < read; i++, j++) {
318            String part = hexString.substring(i,i+2);
319            byteArray[j] =
320            new Short(Integer.toString(Integer.parseInt(part,16))).
321            byteValue();
322            i++;
323        }
324        return byteArray;
325    }
326
327    /**
328     * Converts HEX encoded string to Base64 encoded string.
329     * @param hexString HEX encoded string.
330     * @return Base64 encoded string.
331     */
332    public static String hexStringToBase64(String hexString) {
333        int read = hexString.length();
334        byte[] byteArray = new byte[read/2];
335        for (int i=0, j=0; i < read; i++, j++) {
336            String part = hexString.substring(i,i+2);
337            byteArray[j] =
338            new Short(Integer.toString(Integer.parseInt(part,16))).
339            byteValue();
340            i++;
341        }
342        String encodedID = null;
343        try {
344            encodedID = Base64.encode(byteArray).trim();
345        } catch (Exception e) {
346            if (SAMLUtils.debug.messageEnabled()) {
347                SAMLUtils.debug.message("SAMLUtil:hexStringToBase64: "
348                + "exception encode input:", e);
349            }
350        }
351        if (SAMLUtils.debug.messageEnabled()) {
352            SAMLUtils.debug.message("base 64 source id is :"+encodedID);
353        }
354        return encodedID;
355    }
356    
357    /**
358     * Gets sourceSite corresponding to an issuer from the partner URL list.
359     * @param issuer The issuer string.
360     * @return SAMLServiceManager.SOAPEntry of the issuer if it's on the list;
361     *                        null otherwise.
362     */
363    public static SAMLServiceManager.SOAPEntry getSourceSite(String issuer) {
364        if (issuer == null) {
365            return null;
366        }
367        Map entries = (Map) SAMLServiceManager.getAttribute(
368        SAMLConstants.PARTNER_URLS);
369        if (entries == null) {
370            SAMLUtils.debug.error("SAMLUtils.isOnPartnerURLList: PartnerURL "
371            + "list is null.");
372            return null;
373        }
374        
375        Iterator entryIter = entries.values().iterator();
376        boolean found = false;
377        SAMLServiceManager.SOAPEntry srcSite = null;
378        String theIssuer = null;
379        while (entryIter.hasNext()) {
380            srcSite = (SAMLServiceManager.SOAPEntry) entryIter.next();
381            if ((srcSite != null) &&
382            ((theIssuer = srcSite.getIssuer()) != null) &&
383            (theIssuer.equals(issuer))) {
384                found = true;
385                break;
386            }
387        }
388        if (found) {
389            return srcSite;
390        } else {
391            return null;
392        }
393    }
394    
395    /**
396     * Returns site ID based on the host name. The site ID
397     * will be in Base64 encoded format. This method will print out site ID
398     * to the standard output
399     * @param args host name
400     */
401    public static void main(String args[]) {
402        
403        if (args.length != 1) {
404            System.out.println("usage : java SAMLUtils <host_name>");
405            return;
406        }
407        
408        System.out.println(generateSourceID(args[0]));
409    }
410    
411    /**
412     * Checks if a <code>SubjectConfirmation</code> is correct.
413     * @param sc <code>SubjectConfirmation</code> instance to be checked.
414     * @return true if the <code>SubjectConfirmation</code> instance passed in
415     * has only one <code>ConfirmationMethod</code>, and this
416     * <code>ConfirmationMethod</code> is set to
417     * <code>SAMLConstants.CONFIRMATION_METHOD_IS</code>.
418     */
419    public static boolean isCorrectConfirmationMethod(SubjectConfirmation sc) {
420        if (sc == null) {
421            return false;
422        }
423        
424        Set cmSet = sc.getConfirmationMethod();
425        if ((cmSet == null) || (cmSet.size() != 1)) {
426            if (SAMLUtils.debug.messageEnabled()) {
427                SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:"
428                + " missing ConfirmationMethod in the Subject.");
429            }
430            return false;
431        }
432        
433        String conMethod = (String) cmSet.iterator().next();
434        if ((conMethod == null) ||
435        (!conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_IS))) {
436            if (SAMLUtils.debug.messageEnabled()) {
437                SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:"
438                + " wrong ConfirmationMethod value.");
439            }
440            return false;
441        }
442        
443        return true;
444    }
445    
446    /**
447     * Returns true if the assertion is valid both time wise and
448     * signature wise, and contains at least one AuthenticationStatement.
449     * @param assertion <code>Assertion</code> instance to be checked.
450     * @return <code>true</code> if the assertion is valid both time wise and
451     * signature wise, and contains at least one AuthenticationStatement.
452     */
453    public static boolean isAuthNAssertion(Assertion assertion) {
454        if (assertion == null) {
455            return false;
456        }
457        
458        if ((!assertion.isTimeValid()) || (!assertion.isSignatureValid())) {
459            return false;
460        }
461        
462        Set statements = assertion.getStatement();
463        Statement statement = null;
464        Iterator iterator = statements.iterator();
465        while (iterator.hasNext()) {
466            statement = (Statement) iterator.next();
467            if (statement.getStatementType() ==
468            Statement.AUTHENTICATION_STATEMENT) {
469                return true;
470            }
471        } // loop through statements
472        return false;
473    }
474 
475    /**
476     * Converts a string to a byte array.
477     * @param input a String to be converted.
478     * @return result byte array.
479     */
480    public static byte[] stringToByteArray(String input) {
481        char chars[] = input.toCharArray();
482        byte bytes[] = new byte[chars.length];
483        for (int i = 0; i < chars.length; i++) {
484            bytes[i] = (byte) chars[i];
485        }
486        return bytes;
487    }
488 
489
490    /**
491     * Returns server ID.
492     * @param idTypeString An ID string
493     * @return server ID part of the id.
494     */
495    public static String getServerID(String idTypeString) {
496        if (idTypeString == null) {
497            return null;
498        }
499        int len = idTypeString.length();
500        String id = null;
501        if (len >= SAMLConstants.SERVER_ID_LENGTH) {
502            id = idTypeString.substring((len - SAMLConstants.SERVER_ID_LENGTH),
503            len);
504            return id;
505        } else {
506            return null;
507        }
508    }
509    
510    /**
511     * Returns server url of a site.
512     * @param str Server ID.
513     * @return Server url corresponding to the server id.
514     */
515    public static String getServerURL(String str) {
516        String id = SAMLUtils.getServerID(str);
517        if (id == null) {
518            return null;
519        }
520        
521        if (SAMLUtils.debug.messageEnabled()) {
522            SAMLUtils.debug.message("SAMLUtils.getServerURL: id=" + id);
523        }
524        
525        String remoteUrl = null;
526        try {
527            remoteUrl = SystemConfigurationUtil.getServerFromID(id);
528        } catch (SystemConfigurationException se) {
529            if (SAMLUtils.debug.messageEnabled()) {
530                SAMLUtils.debug.message("SAMLUtils.getServerURL: ServerEntry" +
531                "NotFoundException for " + id);
532            }
533            return null;
534        }
535        String thisUrl = SAMLServiceManager.getServerURL();
536        if (SAMLUtils.debug.messageEnabled()) {
537            SAMLUtils.debug.message("SAMLUtils.getServerURL: remoteUrl=" +
538            remoteUrl + ", thisUrl=" + thisUrl);
539        }
540        if ((remoteUrl == null) || (thisUrl == null) ||
541        (remoteUrl.equalsIgnoreCase(thisUrl))) {
542            return null;
543        } else {
544            return remoteUrl;
545        }
546    }
547    
548    /**
549     * Returns full service url.
550     * @param shortUrl short URL of the service.
551     * @return full service url.
552     */
553    public static String getFullServiceURL(String shortUrl) {
554        String result = null;
555        try {
556            URL u = new URL(shortUrl);
557            URL weburl = SystemConfigurationUtil.getServiceURL(
558                SAMLConstants.SAML_AM_NAMING, u.getProtocol(), u.getHost(),
559                    u.getPort(), u.getPath());
560            result = weburl.toString();
561            if (SAMLUtils.debug.messageEnabled()) {
562                SAMLUtils.debug.message("SAMLUtils.getFullServiceURL:" +
563                "full remote URL is: " + result);
564            }
565        } catch (Exception e) {
566            if (SAMLUtils.debug.warningEnabled()) {
567                SAMLUtils.debug.warning("SAMLUtils.getFullServiceURL:" +
568                "Exception:", e);
569            }
570        }
571        return result;
572    }
573
574    /**
575     * Returns attributes included in <code>AttributeStatement</code> of the
576     * assertion.
577     * @param envParameters return map which includes name value pairs of 
578     *   attributes included in <code>AttributeStatement</code> of the assertion
579     * @param assertion an <code>Assertion</code> object which contains
580     *   <code>AttributeStatement</code>
581     * @param subject the <code>Subject</code> instance from
582     *   <code>AuthenticationStatement</code>. The <code>Subject</code>
583     *   included in <code>AttributeStatement</code> must match this
584     *   <code>Subject</code> instance.
585     */
586    public static void addEnvParamsFromAssertion(Map envParameters,
587    Assertion assertion,
588    com.sun.identity.saml.assertion.Subject subject) {
589        Set statements = assertion.getStatement();
590        Statement statement = null;
591        Iterator stmtIter = null;
592        List attrs = null;
593        Iterator attrIter = null;
594        Attribute attribute = null;
595        Element attrValue = null;
596        List attrValues = null;
597        String attrName = null;
598        String attrValueString = null;
599        if ((statements != null) && (!statements.isEmpty())) {
600            stmtIter = statements.iterator();
601            while (stmtIter.hasNext()) {
602                statement = (Statement) stmtIter.next();
603                if (statement.getStatementType() ==
604                Statement.ATTRIBUTE_STATEMENT) {
605                    // check for subject
606                    if (!subject.equals(
607                    ((AttributeStatement)statement).getSubject())) {
608                        continue;
609                    }
610                    
611                    attrs = ((AttributeStatement) statement).getAttribute();
612                    attrIter = attrs.iterator();
613                    while (attrIter.hasNext()) {
614                        attribute = (Attribute) attrIter.next();
615                        try {
616                            attrValues = attribute.getAttributeValue();
617                        } catch (Exception e) {
618                            debug.error("SAMLUtils.addEnvParamsFromAssertion:"+
619                            " cannot obtain attribute value:", e);
620                            continue;
621                        }
622                        attrName = attribute.getAttributeName();
623                        List attrValueList = null;
624
625                        for(Iterator avIter = attrValues.iterator();
626                            avIter.hasNext(); ) {
627
628                            attrValue = (Element) avIter.next();
629                            if (!XMLUtils.hasElementChild(attrValue)) {
630                                attrValueString =
631                                    XMLUtils.getElementValue(attrValue);
632                                if (attrValueList == null) {
633                                    attrValueList = new ArrayList();
634                                }
635                                attrValueList.add(attrValueString);
636                            }
637                        }
638                        if (attrValueList != null) {
639                            if (debug.messageEnabled()) {
640                                debug.message(
641                                    "SAMLUtils.addEnvParamsFromAssertion:" +
642                                    " attrName = " + attrName +
643                                    " attrValue = " + attrValueList);
644                            }
645                            String[] attrValueStrs = (String[])attrValueList.
646                                toArray(new String[attrValueList.size()]);
647                            try {
648                                envParameters.put(attrName, attrValueStrs);
649                            } catch (Exception ex) {
650                                if (debug.messageEnabled()) {
651                                    debug.message(
652                                        "SAMLUtils.addEnvParamsFromAssertion:",
653                                        ex);
654                                }
655                            }
656                        } else if (debug.messageEnabled()) {
657                            if (debug.messageEnabled()) {
658                                debug.message(
659                                    "SAMLUtils.addEnvParamsFromAssertion:" +
660                                    " attrName = " + attrName +
661                                    " has no value");
662                            }
663                        }
664                    }
665                } // if it's an attribute statement
666            }
667        }
668    }
669
670    /**
671     * Returns maximum content length of a SAML request.
672     * @return maximum content length of a SAML request.
673     */
674    public static int getMaxContentLength() {
675        return maxContentLength;
676    }
677    
678    // ************************************************************************
679    // Methods used by SAML Servlets
680    // ************************************************************************
681 
682    /**
683     * Checks content length of a http request to avoid dos attack.
684     * In case SAML inter-op with other SAML vendor who may not provide content
685     * length in HttpServletRequest. We decide to support no length restriction
686     * for Http communication. Here, we use a special value (e.g. 0) to
687     * indicate that no enforcement is required.
688     * @param request <code>HttpServletRequest</code> instance to be checked.
689     * @exception ServletException if context length of the request exceeds
690     *   maximum content length allowed.
691     */
692    public static void checkHTTPContentLength(HttpServletRequest request)
693    throws ServletException {
694        if (maxContentLength != 0) {
695            int length =  request.getContentLength();
696            if (SAMLUtils.debug.messageEnabled()) {
697                SAMLUtils.debug.message("HttpRequest content length= " +length);
698            }
699            if (length > maxContentLength) {
700                if (SAMLUtils.debug.messageEnabled()) {
701                    SAMLUtils.debug.message(
702                        "content length too large" + length);
703                }
704                throw new ServletException(
705                SAMLUtils.bundle.getString("largeContentLength"));
706            }
707        }
708    }
709    
710    /**
711     * Post assertions and attributes to the target url.
712     * This method opens a URL connection to the target specified and POSTs
713     * assertions to it using the passed HttpServletResponse object. It POSTs
714     * multiple parameter names "assertion" with value being each of the 
715     * <code>Assertion</code> in the passed Set.
716     * @param response <code>HttpServletResponse</code> object
717     * @param out The print writer which for content is to be written too.
718     * @param assertion List of <code>Assertion</code>s to be posted.
719     * @param targeturl target url
720     * @param attrMap Map of attributes to be posted to the target
721     */
722    public static void postToTarget(HttpServletResponse response, PrintWriter out,
723                                    List assertion, String targeturl, Map attrMap) throws IOException {
724        out.println("<HTML>");
725        out.println("<HEAD>\n");
726        out.println("<TITLE>Access rights validated</TITLE>\n");
727        out.println("</HEAD>\n");
728        out.println("<BODY Onload=\"document.forms[0].submit()\">");
729        Iterator it = null;
730        if (SAMLUtils.debug.messageEnabled()) {
731            out.println("<H1>Access rights validated</H1>\n");
732            out.println("<meta http-equiv=\"refresh\" content=\"20\">\n");
733            out.println("<P>We have verified your access rights <STRONG>" +
734            "</STRONG> according to the assertion shown "
735            +"below. \n");
736            out.println("You are being redirected to the resource.\n");
737            out.println("Please wait ......\n");
738            out.println("</P>\n");
739            out.println("<HR><P>\n");
740            if (assertion != null) {
741                it = assertion.iterator();
742                while (it.hasNext()) {
743                    out.println(SAMLUtils.displayXML((String)it.next()));
744                }
745            }
746            out.println("</P>\n");
747        }
748        out.println("<FORM METHOD=\"POST\" ACTION=\"" + targeturl + "\">");
749        if (assertion != null) {
750            it = assertion.iterator();
751            while (it.hasNext()) {
752                out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+
753                SAMLConstants.POST_ASSERTION_NAME + "\"");
754                out.println("VALUE=\"" +
755                URLEncDec.encode((String)it.next()) + "\">");
756            }
757        }
758        if (attrMap != null && !attrMap.isEmpty()) {
759            StringBuffer attrNamesSB = new StringBuffer();
760            Set entrySet = attrMap.entrySet();
761            for(Iterator iter = entrySet.iterator(); iter.hasNext();) {
762                Map.Entry entry = (Map.Entry)iter.next();
763                String attrName = HTMLEncode((String)entry.getKey(), '\"');
764                String attrValue = HTMLEncode((String)entry.getValue(), '\"');
765                out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ attrName +
766                "\" VALUE=\"" + attrValue + "\">");
767                if (attrNamesSB.length() > 0) {
768                    attrNamesSB.append(":");
769                }
770                attrNamesSB.append(attrName);
771            }
772            out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+
773            SAMLConstants.POST_ATTR_NAMES + "\" VALUE=\"" +
774            attrNamesSB + "\">");
775        }
776        out.println("</FORM>");
777        out.println("</BODY></HTML>");
778        out.close();
779    }
780    
781    /**
782     * Returns true of false based on whether the target passed as parameter
783     * accepts form POST.
784     * @param targetIn url to be checked
785     * @return true if it should post assertion to the target passed in; false
786     *   otherwise.
787     */
788    public static boolean postYN(String targetIn) {
789        SAMLUtils.debug.message("Inside postYN()");
790        if ((targetIn == null) || (targetIn.length() == 0)) {
791            return false;
792        }
793        Set targets = (Set) SAMLServiceManager.
794        getAttribute(SAMLConstants.POST_TO_TARGET_URLS);
795        if ((targets == null) || (targets.size() == 0)) {
796            return false;
797        }
798        URL targetUrl = null;
799        try {
800            targetUrl = new URL(targetIn);
801        } catch (MalformedURLException me ) {
802            SAMLUtils.debug.error("SAMLUtils:postYN(): Malformed URL passed");
803            return false;
804        }
805        String targetInHost = targetUrl.getHost();
806        int targetInPort = targetUrl.getPort();
807        String targetInPath = targetUrl.getPath();
808        // making target string without protocol
809        String targetToCompare = new StringBuffer(targetInHost.toLowerCase())
810        .append(":").append(String.valueOf(targetInPort))
811        .append("/").append(targetInPath).toString();
812        if (targets.contains(targetToCompare)) {
813            return true;
814        } else {
815            return false;
816        }
817    }
818    
819    /**
820     * Replaces every occurence of ch with 
821     * "&#&lt;ascii code of ch>;"
822     * @param srcStr orginal string to to be encoded.
823     * @param ch the charactor needs to be encoded.
824     * @return encoded string
825     */
826    public static String HTMLEncode(String srcStr, char ch) {
827        if (srcStr == null) {
828            return null;
829        }
830        
831        int fromIndex = 0;
832        int toIndex;
833        StringBuffer dstSB = new StringBuffer();
834        
835        while((toIndex = srcStr.indexOf(ch, fromIndex)) != -1) {
836            dstSB.append(srcStr.substring(fromIndex, toIndex))
837            .append("&#" + (int)ch + ";");
838            fromIndex = toIndex + 1;
839        }
840        dstSB.append(srcStr.substring(fromIndex));
841        
842        return dstSB.toString();
843    }
844
845    /**
846     * Displays an XML string.
847     * This is a utility function used to hack up an HTML display of an XML
848     * string.
849     * @param input original string
850     * @return encoded string so it can be displayed properly by html.
851     */
852    public static String displayXML(String input) {
853        debug.message("In displayXML ");
854        StringCharacterIterator iter = new StringCharacterIterator(input);
855        StringBuffer buf = new StringBuffer();
856        
857        for(char c = iter.first();c != CharacterIterator.DONE;c = iter.next()) {
858            if (c=='>') {
859                buf.append("&gt;");
860            } else if (c=='<') {
861                buf.append("&lt;");
862            } else if (c=='\n'){
863                buf.append("<BR>\n");
864            } else {
865                buf.append(c);
866            }
867        }
868        return buf.toString();
869    }
870    
871    /**
872     * Gets the list of <code>Assertion</code> objects from a list of
873     * 'String' assertions.
874     * @param assertions List of assertions in string format
875     * @return List of <code>Assertion</code> objects
876     */
877    public static List getListOfAssertions(List assertions) {
878        List returnAssertions = new ArrayList();
879        try {
880            if (assertions != null) {
881                Iterator it = assertions.iterator();
882                while (it.hasNext()) {
883                    Document doc = XMLUtils.toDOMDocument((String)it.next(),
884                                                            debug);
885                    Element root = doc.getDocumentElement();
886                    if (root != null) {
887                        Assertion assertion = new Assertion(root);
888                        returnAssertions.add(assertion);
889                    }
890                }
891            }
892        } catch (Exception e) {
893            if (debug.messageEnabled()) {
894                debug.message("SAMLUtils.getListOfAssertions : " +
895                "Exception : ", e);
896            }
897        }
898        return returnAssertions;
899    }
900    
901    
902    // ************************************************************************
903    // Methods used / shared by SAML Authentication Module and SAML Servlets
904    // ************************************************************************
905
906    /**
907     * Returns byte array from a SAML <code>Response</code>.
908     * @param samlResponse <code>Response</code> object
909     * @return byte array
910     * @exception SAMLException if error occurrs during the process.
911     */
912    public static byte[] getResponseBytes(Response samlResponse)
913        throws SAMLException
914    {
915        byte ret[] = null;
916        try {
917            ret = samlResponse.toString(true, true, true).
918            getBytes(SAMLConstants.DEFAULT_ENCODING);
919        } catch (UnsupportedEncodingException ue) {
920            if (debug.messageEnabled()) {
921                debug.message("getResponseBytes : " , ue);
922            }
923            throw new SAMLException(ue.getMessage());
924        }
925        return ret;
926    }
927 
928    /**
929     * Returns <code>Response</code> object from byte array.
930     * @param bytes byte array
931     * @return <code>Response</code> object
932     */
933    public static Response getResponse(byte [] bytes) {
934        Response temp = null;
935        if (bytes == null) {
936            return null;
937        }
938        try {
939            temp = Response.parseXML(new ByteArrayInputStream(bytes));
940        } catch (SAMLException se) {
941            debug.error("getResponse : " , se);
942        }
943        return temp;
944    }
945
946    /**
947     * Verifies a <code>Response</code>.
948     * @param response SAML <code>Response</code> object
949     * @param requestUrl this server's POST profile URL
950     * @param request <code>HttpServletRequest</code> object
951     * @return true if the response is valid; false otherwise.
952     */
953    public static boolean verifyResponse(Response response,
954    String requestUrl, HttpServletRequest request) {
955        if (!response.isSignatureValid()) {
956            debug.message("verifyResponse: Response's signature is invalid.");
957            return false;
958        }
959
960        // check Recipient == this server's POST profile URL(requestURL)
961        String recipient = response.getRecipient();
962        if ((recipient == null) || (recipient.length() == 0) ||
963        ((!equalURL(recipient, requestUrl)) &&
964        (!equalURL(recipient,getLBURL(requestUrl, request))))) {
965            debug.error("verifyResponse : Incorrect Recipient.");
966            return false;
967        }
968        
969        // check status of the Response
970        if (!response.getStatus().getStatusCode().getValue().endsWith(
971        SAMLConstants.STATUS_CODE_SUCCESS_NO_PREFIX)) {
972            debug.error("verifyResponse : Incorrect StatusCode value.");
973            return false;
974        }
975        
976        return true;
977    }
978    
979    private static String getLBURL(String requestUrl,
980                                 HttpServletRequest request)
981    {
982        String host = request.getHeader("host");
983        if (host == null) {
984            return requestUrl;
985        }
986        int index = requestUrl.indexOf("//");
987        if (index == -1) {
988            return requestUrl;
989        }
990        StringBuffer sb = new StringBuffer(200);
991        sb.append(requestUrl.substring(0, index + 2)).append(host);
992        String rest = requestUrl.substring(index +2, requestUrl.length());
993        if ((index = rest.indexOf("/")) != -1) {
994            sb.append(rest.substring(index, rest.length()));
995        }
996        if (debug.messageEnabled()) {
997            debug.message("getLBURL: LBURL = " + sb.toString());
998        }
999        return sb.toString().trim();
1000    }
1001    
1002    // ************************************************************************
1003    // Methods used by SAML Authentication Module
1004    // ************************************************************************
1005    
1006    /**
1007     * Gets List of assertions in String format from a list of 
1008     * <code>Assertion</code> objects.
1009     * @param assertions List of <code>Assertion</code> objects.
1010     * @return List of assertions in String format
1011     */
1012    public static List getStrAssertions(List assertions) {
1013        List returnAssertions = new ArrayList();
1014        if (assertions != null) {
1015            Iterator it = assertions.iterator();
1016            while (it.hasNext()) {
1017                returnAssertions.add(
1018                    ((Assertion)(it.next())).toString(true,true));
1019            }
1020        }
1021        return returnAssertions;
1022    }
1023    
1024    /**
1025     * Verifies Signature for Post response.
1026     * @param samlResponse <code>Response</code> object from post profile.
1027     * @return true if the signature on the reponse is valid; false otherwise.
1028     */
1029    public static boolean verifySignature(Response samlResponse) {
1030        if ((samlResponse != null) &&
1031        (!samlResponse.isSigned() || (!samlResponse.isSignatureValid()))) {
1032            return false;
1033        }
1034        return true;
1035    }
1036    
1037    /**
1038     * Gets Attribute Map to be set in the Session.
1039     * @param partnerdest <code>SOAPEntry</code> object
1040     * @param assertions List of <code>Assertion</code>s
1041     * @param subject <code>Subject</code> object
1042     * @param target target of final SSO
1043     * @return Map which contains name and attributes.
1044     * @exception Exception if an error occurrs.
1045     */
1046    public static Map getAttributeMap(
1047    SAMLServiceManager.SOAPEntry partnerdest,
1048    List assertions,
1049    com.sun.identity.saml.assertion.Subject subject,
1050    String target)
1051    throws Exception {
1052        String srcID = partnerdest.getSourceID();
1053        String name = null;
1054        String org = null;
1055        Map attrMap = new HashMap();
1056        PartnerAccountMapper paMapper = partnerdest.getPartnerAccountMapper();
1057        
1058        if (paMapper != null) {
1059            Map map = paMapper.getUser(assertions, srcID, target);
1060            name = (String) map.get(PartnerAccountMapper.NAME);
1061            org =  (String) map.get(PartnerAccountMapper.ORG);
1062            attrMap = (Map) map.get(PartnerAccountMapper.ATTRIBUTE);
1063        }
1064        
1065        if (attrMap == null) {
1066            attrMap = new HashMap();
1067        }
1068        attrMap.put(SAMLConstants.USER_NAME, name);
1069        if ((org != null) && (org.length() != 0)) {
1070            attrMap.put(SessionProvider.REALM, org);
1071        } else {
1072            attrMap.put(SessionProvider.REALM, "/");
1073        }
1074        
1075        if (debug.messageEnabled()) {
1076            debug.message("getAttributeMap : " + "name = " +
1077            name + ", realm=" + org + ", attrMap = " + attrMap);
1078        }
1079        
1080        return attrMap;
1081    }
1082    
1083    /**
1084     * Checks response and get back a Map of relevant data including,
1085     * Subject, SOAPEntry for the partner and the List of Assertions.
1086     * @param response <code>Response</code> object
1087     * @return Map of data including Subject, SOAPEntry, and list of assertions.
1088     */
1089    public static Map verifyAssertionAndGetSSMap(Response response) {
1090        // loop to check assertions
1091        com.sun.identity.saml.assertion.Subject subject = null;
1092        SAMLServiceManager.SOAPEntry srcSite = null;
1093        List assertions = response.getAssertion();
1094        Iterator iter = assertions.iterator();
1095        Assertion assertion = null;
1096        String aIDString = null;
1097        String issuer = null;
1098        Iterator stmtIter = null;
1099        Statement statement = null;
1100        int stmtType = Statement.NOT_SUPPORTED;
1101        com.sun.identity.saml.assertion.Subject sub = null;
1102        SubjectConfirmation subConf = null;
1103        Set confMethods = null;
1104        String confMethod = null;
1105        Date date = null;
1106        while (iter.hasNext()) {
1107            assertion = (Assertion) iter.next();
1108            aIDString = assertion.getAssertionID();
1109            // make sure it's not being used
1110            if (idTimeMap.containsKey(aIDString)) {
1111                debug.error("verifyAssertion "
1112                + "AndGetSSMap: Assertion: " + aIDString + " is used.");
1113                return null;
1114            }
1115            
1116            // check issuer of the assertions
1117            issuer = assertion.getIssuer();
1118            if ((srcSite = SAMLUtils.getSourceSite(issuer)) == null) {
1119                debug.error("verifyAsserti "
1120                + "onAndGetSSMap: issuer is not on the Partner list.");
1121                return null;
1122            }
1123            
1124            if (!assertion.isSignatureValid()) {
1125                debug.error("verifyAssertion "
1126                + "AndGetSSMap: assertion's signature is not valid.");
1127                return null;
1128            }
1129            
1130            // must be valid (timewise)
1131            if (!assertion.isTimeValid()) {
1132                debug.error("verifyAssertion "
1133                + "AndGetSSMap: assertion's time is not valid.");
1134                return null;
1135            }
1136            
1137            // TODO: IssuerInstant of the assertion is within a few minutes
1138            // This is a MAY in spec. Which number to use for the few minutes?
1139            
1140            // TODO: check AudienceRestrictionCondition
1141            
1142            //for each assertion, loop to check each statement
1143            stmtIter = assertion.getStatement().iterator();
1144            while (stmtIter.hasNext()) {
1145                statement = (Statement) stmtIter.next();
1146                stmtType = statement.getStatementType();
1147                if ((stmtType == Statement.AUTHENTICATION_STATEMENT) ||
1148                (stmtType == Statement.ATTRIBUTE_STATEMENT) ||
1149                (stmtType == Statement.AUTHORIZATION_DECISION_STATEMENT)) {
1150                    sub = ((SubjectStatement)statement).getSubject();
1151                    
1152                    // ConfirmationMethod of each subject must be set to bearer
1153                    if (((subConf = sub.getSubjectConfirmation()) == null) ||
1154                    ((confMethods = subConf.getConfirmationMethod())
1155                    == null) ||
1156                    (confMethods.size() != 1)) {
1157                        debug.error("verify "
1158                        + "AssertionAndGetSSMap: missing or extra "
1159                        + "ConfirmationMethod.");
1160                        return null;
1161                    }
1162                    if (((confMethod = (String) confMethods.iterator().next())
1163                    == null) ||
1164                    (!confMethod.equals(
1165                    SAMLConstants.CONFIRMATION_METHOD_BEARER))) {
1166                        debug.error("verify "
1167                        + "AssertionAndGetSSMap:wrong ConfirmationMethod.");
1168                        return null;
1169                    }
1170                    
1171                    //TODO: must contain same Subject for all statements?
1172                    
1173                    if (stmtType == Statement.AUTHENTICATION_STATEMENT) {
1174                        //TODO: if it has SubjectLocality,its IP must == sender
1175                        // browser IP. This is a MAY item in the spec.
1176                        if (subject == null) {
1177                            subject = sub;
1178                        }
1179                    }
1180                }
1181            }
1182            
1183            // add the assertion to idTimeMap
1184            if (debug.messageEnabled()) {
1185                debug.message("Adding " + aIDString + " to idTimeMap.");
1186            }
1187            Conditions conds = assertion.getConditions();
1188            if ((conds != null) && ((date = conds.getNotOnorAfter()) != null)) {
1189                cGoThrough.addElement(aIDString);
1190                idTimeMap.put(aIDString, new Long(date.getTime()));
1191            } else {
1192                cPeriodic.addElement(aIDString);
1193                // it doesn't matter what we store for the value.
1194                idTimeMap.put(aIDString, aIDString);
1195            }
1196        }
1197        
1198        // must have at least one SSO assertion
1199        if ((subject == null) || (srcSite == null)) {
1200            debug.error("verifyAssertion AndGetSSMap: couldn't find Subject.");
1201            return null;
1202        }
1203        Map ssMap = new HashMap();
1204        ssMap.put(SAMLConstants.SUBJECT, subject);
1205        ssMap.put(SAMLConstants.SOURCE_SITE_SOAP_ENTRY, srcSite);
1206        ssMap.put(SAMLConstants.POST_ASSERTION, assertions);
1207        return ssMap;
1208    }
1209    
1210    /**
1211     * Checks if the Assertion is time valid and
1212     * if the Assertion is allowed by AudienceRestrictionCondition.
1213     *
1214     * @param assertion an Assertion object
1215     * @return true if the operation is successful otherwise, return false
1216     * @exception IOException IOException
1217     */
1218    private static boolean checkCondition(Assertion assertion)
1219        throws IOException
1220    {
1221        if (assertion == null) {
1222            return false;
1223        }
1224        if (!assertion.isSignatureValid()) {
1225            debug.error(bundle.getString("assertionSignatureNotValid"));
1226            return false;
1227        }
1228        // check if the Assertion is time valid
1229        if (!(assertion.isTimeValid())) {
1230            debug.error(bundle.getString("assertionTimeNotValid"));
1231            return false;
1232        }
1233        // check the Assertion is allowed by AudienceRestrictionCondition
1234        Conditions cnds = assertion.getConditions();
1235        Set audienceCnd = new HashSet();
1236        audienceCnd = cnds.getAudienceRestrictionCondition();
1237        Iterator it = null;
1238        if (audienceCnd != null) {
1239            if (!audienceCnd.isEmpty()) {
1240                it = audienceCnd.iterator();
1241                while (it.hasNext()) {
1242                    if ((((AudienceRestrictionCondition) it.next()).
1243                    evaluate()) == Condition.INDETERMINATE ) {
1244                        if (debug.messageEnabled()) {
1245                            debug.message("Audience " +
1246                            "RestrictionConditions is indeterminate.");
1247                        }
1248                    } else {
1249                        debug.error("Failed AudienceRestrictionCondition");
1250                        return false;
1251                    }
1252                }
1253            }
1254        }
1255        return true;
1256    }
1257    
1258    /**
1259     * Determines if there is a valid SSO Assertion
1260     * inside of SAML Response.
1261     *
1262     * @param assertions a List of <code>Assertion</code> objects
1263     * @return a Subject object
1264     * @exception IOException IOException
1265     */
1266    public static com.sun.identity.saml.assertion.Subject examAssertions(
1267    List assertions) throws IOException {
1268        if (assertions == null) {
1269            return null;
1270        }
1271        boolean validation = false;
1272        com.sun.identity.saml.assertion.Subject subject = null;
1273        Iterator iter = assertions.iterator();
1274        
1275        while (iter.hasNext()) {
1276            Assertion assertion = (Assertion)iter.next();
1277            
1278            if (!checkCondition(assertion)) {
1279                return null;
1280            }
1281            debug.message("Passed checking Conditions!");
1282            
1283            // exam the Statement inside the Assertion
1284            Set statements = new HashSet();
1285            statements = assertion.getStatement();
1286            
1287            if (statements == null || statements.isEmpty()) {
1288                debug.error(bundle.getString("noStatement"));
1289                return null;
1290            }
1291            Iterator iterator = statements.iterator();
1292            while (iterator.hasNext()) {
1293                Statement statement = (Statement) iterator.next();
1294                subject = ((SubjectStatement)statement).getSubject();
1295                SubjectConfirmation sc = subject.getSubjectConfirmation();
1296                Set cm = new HashSet();
1297                cm =  sc.getConfirmationMethod();
1298                if (cm == null || cm.isEmpty()) {
1299                    debug.error("Subject confirmation method is null");
1300                    return null;
1301                }
1302                String conMethod = (String) cm.iterator().next();
1303                // add checking artifact confirmation method identifier based
1304                // on Assertion version number
1305                if ((conMethod != null) && (assertion.getMajorVersion() ==
1306                SAMLConstants.ASSERTION_MAJOR_VERSION) &&
1307                (((assertion.getMinorVersion() ==
1308                SAMLConstants.ASSERTION_MINOR_VERSION_ONE) &&
1309                conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_ARTIFACT))
1310                ||
1311                ((assertion.getMinorVersion() ==
1312                SAMLConstants.ASSERTION_MINOR_VERSION_ZERO) &&
1313                (conMethod.equals(
1314                SAMLConstants.DEPRECATED_CONFIRMATION_METHOD_ARTIFACT))))) {
1315                    if (debug.messageEnabled()) {
1316                        debug.message("Correct Confirmation method");
1317                    }
1318                } else {
1319                    debug.error("Wrong Confirmation Method.");
1320                    return null;
1321                }
1322                if (statement instanceof AuthenticationStatement) {
1323                    //found an SSO Assertion
1324                    validation = true;
1325                }
1326            }  // end of  while (iterator.hasNext()) for Statements
1327        } // end of while (iter.hasNext()) for Assertions
1328        
1329        if (!validation) {
1330            debug.error(bundle.getString("noSSOAssertion"));
1331            return null;
1332        }
1333        return subject;
1334    }
1335
1336    /**
1337     * Return whether the signature on the object is valid or not.
1338     * @param xmlString input XML String 
1339     * @param idAttribute ASSERTION_ID_ATTRIBUTE or RESPONSE_ID_ATTRIBUTE
1340     * @param issuer the issuer of the Assertion 
1341     * @return true if the signature on the object is valid; false otherwise.
1342     */
1343    public static boolean checkSignatureValid(String xmlString, 
1344                                                  String idAttribute, 
1345                                                  String issuer)
1346    {
1347            String certAlias = null;
1348            boolean valid = true; 
1349            Map entries = (Map) SAMLServiceManager.getAttribute(
1350                                SAMLConstants.PARTNER_URLS);
1351        if (entries != null) {
1352            SAMLServiceManager.SOAPEntry srcSite =
1353                (SAMLServiceManager.SOAPEntry) entries.get(issuer);
1354            if (srcSite != null) {
1355                certAlias = srcSite.getCertAlias();
1356            }
1357        }
1358      
1359        try {
1360            SAMLUtils.debug.message("SAMLUtils.checkSignatureValid for certAlias {}", certAlias);
1361            XMLSignatureManager manager = XMLSignatureManager.getInstance();
1362            valid = manager.verifyXMLSignature(xmlString, 
1363                                   idAttribute, certAlias);
1364        } catch (Exception e) {
1365            SAMLUtils.debug.warning("SAMLUtils.checkSignatureValid:"+
1366                                " signature validation exception", e);
1367            valid = false;
1368        }
1369        if (!valid) {
1370            if (SAMLUtils.debug.messageEnabled()) {
1371                SAMLUtils.debug.message("SAMLUtils.checkSignatureValid:"+
1372                                        " Couldn't verify signature.");
1373            }
1374        }
1375        return valid;
1376    }
1377
1378    /**
1379     * Sets the given <code>HttpServletResponse</code> object with the
1380     * headers in the given <code>MimeHeaders</code> object.
1381     * @param headers the <code>MimeHeaders</code> object
1382     * @param response the <code>HttpServletResponse</code> object to which the
1383     *        headers are to be written.
1384     */
1385    public static void setMimeHeaders(
1386         MimeHeaders headers, HttpServletResponse response) {
1387        
1388         if(headers == null || response == null) {
1389            debug.message("SAMLUtils.setMimeHeaders : null input");
1390            return;
1391         }
1392
1393         for (Iterator iter = headers.getAllHeaders(); iter.hasNext();){
1394              MimeHeader header = (MimeHeader)iter.next();
1395
1396              String[] values = headers.getHeader(header.getName());
1397              if (values.length == 1) {
1398                  response.setHeader(header.getName(), header.getValue());
1399              } else {
1400                  StringBuffer concat = new StringBuffer();
1401                  int i = 0;
1402                  while (i < values.length) {
1403                      if (i != 0) {
1404                          concat.append(',');
1405                      }
1406                      concat.append(values[i++]);
1407                   }
1408                   response.setHeader(header.getName(),concat.toString());
1409              }
1410
1411         }
1412         return; 
1413    }
1414
1415    /**
1416     * Returns a <code>MimeHeaders</code> object that contains the headers
1417     * in the given <code>HttpServletRequest</code> object.
1418     *
1419     * @param req the <code>HttpServletRequest</code> object.
1420     * @return a new <code>MimeHeaders</code> object containing the headers.
1421     */
1422    public static MimeHeaders getMimeHeaders(HttpServletRequest req) {
1423
1424         MimeHeaders headers = new MimeHeaders();
1425
1426         if(req == null) {
1427            debug.message("SAMLUtils.getMimeHeaders: null input");
1428            return headers;
1429         }
1430
1431         Enumeration enumerator = req.getHeaderNames();
1432
1433         while(enumerator.hasMoreElements()) {
1434             String headerName = (String)enumerator.nextElement();
1435             String headerValue = req.getHeader(headerName);
1436
1437             StringTokenizer values = new StringTokenizer(headerValue, ",");
1438             while(values.hasMoreTokens()) {
1439                 headers.addHeader(headerName, values.nextToken().trim());
1440             }
1441         }
1442
1443         return headers; 
1444    }
1445     
1446    /**
1447     * Returns the authenticaion login url with goto parameter
1448     * in the given <code>HttpServletRequest</code> object.
1449     *
1450     * @param req the <code>HttpServletRequest</code> object.
1451     * @return a new authenticaion login url with goto parameter.
1452     */
1453    public static String getLoginRedirectURL(HttpServletRequest req) {
1454        String qs = req.getQueryString();
1455        String gotoUrl = req.getRequestURL().toString();
1456        String key = null; 
1457        if (qs != null && qs.length() > 0) {
1458            gotoUrl = gotoUrl + "?" + qs;
1459            int startIdx = -1;
1460            int endIdx = -1; 
1461            StringBuffer result = new StringBuffer(); 
1462            if ((startIdx = qs.indexOf((String) SAMLServiceManager.
1463                getAttribute(SAMLConstants.TARGET_SPECIFIER))) > 0) {
1464                result.append(qs.substring(0, startIdx - 1)); 
1465            }    
1466            if ((endIdx = qs.indexOf("&", startIdx)) != -1) {
1467                if (startIdx == 0) {
1468                    result.append(qs.substring(endIdx + 1)); 
1469                } else {
1470                    result.append(qs.substring(endIdx));
1471                }    
1472            } 
1473            key = result.toString();
1474        }
1475
1476        String reqUrl = req.getScheme() + "://" + req.getServerName() + ":" +
1477            req.getServerPort() + req.getContextPath();
1478        String redirectUrl = null;
1479        if (key == null || key.equals("")) {
1480            redirectUrl = reqUrl + "/UI/Login?goto=" + urlEncodeQueryParameterNameOrValue(gotoUrl);
1481        } else {
1482            redirectUrl = reqUrl +"/UI/Login?" + key + "&goto=" + urlEncodeQueryParameterNameOrValue(gotoUrl);
1483        }
1484        if (SAMLUtils.debug.messageEnabled()) {
1485            SAMLUtils.debug.message("Redirect to auth login via:" +
1486                redirectUrl);
1487        }    
1488        return redirectUrl; 
1489    }    
1490    
1491      
1492    /** 
1493     * Processes SAML Artifact
1494     * @param artifact SAML Artifact
1495     * @param target Target URL 
1496     * @return Attribute Map
1497     * @exception SAMLException if failed to get the Assertions or
1498     *     Attribute Map.
1499     */
1500    public static Map processArtifact(String[] artifact, String target) 
1501        throws SAMLException {
1502        List assts = null;  
1503        Subject assertionSubject = null; 
1504        AssertionArtifact firstArtifact = null;  
1505        Map sessMap = null; 
1506        // Call SAMLClient to do the Single-sign-on
1507        try {
1508            assts = SAMLClient.artifactQueryHandler(artifact, (String) null);
1509            //exam the SAML response
1510            if ((assertionSubject = examAssertions(assts)) == null) {
1511                return null; 
1512            }
1513            firstArtifact = new AssertionArtifact(artifact[0]);
1514            String sid = firstArtifact.getSourceID();
1515            Map partner = (Map) SAMLServiceManager.getAttribute(
1516                SAMLConstants.PARTNER_URLS);
1517            if (partner == null) {
1518                throw new SAMLException(bundle.getString
1519                    ("nullPartnerUrl"));
1520            }
1521            SAMLServiceManager.SOAPEntry partnerdest = 
1522                (SAMLServiceManager.SOAPEntry) partner.get(sid);
1523            if (partnerdest == null) {
1524                throw new SAMLException(bundle.getString
1525                    ("failedAccountMapping"));
1526            }
1527            sessMap = getAttributeMap(partnerdest, assts,
1528                assertionSubject, target); 
1529        } catch (Exception se) {
1530            debug.error("SAMLUtils.processArtifact :" , se);
1531            throw new SAMLException(
1532                bundle.getString("failProcessArtifact"));
1533        }    
1534        return sessMap;   
1535    }
1536    
1537    /**
1538     * Creates Session 
1539     * @param request HttpServletRequest
1540     * @param response HttpServletResponse
1541     * @param attrMap Attribute Map 
1542     * @exception if failed to create Session
1543     */
1544    public static Object generateSession(HttpServletRequest request,
1545        HttpServletResponse response, 
1546        Map attrMap) throws SAMLException {  
1547        Map sessionInfoMap = new HashMap();
1548        String realm = (String) attrMap.get(SessionProvider.REALM);
1549        if ((realm == null) || (realm.length() == 0)) {
1550            realm = "/";
1551        } 
1552        sessionInfoMap.put(SessionProvider.REALM, realm);
1553        String principalName = 
1554            (String) attrMap.get(SessionProvider.PRINCIPAL_NAME);
1555        if (principalName == null) {
1556            principalName = (String) attrMap.get(SAMLConstants.USER_NAME);
1557        }
1558        sessionInfoMap.put(SessionProvider.PRINCIPAL_NAME, principalName);
1559        //TODO: sessionInfoMap.put(SessionProvider.AUTH_LEVEL, "0");
1560        Object session = null;  
1561        try {  
1562            SessionProvider sessionProvider = SessionManager.getProvider();
1563            session = sessionProvider.createSession(
1564                sessionInfoMap, request, response, null);
1565            setAttrMapInSession(sessionProvider, attrMap, session);
1566        } catch (SessionException se) {
1567            if (debug.messageEnabled()) {
1568                debug.message("SAMLUtils.generateSession:", se);
1569            }
1570            throw new SAMLException(se);
1571        }
1572        return session;
1573    }
1574    
1575    /**
1576     * Processes SAML Response
1577     * @param samlResponse SAML Response object
1578     * @param target Target URL 
1579     * @return Attribute Map
1580     * @exception SAMLException if failed to get Attribute Map.
1581     */
1582    public static Map processResponse(Response samlResponse, String target) 
1583        throws SAMLException {
1584        List assertions = null;    
1585        SAMLServiceManager.SOAPEntry partnerdest = null;
1586        Subject assertionSubject = null;
1587        if (samlResponse.isSigned()) {
1588            // verify the signature
1589            boolean isSignedandValid = verifySignature(samlResponse);
1590            if (!isSignedandValid) {
1591                throw new SAMLException(bundle.getString("invalidResponse"));
1592            }
1593        }
1594        // check Assertion and get back a Map of relevant data including,
1595        // Subject, SOAPEntry for the partner and the List of Assertions.
1596        Map ssMap = verifyAssertionAndGetSSMap(samlResponse);
1597        if (debug.messageEnabled()) {
1598            debug.message("processResponse: ssMap = " + ssMap);
1599        }
1600        
1601        if (ssMap == null) {
1602            throw new SAMLException(bundle.getString("invalidAssertion"));
1603        }
1604        assertionSubject = (com.sun.identity.saml.assertion.Subject)
1605            ssMap.get(SAMLConstants.SUBJECT);
1606        if (assertionSubject == null) {
1607            throw new SAMLException(bundle.getString("nullSubject"));
1608        }
1609        
1610        partnerdest = (SAMLServiceManager.SOAPEntry)ssMap
1611            .get(SAMLConstants.SOURCE_SITE_SOAP_ENTRY);
1612        if (partnerdest == null) {
1613            throw new SAMLException(bundle.getString("failedAccountMapping"));
1614        }
1615        
1616        assertions = (List)ssMap.get(SAMLConstants.POST_ASSERTION);
1617        Map sessMap = null;
1618        try { 
1619            sessMap = getAttributeMap(partnerdest, assertions,
1620                assertionSubject, target); 
1621        } catch (Exception se) {
1622            debug.error("SAMLUtils.processResponse :" , se);
1623            throw new SAMLException(
1624                bundle.getString("failProcessResponse"));
1625        }
1626        return sessMap;
1627    }
1628    
1629    /**
1630     *Sets the attribute map in the session
1631     *
1632     *@param attrMap, the Attribute Map
1633     *@param session, the valid session object
1634     *@exception SessionException if failed to set Attribute in the 
1635     *    Session.  
1636     */
1637    private static void setAttrMapInSession(
1638        SessionProvider sessionProvider,
1639        Map attrMap, Object session)
1640        throws SessionException {
1641        if (attrMap != null && !attrMap.isEmpty()) {
1642            Set entrySet = attrMap.entrySet();
1643            for(Iterator iter = entrySet.iterator(); iter.hasNext();) {
1644                Map.Entry entry = (Map.Entry)iter.next();
1645                String attrName = (String)entry.getKey();
1646                String[] attrValues = null;
1647                if (attrName.equals(SAMLConstants.USER_NAME) ||
1648                    attrName.equals(SessionProvider.PRINCIPAL_NAME)) {
1649                    String attrValue = (String)entry.getValue();
1650                    attrValues = new String[1];
1651                    attrValues[0] = attrValue;
1652                } else if (attrName.equals(SessionProvider.REALM) ||
1653                    attrName.equals(SessionProvider.AUTH_LEVEL)) {
1654                    // ignore
1655                    continue; 
1656                } else {
1657                    attrValues = (String[])entry.getValue();
1658                }
1659                sessionProvider.setProperty(session, attrName, attrValues);
1660                if (debug.messageEnabled()) {
1661                    debug.message("SAMLUtils.setAttrMapInSessioin: attrName ="+
1662                        attrName);
1663                }
1664            }
1665        } 
1666    }
1667
1668    /**
1669     * Compares two URLs to see if they are equal. Two URLs are equal if
1670     * they have same protocol, host, port and path (case ignored).
1671     * Note : the method is provided to avoid URL.equals() call which requires
1672     * name lookup. Name lookup is a blocking operation and very expensive
1673     * if the hostname could not be resolved.
1674     *
1675     * @return true if the URLs are equal, false otherwise.
1676     */
1677    private static boolean equalURL(String url1, String url2) {
1678        try {
1679            URL u1 = new URL(url1);
1680            URL u2 = new URL(url2);
1681            int port1 = u1.getPort();
1682            if (port1 == -1) {
1683                port1 = u1.getDefaultPort();
1684            }
1685            int port2 = u2.getPort();
1686            if (port2 == -1) {
1687                port2 = u2.getDefaultPort();
1688            }
1689            if ((u1.getProtocol().equalsIgnoreCase(u2.getProtocol())) &&
1690                (u1.getHost().equalsIgnoreCase(u2.getHost())) &&
1691                (port1 == port2) &&
1692                (u1.getPath().equalsIgnoreCase(u2.getPath()))) {
1693                return true;
1694            } else {
1695                return false;
1696            }
1697        } catch (MalformedURLException m) {
1698            debug.message("Error in SAMLUtils.equalURL", m);
1699            return false;
1700        }
1701    }
1702
1703      /**
1704       * Gets input Node Canonicalized
1705       *
1706       * @param node Node
1707       * @return Canonical element if the operation succeeded.
1708       *     Otherwise, return null.
1709       */
1710      public static Element getCanonicalElement(Node node) {
1711          try {
1712              Canonicalizer c14n = Canonicalizer.getInstance(
1713                  "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
1714              byte outputBytes[] = c14n.canonicalizeSubtree(node);
1715              DocumentBuilder documentBuilder = 
1716                 XMLUtils.getSafeDocumentBuilder(false);
1717              Document doc = documentBuilder.parse(
1718                  new ByteArrayInputStream(outputBytes));
1719              Element result = doc.getDocumentElement();
1720              return result;
1721          } catch (Exception e) {
1722              SAMLUtils.debug.error("Response:getCanonicalElement: " +
1723                  "Error while performing canonicalization on " +
1724                  "the input Node.");
1725              return null;
1726          }
1727      }
1728      
1729     /**
1730      * Sends to error page URL for SAML protocols. If the error page is
1731      * hosted in the same web application, forward is used with
1732      * parameters. Otherwise, redirection or HTTP POST is used with
1733      * parameters.
1734      * Three parameters are passed to the error URL:
1735      *  -- errorcode : Error key, this is the I18n key of the error message.
1736      *  -- httpstatuscode : Http status code for the error
1737      *  -- message : detailed I18n'd error message
1738      * @param request HttpServletRequest object
1739      * @param response HttpServletResponse object
1740      * @param httpStatusCode Http Status code
1741      * @param errorCode Error code
1742      * @param errorMsg Detailed error message
1743      */
1744     public static void sendError(HttpServletRequest request,
1745         HttpServletResponse response, int httpStatusCode,
1746         String errorCode, String errorMsg) {
1747                 String errorUrl = SystemConfigurationUtil.getProperty(
1748               SAMLConstants.ERROR_PAGE_URL,
1749               SAMLConstants.DEFAULT_ERROR_PAGE_URL);
1750         if(debug.messageEnabled()) {
1751            debug.message("SAMLUtils.sendError: error page" + errorUrl);
1752         }
1753         String tmp = errorUrl.toLowerCase();
1754         if (!tmp.startsWith("http://") && !tmp.startsWith("https://")) {
1755             // use forward
1756             String jointString = "?";
1757             if (errorUrl.indexOf("?") != -1) {
1758                 jointString = "&";
1759             }
1760             String newUrl = errorUrl.trim() + jointString
1761                  + SAMLConstants.ERROR_CODE + "=" + errorCode + "&"
1762                  + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode
1763                  + "&" + SAMLConstants.ERROR_MESSAGE + "="
1764                  + urlEncodeQueryParameterNameOrValue(errorMsg);
1765
1766             forwardRequest(newUrl, request, response);
1767         } else {
1768           String binding = SystemConfigurationUtil.getProperty(
1769                            SAMLConstants.ERROR_PAGE_HTTP_BINDING,
1770                            SAMLConstants.HTTP_POST);
1771           if(SAMLConstants.HTTP_REDIRECT.equals(binding)) {
1772               // use FSUtils, this may be redirection or forward
1773              String jointString = "?";
1774              if (errorUrl.indexOf("?") != -1) {
1775                  jointString = "&";
1776              }
1777              String newUrl = errorUrl.trim() + jointString
1778                   + SAMLConstants.ERROR_CODE + "=" + errorCode + "&"
1779                   + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode
1780                   + "&" + SAMLConstants.ERROR_MESSAGE + "="
1781                   + urlEncodeQueryParameterNameOrValue(errorMsg);
1782
1783              FSUtils.forwardRequest(request, response, newUrl) ;
1784           } else {
1785               // Populate request attributes to be available for rendering.
1786               request.setAttribute("ERROR_URL", errorUrl);
1787               request.setAttribute("ERROR_CODE_NAME", SAMLConstants.ERROR_CODE);
1788               request.setAttribute("ERROR_CODE", errorCode);
1789               request.setAttribute("ERROR_MESSAGE_NAME", SAMLConstants.ERROR_MESSAGE);
1790               request.setAttribute("ERROR_MESSAGE", urlEncodeQueryParameterNameOrValue(errorMsg));
1791               request.setAttribute("HTTP_STATUS_CODE_NAME", SAMLConstants.HTTP_STATUS_CODE);
1792               request.setAttribute("HTTP_STATUS_CODE", httpStatusCode);
1793               request.setAttribute("SAML_ERROR_KEY", bundle.getString("samlErrorKey"));
1794               // Forward to auto-submitting form.
1795               forwardRequest(ERROR_JSP, request, response);
1796           }
1797         }
1798     }
1799
1800    /**
1801     * Forwards to the passed URL.
1802     *
1803     * @param url
1804     *         Forward URL
1805     * @param request
1806     *         Request object
1807     * @param response
1808     *         Response object
1809     */
1810    private static void forwardRequest(String url, HttpServletRequest request, HttpServletResponse response) {
1811        try {
1812            request.getRequestDispatcher(url).forward(request, response);
1813
1814        } catch (ServletException sE) {
1815            handleForwardError(url, sE, response);
1816        } catch (IOException ioE) {
1817            handleForwardError(url, ioE, response);
1818        }
1819    }
1820
1821    /**
1822     * Handle any forward error.
1823     *
1824     * @param url
1825     *         Attempted forward URL
1826     * @param exception
1827     *         Caught exception
1828     * @param response
1829     *         Response object
1830     */
1831    private static void handleForwardError(String url, Exception exception, HttpServletResponse response) {
1832        debug.error("SAMLUtils.sendError: Exception occurred while trying to forward to resource: " + url, exception);
1833
1834        try {
1835            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, exception.getMessage());
1836        } catch (IOException ioE) {
1837            debug.error("Failed to inform the response of caught exception", ioE);
1838        }
1839    }
1840
1841}