001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016package org.forgerock.openig.handler.saml;
017
018import static java.lang.String.*;
019
020import java.io.File;
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Properties;
029
030import javax.servlet.ServletException;
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletResponse;
033
034import org.forgerock.json.fluent.JsonValue;
035import org.forgerock.openig.config.Environment;
036import org.forgerock.openig.handler.GenericHandler;
037import org.forgerock.openig.handler.HandlerException;
038import org.forgerock.openig.header.LocationHeader;
039import org.forgerock.openig.heap.GenericHeaplet;
040import org.forgerock.openig.heap.HeapException;
041import org.forgerock.openig.http.Exchange;
042import org.forgerock.openig.http.Form;
043import org.forgerock.openig.http.Request;
044import org.forgerock.openig.http.Response;
045import org.forgerock.openig.http.Session;
046
047import com.sun.identity.common.ShutdownManager;
048import com.sun.identity.plugin.session.SessionException;
049import com.sun.identity.saml2.assertion.Assertion;
050import com.sun.identity.saml2.assertion.AuthnStatement;
051import com.sun.identity.saml2.assertion.Subject;
052import com.sun.identity.saml2.common.SAML2Constants;
053import com.sun.identity.saml2.common.SAML2Exception;
054import com.sun.identity.saml2.common.SAML2Utils;
055import com.sun.identity.saml2.jaxb.entityconfig.SPSSOConfigElement;
056import com.sun.identity.saml2.meta.SAML2MetaManager;
057import com.sun.identity.saml2.profile.CacheObject;
058import com.sun.identity.saml2.profile.LogoutUtil;
059import com.sun.identity.saml2.profile.SPACSUtils;
060import com.sun.identity.saml2.profile.SPCache;
061import com.sun.identity.saml2.profile.SPSSOFederate;
062import com.sun.identity.saml2.profile.SPSingleLogout;
063import com.sun.identity.saml2.servlet.SPSingleLogoutServiceSOAP;
064
065/**
066 * The SAML federation handler.
067 */
068public class SamlFederationHandler extends GenericHandler {
069
070    /** Default Realm is always / in this case. */
071    private static final String DEFAULT_REALM = "/";
072
073    /** The attribute mapping. */
074    private final Map<String, String> attributeMapping;
075
076    /** The value contained in the assertion subject is set as the value of the attribute subjectName in the session. */
077    private final String subjectMapping;
078
079    /** The delimiter to use when there are multiple contexts in the assertion. */
080    private final String authnContextDelimiter;
081
082    /** The name to use when placing context values into the session. */
083    private final String authnContext;
084
085    private final String sessionIndexMapping;
086
087    private final String redirectURI;
088
089    private final String logoutURI;
090
091    private final String assertionConsumerEndpoint;
092
093    private final String sPinitiatedSSOEndpoint;
094
095    private final String singleLogoutEndpoint;
096
097    /** IDP Single Logout SOAP Endpoint. */
098    private final String singleLogoutEndpointSoap;
099
100    /** SP Single Logout Endpoint. */
101    private final String sPinitiatedSLOEndpoint;
102
103    /**
104     * Constructs a federation handler according to the specified parameters.
105     *
106     * @param attributeMapping
107     *            The attribute mapping.
108     * @param subjectMapping
109     *            The value contained in the assertion subject is set as the value of the attribute subjectName in the
110     *            session.
111     * @param authnContextDelimiter
112     *            The delimiter to use when there are multiple contexts in the assertion.
113     * @param authnContext
114     *            The name to use when placing context values into the session.
115     * @param sessionIndexMapping
116     *            The IDP's sessionIndex for the user is sent in the assertion.
117     * @param redirectURI
118     *            The redirectURI should be set to the page the Form-Filter recognizes as the login page for the target
119     *            application.
120     * @param logoutURI
121     *            The logoutURI should be set to the URI which logs the user out of the target application.
122     * @param assertionConsumerEndpoint
123     *            The assertionMapping defines how to transform the attributes from the incoming assertion to attribute
124     *            value pairs in the session.
125     * @param sPinitiatedSSOEndpoint
126     *            The default value is SPInitiatedSSO.
127     * @param singleLogoutEndpoint
128     *            The default value of fedletSLO is the same as the Fedlet.
129     * @param singleLogoutEndpointSoap
130     *            IDP Single Logout SOAP Endpoint.
131     * @param sPinitiatedSLOEndpoint
132     *            SP Single Logout Endpoint.
133     */
134    SamlFederationHandler(Map<String, String> attributeMapping,
135                          String subjectMapping,
136                          String authnContextDelimiter,
137                          String authnContext,
138                          String sessionIndexMapping,
139                          String redirectURI,
140                          String logoutURI,
141                          String assertionConsumerEndpoint,
142                          String sPinitiatedSSOEndpoint,
143                          String singleLogoutEndpoint,
144                          String singleLogoutEndpointSoap,
145                          String sPinitiatedSLOEndpoint) {
146        this.attributeMapping = Collections.unmodifiableMap(attributeMapping);
147        this.subjectMapping = subjectMapping;
148        this.authnContextDelimiter = authnContextDelimiter;
149        this.authnContext = authnContext;
150        this.sessionIndexMapping = sessionIndexMapping;
151        this.redirectURI = redirectURI;
152        this.logoutURI = logoutURI;
153        this.assertionConsumerEndpoint = assertionConsumerEndpoint;
154        this.sPinitiatedSSOEndpoint = sPinitiatedSSOEndpoint;
155        this.singleLogoutEndpoint = singleLogoutEndpoint;
156        this.singleLogoutEndpointSoap = singleLogoutEndpointSoap;
157        this.sPinitiatedSLOEndpoint = sPinitiatedSLOEndpoint;
158    }
159
160    @Override
161    public void handle(Exchange exchange) throws HandlerException, IOException {
162        HttpServletRequest request = adaptRequest(exchange);
163        HttpServletResponse response = adaptResponse(exchange);
164        try {
165            String path = exchange.request.getUri().getPath();
166            if (path.indexOf(assertionConsumerEndpoint) > 0) {
167                serviceAssertionConsumer(exchange, request, response);
168            } else if (path.indexOf(sPinitiatedSSOEndpoint) > 0) {
169                serviceSPInitiatedSSO(exchange, request, response);
170            } else if (path.indexOf(sPinitiatedSLOEndpoint) > 0) {
171                serviceSPInitiatedSLO(exchange, request, response);
172            } else if (path.indexOf(singleLogoutEndpointSoap) > 0) {
173                serviceIDPInitiatedSLOSOAP(request, response);
174            } else if (path.indexOf(singleLogoutEndpoint) > 0) {
175                serviceIDPInitiatedSLO(exchange, request, response);
176            } else {
177                logger.warning(format("FederationServlet warning: URI not in service %s", exchange.request.getUri()));
178            }
179        } catch (ServletException se) {
180            errorResponse(exchange, response, se.getMessage());
181        } catch (SAML2Exception sme) {
182            errorResponse(exchange, response, sme.getMessage());
183        } catch (SessionException se) {
184            errorResponse(exchange, response, se.getMessage());
185        }
186    }
187
188    /**
189     * Whether IDP or SP initiated, the final request ends up here.
190     * <p>
191     * The assertion is validated, attributes are retrieved from and set in the HttpSession where downstream filters
192     * can access them and pass them on to the target application.
193     */
194    private void serviceAssertionConsumer(Exchange exchange,
195                                          HttpServletRequest request,
196                                          HttpServletResponse response) throws IOException,
197                                                                               ServletException,
198                                                                               SAML2Exception,
199                                                                               SessionException {
200        Map<?, ?> map = SPACSUtils.processResponseForFedlet(request, response);
201        addAttributesToSession(exchange.session, map);
202        /*
203         * Redirect back to the original target application's login page and let the filters take over. If the relayURI
204         * is set in the assertion we must use that, otherwise we will use the configured value, which should be the
205         * login page for the target application.
206         */
207        String relayURI = (String) map.get(SAML2Constants.RELAY_STATE);
208        String uri = isRelayURIProvided(relayURI) ? relayURI : redirectURI;
209        sendRedirect(exchange, uri);
210    }
211
212    private boolean isRelayURIProvided(String relayURI) {
213        return relayURI != null && !relayURI.isEmpty();
214    }
215
216    /**
217     * Store attribute value pairs in the session based on the assertionMapping found in config file.
218     * <p>
219     * The intent is to have a filter use one of these attributes as the subject and possibly the password. The
220     * presence of these attributes in the Session implies the assertion has been processed and validated.
221     *
222     * @param session
223     *            exchange's {@link Session}
224     * @param assertion
225     *            SAML assertion content
226     */
227    private void addAttributesToSession(final Session session, Map<?, ?> assertion) {
228        Map<?, ?> attributeStatement = (Map<?, ?>) assertion.get(SAML2Constants.ATTRIBUTE_MAP);
229        if (attributeStatement != null) {
230            for (String key : attributeMapping.keySet()) {
231                HashSet<?> t = (HashSet<?>) attributeStatement.get(attributeMapping.get(key));
232                if (t != null) {
233                    String sessionValue = (String) t.iterator().next();
234                    session.put(key, sessionValue);
235                } else {
236                    logger.warning(format("FederationServlet: Warning no assertion attribute found for : %s",
237                                          attributeMapping.get(key)));
238                }
239            }
240        } else {
241            logger.warning("FederationServlet attribute statement was not present in assertion");
242        }
243        if (subjectMapping != null) {
244            String subjectValue = ((Subject) assertion.get(SAML2Constants.SUBJECT)).getNameID().getValue();
245            session.put(subjectMapping, subjectValue);
246            logger.debug(format("FederationServlet adding subject to session: %s = %s", subjectMapping,
247                                subjectValue));
248        }
249
250        if (sessionIndexMapping != null) {
251            String sessionIndexValue = (String) assertion.get(SAML2Constants.SESSION_INDEX);
252            session.put(sessionIndexMapping, sessionIndexValue);
253            logger.debug(format("FederationServlet adding session index: %s = %s", sessionIndexMapping,
254                                sessionIndexValue));
255        }
256
257        if (authnContext != null) {
258            @SuppressWarnings("unchecked")
259            List<AuthnStatement> authnStatements = ((Assertion) assertion.get(SAML2Constants.ASSERTION))
260                    .getAuthnStatements();
261            StringBuilder authnContextValues = new StringBuilder();
262            for (AuthnStatement authnStatement : authnStatements) {
263                String authnContextValue = authnStatement.getAuthnContext().getAuthnContextClassRef();
264                if (authnContextValue != null && !authnContextValue.isEmpty()) {
265                    authnContextValues.append(authnContextValue);
266                    authnContextValues.append(authnContextDelimiter);
267                }
268            }
269            if (authnContextValues.length() > 0) {
270                // remove the last delimiter as it is redundant
271                authnContextValues.deleteCharAt(authnContextValues.length() - 1);
272                session.put(authnContext, authnContextValues.toString());
273                logger.debug(format("FederationServlet adding authentication contexts to session: %s = %s",
274                                    authnContext, authnContextValues));
275            }
276        }
277    }
278
279    @SuppressWarnings("unchecked")
280    private static void serviceSPInitiatedSSO(Exchange exchange,
281                                              HttpServletRequest request,
282                                              HttpServletResponse response) throws SAML2Exception {
283        Form form = exchange.request.getForm();
284        String metaAlias = form.getFirst(SAML2Constants.METAALIAS);
285        if (metaAlias == null || metaAlias.length() == 0) {
286            SAML2MetaManager manager = new SAML2MetaManager();
287            List<String> spMetaAliases =
288                    manager.getAllHostedServiceProviderMetaAliases(DEFAULT_REALM);
289            if (spMetaAliases != null && !spMetaAliases.isEmpty()) {
290                metaAlias = spMetaAliases.get(0);
291            }
292        }
293        String idpEntityID = form.getFirst(SAML2Constants.IDPENTITYID);
294        Map<String, List<?>> paramsMap = SAML2Utils.getParamsMap(request);
295        List<String> list = new ArrayList<String>();
296        list.add(SAML2Constants.NAMEID_TRANSIENT_FORMAT);
297
298        // next line testing to see if we can change the name format
299        paramsMap.put(SAML2Constants.NAMEID_POLICY_FORMAT, list);
300
301        // TODO: add option to specify artifact
302        if (paramsMap.get(SAML2Constants.BINDING) == null) {
303            // use POST binding
304            list = new ArrayList<String>();
305            list.add(SAML2Constants.HTTP_POST);
306            paramsMap.put(SAML2Constants.BINDING, list);
307        }
308        if (idpEntityID == null || idpEntityID.length() == 0) {
309            SAML2MetaManager manager = new SAML2MetaManager();
310            List<String> idpEntities = manager.getAllRemoteIdentityProviderEntities(DEFAULT_REALM);
311            if (idpEntities != null && !idpEntities.isEmpty()) {
312                idpEntityID = idpEntities.get(0);
313            }
314        }
315        if (metaAlias == null || idpEntityID == null) {
316            throw new SAML2Exception("No metadata for SP or IDP");
317        }
318        SPSSOFederate.initiateAuthnRequest(request, response, metaAlias, idpEntityID, paramsMap);
319    }
320
321    /**
322     * This implementation is based on the {@literal spSingleLogoutInit.jsp} from OpenAM.
323     * Expects to find the {@literal NameID} and {@literal SessionIndex} in the session, these are stored during the
324     * IDP login process.
325     * <p>
326     * Optional request parameters are:
327     * <ul>
328     *     <li>{@literal RelayState} - the target URL on successful Single Logout.</li>
329     *     <li>{@literal spEntityID} - SP entity ID. When it is missing, first SP from metadata is used.</li>
330     *     <li>{@literal idpEntityID} - IDP entity ID. When it is missing, first IDP from metadata is used.</li>
331     *     <li>{@literal binding} - binding used for this request and when not set it will use the default binding of
332     *     the IDP.</li>
333     * </ul>
334     */
335    @SuppressWarnings("unchecked")
336    private void serviceSPInitiatedSLO(Exchange exchange, HttpServletRequest request, HttpServletResponse response)
337            throws IOException, SAML2Exception {
338        logger.debug("FederationServlet.serviceSPInitiatedSLO entering");
339
340        Session session = exchange.session;
341        // Retrieve these values from the session, if they do not exist then the session has expired
342        // or this is being called before we have authenticated to the IDP
343        String nameID = (String) session.get(subjectMapping);
344        String sessionIndex = (String) session.get(sessionIndexMapping);
345        if (nameID == null || nameID.isEmpty() || sessionIndex == null || sessionIndex.isEmpty()) {
346            throw new SAML2Exception(SAML2Utils.bundle.getString("nullNameID"));
347        }
348
349        Form form = exchange.request.getForm();
350        SAML2MetaManager manager = new SAML2MetaManager();
351        String relayState = form.getFirst(SAML2Constants.RELAY_STATE);
352        String spEntityID = form.getFirst(SAML2Constants.SPENTITYID);
353        String binding = form.getFirst(SAML2Constants.BINDING);
354        String idpEntityID = form.getFirst(SAML2Constants.IDPENTITYID);
355
356        // If the idpEntityID has not been specified then read it from the IDP metadata.
357        if (idpEntityID == null || idpEntityID.isEmpty()) {
358            List<String> idpEntities = manager.getAllRemoteIdentityProviderEntities(DEFAULT_REALM);
359            if (idpEntities != null && !idpEntities.isEmpty()) {
360                // Just take the first one since only one is supported
361                idpEntityID = idpEntities.get(0);
362            }
363        }
364        logger.debug(format("FederationServlet.serviceSPInitiatedSLO idpEntityID: %s", idpEntityID));
365
366
367        String metaAlias = null;
368        // If the spEntityID has not been specified then read it from the SP metadata.
369        if (spEntityID == null || spEntityID.isEmpty()) {
370            List<String> spMetaAliases = manager.getAllHostedServiceProviderMetaAliases(DEFAULT_REALM);
371            if (spMetaAliases != null && !spMetaAliases.isEmpty()) {
372                // Just take the first one since only one is supported
373                metaAlias = spMetaAliases.get(0);
374                spEntityID = manager.getEntityByMetaAlias(metaAlias);
375            }
376        } else {
377            SPSSOConfigElement spConfig = manager.getSPSSOConfig(DEFAULT_REALM, spEntityID);
378            if (spConfig != null) {
379                metaAlias = spConfig.getMetaAlias();
380            }
381        }
382        logger.debug(format("FederationServlet.serviceSPInitiatedSLO metaAlias: %s", metaAlias));
383        logger.debug(format("FederationServlet.serviceSPInitiatedSLO spEntityID: %s", spEntityID));
384
385
386        if (metaAlias == null || idpEntityID == null) {
387            throw new SAML2Exception("No metadata for SP or IDP");
388        }
389
390        // If the binding has not been specified then look up the IDP's default binding.
391        if (binding == null) {
392            binding = LogoutUtil.getSLOBindingInfo(request, metaAlias, SAML2Constants.SP_ROLE, idpEntityID);
393        }
394
395        if (!SAML2Utils.isSPProfileBindingSupported(DEFAULT_REALM, spEntityID, SAML2Constants.SLO_SERVICE, binding)) {
396            logger.error(format("FederationServlet.serviceSPInitiatedSLO unsupported binding: %s", binding));
397            throw new SAML2Exception(SAML2Utils.bundle.getString("unsupportedBinding"));
398        }
399
400        logger.debug(format("FederationServlet.serviceSPInitiatedSLO binding: %s", binding));
401
402        HashMap<String, String> paramsMap = new HashMap<String, String>(7);
403        paramsMap.put(SAML2Constants.INFO_KEY, spEntityID + "|" + idpEntityID + "|" + nameID);
404        paramsMap.put(SAML2Constants.SESSION_INDEX, sessionIndex);
405        paramsMap.put(SAML2Constants.METAALIAS, metaAlias);
406        paramsMap.put(SAML2Constants.IDPENTITYID, idpEntityID);
407        paramsMap.put(SAML2Constants.ROLE, SAML2Constants.SP_ROLE);
408        paramsMap.put(SAML2Constants.BINDING, binding);
409
410        // If the relayState has not been specified try to use the SP default otherwise set it to the logoutURI if set.
411        if (relayState == null || relayState.isEmpty()) {
412            relayState = SAML2Utils.getAttributeValueFromSSOConfig(DEFAULT_REALM, spEntityID, SAML2Constants.SP_ROLE,
413                                                                   SAML2Constants.DEFAULT_RELAY_STATE);
414            if (relayState == null || (relayState.isEmpty() && logoutURI != null && !logoutURI.isEmpty())) {
415                relayState = logoutURI;
416            }
417        }
418        if (relayState != null && !relayState.isEmpty()) {
419            paramsMap.put(SAML2Constants.RELAY_STATE, relayState);
420        }
421
422        logger.debug(format("FederationServlet.serviceSPInitiatedSLO relayState: %s", relayState));
423
424        SPSingleLogout.initiateLogoutRequest(request, response, binding, paramsMap);
425
426        if (SAML2Constants.SOAP.equalsIgnoreCase(binding)) {
427            sendRedirect(exchange, relayState);
428        }
429    }
430
431    private void serviceIDPInitiatedSLO(final Exchange exchange,
432                                        HttpServletRequest request,
433                                        HttpServletResponse response)
434            throws SAML2Exception, SessionException, IOException {
435        logger.debug("FederationServlet.serviceIDPInitiatedSLO entering");
436
437        String relayState = getLogoutRelayState(exchange.request);
438        logger.debug(format("FederationServlet.serviceIDPInitiatedSLO relayState : %s", relayState));
439
440        Form form = exchange.request.getForm();
441        // Check if this is a request as part of an IDP initiated SLO
442        String samlRequest = form.getFirst(SAML2Constants.SAML_REQUEST);
443        if (samlRequest != null) {
444            logger.debug("FederationServlet.serviceIDPInitiatedSLO processing IDP request");
445            SPSingleLogout.processLogoutRequest(request, response, samlRequest, relayState);
446        } else {
447            // Otherwise it might be a response from the IDP as part of a SP initiated SLO
448            String samlResponse = form.getFirst(SAML2Constants.SAML_RESPONSE);
449            if (samlResponse != null) {
450                logger.debug("FederationServlet.serviceIDPInitiatedSLO processing IDP response");
451                SPSingleLogout.processLogoutResponse(request, response, samlResponse, relayState);
452                if (relayState != null) {
453                    sendRedirect(exchange, relayState);
454                }
455            }
456        }
457
458        cleanSession(exchange.session);
459    }
460
461    private void serviceIDPInitiatedSLOSOAP(HttpServletRequest request, HttpServletResponse response)
462            throws IOException, ServletException {
463
464        logger.debug("FederationServlet.serviceIDPInitiatedSLOSOAP entering");
465
466        SPSingleLogoutServiceSOAP spSingleLogoutServiceSOAP = new SPSingleLogoutServiceSOAP();
467        spSingleLogoutServiceSOAP.doPost(request, response);
468    }
469
470    private String getLogoutRelayState(Request request) {
471
472        Form form = request.getForm();
473        String relayState = form.getFirst(SAML2Constants.RELAY_STATE);
474        if (relayState != null) {
475            // Check the SP cache for the actual relayState value as the relayState is
476            // often passed as an ID to the IDP as part of the SP initiated SLO. Based on
477            // code from spSingleLogoutPOST.jsp in OpenAM
478            CacheObject tmpRs = (CacheObject) SPCache.relayStateHash.remove(relayState);
479            if (tmpRs != null) {
480                relayState = (String) tmpRs.getObject();
481            }
482        }
483
484        if (relayState == null || (relayState.isEmpty() && logoutURI != null && !logoutURI.isEmpty())) {
485            relayState = logoutURI;
486        }
487
488        return relayState;
489    }
490
491    /** Clean the session at the end of the SLO process. */
492    private void cleanSession(final Session session) {
493        logger.debug("End of SLO - Processing to session cleanup");
494        session.remove(subjectMapping);
495        session.remove(sessionIndexMapping);
496        session.remove(authnContext);
497
498        if (attributeMapping != null) {
499            for (final String key : attributeMapping.keySet()) {
500                session.remove(key);
501            }
502        }
503    }
504
505    private void errorResponse(Exchange exchange, HttpServletResponse response, String message) throws IOException {
506        final String msg = format("SSO Failed: %s", message);
507        if (!response.isCommitted()) {
508            sendError(exchange, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
509        }
510        logger.error(msg);
511    }
512
513    private static void sendError(Exchange exchange, int status, String message) {
514        exchange.response = new Response();
515        exchange.response.setStatus(status);
516        exchange.response.getEntity().setString(message);
517    }
518
519    private static void sendRedirect(Exchange exchange, String redirectUri) {
520        exchange.response = new Response();
521        // Redirect with a 302 (Found) status code
522        exchange.response.setStatus(HttpServletResponse.SC_FOUND);
523        // Web container was rebasing location header against server URL
524        // Not useful if relayState is already (and always) an absolute URL
525        LocationHeader header = new LocationHeader(redirectUri);
526        header.toMessage(exchange.response);
527    }
528
529    private static HttpServletResponse adaptResponse(Exchange exchange) {
530        return (HttpServletResponse) exchange.get(HttpServletResponse.class.getName());
531    }
532
533    private static HttpServletRequest adaptRequest(Exchange exchange) {
534        HttpServletRequest request = (HttpServletRequest) exchange.get(HttpServletRequest.class.getName());
535        return new RequestAdapter(request, exchange);
536    }
537
538    /**
539     * Reads the actual federation servlet from the JSON configuration file.
540     */
541    public static class Heaplet extends GenericHeaplet {
542        @Override
543        public Object create() throws HeapException {
544            final Map<String, String> attributeMapping = new HashMap<String, String>();
545            JsonValue mappings = config.get("assertionMapping").expect(Map.class);
546            if (mappings != null) {
547                for (String key : mappings.keys()) {
548                    attributeMapping.put(key, mappings.get(key).asString());
549                }
550            }
551            final String authnContextDelimiter = config.get("authnContextDelimiter").defaultTo("|").asString();
552            final String authnContext = config.get("authnContext").asString();
553            final String redirectURI = config.get("redirectURI").asString();
554            final String logoutURI = config.get("logoutURI").asString();
555            // Give subjectMapping and sessionIndexMapping a default value as they are needed when doing SP initiated
556            // SLO
557            final String subjectMapping = config.get("subjectMapping").defaultTo("subjectMapping").asString();
558            final String sessionIndexMapping = config.get("sessionIndexMapping").defaultTo("sessionIndexMapping")
559                    .asString();
560            final String assertionConsumerEndpoint = config.get("assertionConsumerEndpoint")
561                    .defaultTo("fedletapplication").asString();
562            final String sPinitiatedSSOEndpoint = config.get("SPinitiatedSSOEndpoint").defaultTo("SPInitiatedSSO")
563                    .asString();
564            final String singleLogoutEndpoint = config.get("singleLogoutEndpoint").defaultTo("fedletSloRedirect")
565                    .asString();
566            final String singleLogoutEndpointSoap = config.get("singleLogoutEndpointSoap").defaultTo("fedletSloSoap")
567                    .asString();
568            final String sPinitiatedSLOEndpoint = config.get("SPinitiatedSLOEndpoint").defaultTo("SPInitiatedSLO")
569                    .asString();
570            /*
571             * Get the gateway configuration directory and set it as a system property to override the default openFed
572             * location. Federation config files will reside in the SAML directory.
573             */
574            Environment environment = heap.get(Environment.ENVIRONMENT_HEAP_KEY, Environment.class);
575            String samlDirectory = new File(environment.getBaseDirectory(), "SAML").getPath();
576            logger.info(format("FederationServlet init directory: %s", samlDirectory));
577            Properties p = System.getProperties();
578            p.setProperty("com.sun.identity.fedlet.home", samlDirectory);
579            System.setProperties(p);
580
581            return new SamlFederationHandler(attributeMapping,
582                                             subjectMapping,
583                                             authnContextDelimiter,
584                                             authnContext,
585                                             sessionIndexMapping,
586                                             redirectURI,
587                                             logoutURI,
588                                             assertionConsumerEndpoint,
589                                             sPinitiatedSSOEndpoint,
590                                             singleLogoutEndpoint,
591                                             singleLogoutEndpointSoap,
592                                             sPinitiatedSLOEndpoint);
593        }
594
595        @Override
596        public void destroy() {
597            // Automatically shutdown the fedlet
598            ShutdownManager manager = ShutdownManager.getInstance();
599            if (manager.acquireValidLock()) {
600                try {
601                    manager.shutdown();
602                } finally {
603                    manager.releaseLockAndNotify();
604                }
605            }
606            super.destroy();
607        }
608    }
609}