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




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.