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