001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nelson Silva <nelson.silva@inevo.pt>
016 */
017package org.nuxeo.ecm.platform.auth.saml;
018
019import org.apache.commons.codec.binary.Base64;
020import org.apache.commons.lang.StringUtils;
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.nuxeo.common.utils.i18n.I18NUtils;
024import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
025import org.nuxeo.ecm.platform.auth.saml.binding.HTTPPostBinding;
026import org.nuxeo.ecm.platform.auth.saml.binding.HTTPRedirectBinding;
027import org.nuxeo.ecm.platform.auth.saml.binding.SAMLBinding;
028import org.nuxeo.ecm.platform.auth.saml.key.KeyManager;
029import org.nuxeo.ecm.platform.auth.saml.slo.SLOProfile;
030import org.nuxeo.ecm.platform.auth.saml.slo.SLOProfileImpl;
031import org.nuxeo.ecm.platform.auth.saml.sso.WebSSOProfile;
032import org.nuxeo.ecm.platform.auth.saml.sso.WebSSOProfileImpl;
033import org.nuxeo.ecm.platform.auth.saml.user.EmailBasedUserResolver;
034import org.nuxeo.ecm.platform.auth.saml.user.UserMapperBasedResolver;
035import org.nuxeo.ecm.platform.auth.saml.user.AbstractUserResolver;
036import org.nuxeo.ecm.platform.auth.saml.user.UserResolver;
037import org.nuxeo.ecm.platform.ui.web.auth.LoginScreenHelper;
038import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants;
039import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
040import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPluginLogoutExtension;
041import org.nuxeo.ecm.platform.ui.web.auth.service.LoginProviderLinkComputer;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.usermapper.service.UserMapperService;
044import org.opensaml.DefaultBootstrap;
045import org.opensaml.common.SAMLException;
046import org.opensaml.common.SAMLObject;
047import org.opensaml.common.binding.BasicSAMLMessageContext;
048import org.opensaml.common.binding.SAMLMessageContext;
049import org.opensaml.common.xml.SAMLConstants;
050import org.opensaml.saml2.core.AuthnRequest;
051import org.opensaml.saml2.core.LogoutRequest;
052import org.opensaml.saml2.core.LogoutResponse;
053import org.opensaml.saml2.core.NameID;
054import org.opensaml.saml2.encryption.Decrypter;
055import org.opensaml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
056import org.opensaml.saml2.metadata.EntityDescriptor;
057import org.opensaml.saml2.metadata.IDPSSODescriptor;
058import org.opensaml.saml2.metadata.RoleDescriptor;
059import org.opensaml.saml2.metadata.SPSSODescriptor;
060import org.opensaml.saml2.metadata.SingleLogoutService;
061import org.opensaml.saml2.metadata.SingleSignOnService;
062import org.opensaml.saml2.metadata.provider.AbstractMetadataProvider;
063import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
064import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
065import org.opensaml.saml2.metadata.provider.MetadataProvider;
066import org.opensaml.saml2.metadata.provider.MetadataProviderException;
067import org.opensaml.security.MetadataCredentialResolver;
068import org.opensaml.util.URLBuilder;
069import org.opensaml.ws.message.decoder.MessageDecodingException;
070import org.opensaml.ws.transport.InTransport;
071import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
072import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
073import org.opensaml.xml.Configuration;
074import org.opensaml.xml.ConfigurationException;
075import org.opensaml.xml.encryption.ChainingEncryptedKeyResolver;
076import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
077import org.opensaml.xml.encryption.SimpleRetrievalMethodEncryptedKeyResolver;
078import org.opensaml.xml.io.Marshaller;
079import org.opensaml.xml.io.MarshallingException;
080import org.opensaml.xml.parse.BasicParserPool;
081import org.opensaml.xml.security.credential.Credential;
082import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
083import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
084import org.opensaml.xml.signature.SignatureTrustEngine;
085import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine;
086import org.opensaml.xml.util.Pair;
087import org.opensaml.xml.util.XMLHelper;
088import org.w3c.dom.Element;
089
090import javax.servlet.ServletException;
091import javax.servlet.http.Cookie;
092import javax.servlet.http.HttpServletRequest;
093import javax.servlet.http.HttpServletResponse;
094import javax.servlet.http.HttpSession;
095
096import java.io.File;
097import java.io.IOException;
098import java.io.StringWriter;
099import java.util.ArrayList;
100import java.util.HashMap;
101import java.util.List;
102import java.util.Map;
103
104import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGIN_ERROR;
105
106/**
107 * A SAML2 authentication provider.
108 *
109 * @since 6.0
110 */
111public class SAMLAuthenticationProvider implements NuxeoAuthenticationPlugin, LoginProviderLinkComputer,
112        NuxeoAuthenticationPluginLogoutExtension {
113
114    private static final Log log = LogFactory.getLog(SAMLAuthenticationProvider.class);
115
116    private static final String ERROR_PAGE = "saml/error.jsp";
117    private static final String ERROR_AUTH = "error.saml.auth";
118    private static final String ERROR_USER = "error.saml.userMapping";
119
120    // User Resolver
121    private static final Class<? extends UserResolver> DEFAULT_USER_RESOLVER_CLASS = EmailBasedUserResolver.class;
122    private static final Class<? extends UserResolver> USERMAPPER_USER_RESOLVER_CLASS = UserMapperBasedResolver.class;
123
124
125    // SAML Constants
126    static final String SAML_SESSION_KEY = "SAML_SESSION";
127
128    // Supported SAML Bindings
129    // TODO: Allow registering new bindings
130    static List<SAMLBinding> bindings = new ArrayList<>();
131    static {
132        bindings.add(new HTTPPostBinding());
133        bindings.add(new HTTPRedirectBinding());
134    }
135
136    // Decryption key resolver
137    private static ChainingEncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver();
138    static {
139        encryptedKeyResolver.getResolverChain().add(new InlineEncryptedKeyResolver());
140        encryptedKeyResolver.getResolverChain().add(new EncryptedElementTypeEncryptedKeyResolver());
141        encryptedKeyResolver.getResolverChain().add(new SimpleRetrievalMethodEncryptedKeyResolver());
142    }
143
144    // Profiles supported by the IdP
145    private Map<String, AbstractSAMLProfile> profiles = new HashMap<>();
146
147    private UserResolver userResolver;
148
149    private KeyManager keyManager;
150
151    private SignatureTrustEngine trustEngine;
152
153    private Decrypter decrypter;
154
155    private MetadataProvider metadataProvider;
156
157    @Override
158    public void initPlugin(Map<String, String> parameters) {
159
160        // Initialize the User Resolver
161        String userResolverClassname = parameters.get("userResolverClass");
162        Class<? extends UserResolver> userResolverClass = null;
163        if (StringUtils.isBlank(userResolverClassname)) {
164            UserMapperService ums = Framework.getService(UserMapperService.class);
165            if (ums!=null) {
166                userResolverClass = USERMAPPER_USER_RESOLVER_CLASS;
167            } else {
168                userResolverClass = DEFAULT_USER_RESOLVER_CLASS;
169            }
170        } else {
171            try {
172                userResolverClass = Class.forName(userResolverClassname).asSubclass(AbstractUserResolver.class);
173            } catch (ClassNotFoundException e) {
174                log.error("Failed get user resolver class " + userResolverClassname);
175            }
176
177        }
178        try {
179            userResolver = userResolverClass.newInstance();
180            userResolver.init(parameters);
181        } catch (InstantiationException | IllegalAccessException e) {
182            log.error("Failed to initialize user resolver " + userResolverClassname);
183        }
184
185        // Initialize the OpenSAML library
186        try {
187            DefaultBootstrap.bootstrap();
188        } catch (ConfigurationException e) {
189            log.error("Failed to bootstrap OpenSAML", e);
190        }
191
192        // Read the IdP metadata and initialize the supported profiles
193        try {
194            // Read the IdP metadata
195            initializeMetadataProvider(parameters);
196
197            // Setup Signature Trust Engine
198            MetadataCredentialResolver metadataCredentialResolver = new MetadataCredentialResolver(metadataProvider);
199            trustEngine = new ExplicitKeySignatureTrustEngine(
200                    metadataCredentialResolver,
201                    org.opensaml.xml.Configuration.getGlobalSecurityConfiguration().getDefaultKeyInfoCredentialResolver());
202
203            // Setup decrypter
204            Credential encryptionCredential = getKeyManager().getEncryptionCredential();
205            if (encryptionCredential != null) {
206                KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(encryptionCredential);
207                decrypter = new Decrypter(null, resolver, encryptedKeyResolver);
208                decrypter.setRootInNewDocument(true);
209            }
210
211            // Process IdP roles
212            for (RoleDescriptor roleDescriptor : getIdPDescriptor().getRoleDescriptors()) {
213
214                // Web SSO role
215                if (roleDescriptor.getElementQName().equals(IDPSSODescriptor.DEFAULT_ELEMENT_NAME)
216                        && roleDescriptor.isSupportedProtocol(org.opensaml.common.xml.SAMLConstants.SAML20P_NS)) {
217
218                    IDPSSODescriptor idpSSO = (IDPSSODescriptor) roleDescriptor;
219
220                    // SSO
221                    for (SingleSignOnService sso : idpSSO.getSingleSignOnServices()) {
222                        if (sso.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
223                            addProfile(new WebSSOProfileImpl(sso));
224                            break;
225                        }
226                    }
227
228                    // SLO
229                    for (SingleLogoutService slo : idpSSO.getSingleLogoutServices()) {
230                        if (slo.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
231                            addProfile(new SLOProfileImpl(slo));
232                            break;
233                        }
234                    }
235                }
236            }
237
238        } catch (MetadataProviderException e) {
239            log.warn("Failed to register IdP: " + e.getMessage());
240        }
241
242        // contribute icon and link to the Login Screen
243        if (StringUtils.isNotBlank(parameters.get("name"))) {
244            LoginScreenHelper.registerLoginProvider(parameters.get("name"), parameters.get("icon"), null,
245                    parameters.get("label"), parameters.get("description"), this);
246        }
247    }
248
249    private void addProfile(AbstractSAMLProfile profile) {
250        profile.setTrustEngine(trustEngine);
251        profile.setDecrypter(decrypter);
252        profiles.put(profile.getProfileIdentifier(), profile);
253    }
254
255    private void initializeMetadataProvider(Map<String, String> parameters) throws MetadataProviderException {
256        AbstractMetadataProvider metadataProvider;
257
258        String metadataUrl = parameters.get("metadata");
259        if (metadataUrl == null) {
260            throw new MetadataProviderException("No metadata URI set for provider "
261                    + ((parameters.containsKey("name")) ? parameters.get("name") : ""));
262        }
263
264        int requestTimeout = parameters.containsKey("timeout") ? Integer.parseInt(parameters.get("timeout")) : 5;
265
266        if (metadataUrl.startsWith("http:") || metadataUrl.startsWith("https:")) {
267            metadataProvider = new HTTPMetadataProvider(metadataUrl, requestTimeout * 1000);
268        } else { // file
269            metadataProvider = new FilesystemMetadataProvider(new File(metadataUrl));
270        }
271
272        metadataProvider.setParserPool(new BasicParserPool());
273        metadataProvider.initialize();
274
275        this.metadataProvider = metadataProvider;
276    }
277
278    private EntityDescriptor getIdPDescriptor() throws MetadataProviderException {
279        return (EntityDescriptor) metadataProvider.getMetadata();
280    }
281
282    /**
283     * Returns a Login URL to use with HTTP Redirect
284     */
285    protected String getSSOUrl(HttpServletRequest request, HttpServletResponse response) {
286        WebSSOProfile sso = (WebSSOProfile) profiles.get(WebSSOProfile.PROFILE_URI);
287        if (sso == null) {
288            return null;
289        }
290
291        // Create and populate the context
292        SAMLMessageContext context = new BasicSAMLMessageContext();
293        populateLocalContext(context);
294
295        // Store the requested URL in the Relay State
296        String requestedUrl = getRequestedUrl(request);
297        if (requestedUrl != null) {
298            context.setRelayState(requestedUrl);
299        }
300
301        // Build Uri
302        HTTPRedirectBinding binding = (HTTPRedirectBinding) getBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
303        String loginURL = sso.getEndpoint().getLocation();
304        try {
305            AuthnRequest authnRequest = sso.buildAuthRequest(request);
306            authnRequest.setDestination(sso.getEndpoint().getLocation());
307            context.setOutboundSAMLMessage(authnRequest);
308            loginURL = binding.buildRedirectURL(context, sso.getEndpoint().getLocation());
309        } catch (SAMLException e) {
310            log.error("Failed to build redirect URL", e);
311        }
312        return loginURL;
313    }
314
315    private String getRequestedUrl(HttpServletRequest request) {
316        String requestedUrl = (String) request.getAttribute(NXAuthConstants.REQUESTED_URL);
317        if (requestedUrl == null) {
318            HttpSession session = request.getSession(false);
319            if (session != null) {
320                requestedUrl = (String) session.getAttribute(NXAuthConstants.START_PAGE_SAVE_KEY);
321            }
322        }
323        return requestedUrl;
324    }
325
326    @Override
327    public String computeUrl(HttpServletRequest request, String requestedUrl) {
328        return getSSOUrl(request, null);
329    }
330
331    @Override
332    public Boolean handleLoginPrompt(HttpServletRequest request, HttpServletResponse response, String baseURL) {
333
334        String loginError = (String) request.getAttribute(LOGIN_ERROR);
335        if (loginError != null) {
336            try {
337                request.getRequestDispatcher(ERROR_PAGE).forward(request, response);
338                return true;
339            } catch (ServletException | IOException e) {
340                log.error("Failed to redirect to error page", e);
341                return false;
342            }
343        }
344
345        String loginURL = getSSOUrl(request, response);
346        try {
347            response.sendRedirect(loginURL);
348        } catch (IOException e) {
349            String errorMessage = String.format("Unable to send redirect on %s", loginURL);
350            log.error(errorMessage, e);
351            return false;
352        }
353        return true;
354    }
355
356    // Retrieves user identification information from the request.
357    @Override
358    public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest request, HttpServletResponse response) {
359
360        HttpServletRequestAdapter inTransport = new HttpServletRequestAdapter(request);
361        SAMLBinding binding = getBinding(inTransport);
362
363        // Check if we support this binding
364        if (binding == null) {
365            return null;
366        }
367
368        HttpServletResponseAdapter outTransport = new HttpServletResponseAdapter(response, request.isSecure());
369
370        // Create and populate the context
371        SAMLMessageContext context = new BasicSAMLMessageContext();
372        context.setInboundMessageTransport(inTransport);
373        context.setOutboundMessageTransport(outTransport);
374        populateLocalContext(context);
375
376        // Decode the message
377        try {
378            binding.decode(context);
379        } catch (org.opensaml.xml.security.SecurityException | MessageDecodingException e) {
380            log.error("Error during SAML decoding", e);
381            return null;
382        }
383
384        // Set Peer context info if needed
385        try {
386            if (context.getPeerEntityId() == null) {
387                context.setPeerEntityId(getIdPDescriptor().getEntityID());
388            }
389            if (context.getPeerEntityMetadata() == null) {
390                context.setPeerEntityMetadata(getIdPDescriptor());
391            }
392            if (context.getPeerEntityRole() == null) {
393                context.setPeerEntityRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
394            }
395        } catch (MetadataProviderException e) {
396            //
397        }
398
399        // Check for a response processor for this profile
400        AbstractSAMLProfile processor = getProcessor(context);
401
402        if (processor == null) {
403            log.warn("Unsupported profile encountered in the context " + context.getCommunicationProfileId());
404            return null;
405        }
406
407        // Set the communication profile
408        context.setCommunicationProfileId(processor.getProfileIdentifier());
409
410        // Delegate handling the message to the processor
411        SAMLObject message = context.getInboundSAMLMessage();
412
413        // Handle SLO
414        // TODO - Try to handle IdP initiated SLO somewhere else
415        if (processor instanceof SLOProfile) {
416            SLOProfile slo = (SLOProfile) processor;
417            try {
418                // Handle SLO response
419                if (message instanceof LogoutResponse) {
420                    slo.processLogoutResponse(context);
421                    // Handle SLO request
422                } else if (message instanceof LogoutRequest) {
423                    SAMLCredential credential = getSamlCredential(request);
424                    slo.processLogoutRequest(context, credential);
425                }
426            } catch (SAMLException e) {
427                log.debug("Error processing SAML message", e);
428            }
429            return null;
430        }
431
432        // Handle SSO
433        SAMLCredential credential;
434
435        try {
436            credential = ((WebSSOProfile) processor).processAuthenticationResponse(context);
437        } catch (SAMLException e) {
438            log.error("Error processing SAML message", e);
439            sendError(request, ERROR_AUTH);
440            return null;
441        }
442
443        String userId = userResolver.findOrCreateNuxeoUser(credential);
444
445        if (userId == null) {
446            log.warn("Failed to resolve user with NameID \"" + credential.getNameID().getValue() + "\".");
447            sendError(request, ERROR_USER);
448            return null;
449        }
450
451        // Store session id in a cookie
452        if (credential.getSessionIndexes() != null && !credential.getSessionIndexes().isEmpty()) {
453            String nameValue = credential.getNameID().getValue();
454            String nameFormat = credential.getNameID().getFormat();
455            String sessionId = credential.getSessionIndexes().get(0);
456            addCookie(response, SAML_SESSION_KEY, sessionId + "|" + nameValue + "|" + nameFormat);
457        }
458
459        return new UserIdentificationInfo(userId, userId);
460    }
461
462    protected AbstractSAMLProfile getProcessor(SAMLMessageContext context) {
463        String profileId;
464        SAMLObject message = context.getInboundSAMLMessage();
465        if (message instanceof LogoutResponse || message instanceof LogoutRequest) {
466            profileId = SLOProfile.PROFILE_URI;
467        } else {
468            profileId = WebSSOProfile.PROFILE_URI;
469        }
470
471        return profiles.get(profileId);
472    }
473
474    protected SAMLBinding getBinding(String bindingURI) {
475        for (SAMLBinding binding : bindings) {
476            if (binding.getBindingURI().equals(bindingURI)) {
477                return binding;
478            }
479        }
480        return null;
481    }
482
483    protected SAMLBinding getBinding(InTransport transport) {
484        for (SAMLBinding binding : bindings) {
485            if (binding.supports(transport)) {
486                return binding;
487            }
488        }
489        return null;
490    }
491
492    private void populateLocalContext(SAMLMessageContext context) {
493        // Set local info
494        context.setLocalEntityId(SAMLConfiguration.getEntityId());
495        context.setLocalEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
496        context.setMetadataProvider(metadataProvider);
497
498        // Set the signing key
499        keyManager = Framework.getLocalService(KeyManager.class);
500        if (getKeyManager().getSigningCredential() != null) {
501            context.setOutboundSAMLMessageSigningCredential(getKeyManager().getSigningCredential());
502        }
503    }
504
505    @Override
506    public Boolean needLoginPrompt(HttpServletRequest httpRequest) {
507        return true;
508    }
509
510    @Override
511    public List<String> getUnAuthenticatedURLPrefix() {
512        return null;
513    }
514
515    /**
516     * Returns a Logout URL to use with HTTP Redirect
517     */
518    protected String getSLOUrl(HttpServletRequest request, HttpServletResponse response) {
519        SLOProfile slo = (SLOProfile) profiles.get(SLOProfile.PROFILE_URI);
520        if (slo == null) {
521            return null;
522        }
523
524        String logoutURL = slo.getEndpoint().getLocation();
525
526        SAMLCredential credential = getSamlCredential(request);
527
528        // Create and populate the context
529        SAMLMessageContext context = new BasicSAMLMessageContext();
530        populateLocalContext(context);
531
532        try {
533            LogoutRequest logoutRequest = slo.buildLogoutRequest(context, credential);
534            Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(logoutRequest);
535            if (marshaller == null) {
536                log.error("Unable to marshall message, no marshaller registered " + "for message object: "
537                        + logoutRequest.getElementQName());
538            }
539            Element dom = marshaller.marshall(logoutRequest);
540            StringWriter buffer = new StringWriter();
541            XMLHelper.writeNode(dom, buffer);
542            String encodedSaml = Base64.encodeBase64String(buffer.toString().getBytes());
543
544            // Add the SAML as parameter
545            URLBuilder urlBuilder = new URLBuilder(logoutURL);
546            urlBuilder.getQueryParams().add(new Pair<>(HTTPRedirectBinding.SAML_REQUEST, encodedSaml));
547            logoutURL = urlBuilder.buildURL();
548        } catch (SAMLException e) {
549            log.error("Failed to get SAML Logout request", e);
550        } catch (MarshallingException e) {
551            log.error("Encountered error marshalling message to its DOM representation", e);
552        }
553
554        return logoutURL;
555    }
556
557    private SAMLCredential getSamlCredential(HttpServletRequest request) {
558        SAMLCredential credential = null;
559
560        // Retrieve the SAMLCredential credential from cookie
561        Cookie cookie = getCookie(request, SAML_SESSION_KEY);
562        if (cookie != null) {
563            String[] parts = cookie.getValue().split("\\|");
564            String sessionId = parts[0];
565            String nameValue = parts[1];
566            String nameFormat = parts[2];
567
568            NameID nameID = (NameID) Configuration.getBuilderFactory().getBuilder(NameID.DEFAULT_ELEMENT_NAME).buildObject(
569                    NameID.DEFAULT_ELEMENT_NAME);
570            nameID.setValue(nameValue);
571            nameID.setFormat(nameFormat);
572
573            List<String> sessionIndexes = new ArrayList<>();
574            sessionIndexes.add(sessionId);
575
576            credential = new SAMLCredential(nameID, sessionIndexes);
577        }
578
579        return credential;
580    }
581
582    @Override
583    public Boolean handleLogout(HttpServletRequest request, HttpServletResponse response) {
584        String logoutURL = getSLOUrl(request, response);
585
586        if (logoutURL == null) {
587            return false;
588        }
589
590        if (log.isDebugEnabled()) {
591            log.debug("Send redirect to " + logoutURL);
592        }
593
594        try {
595            response.sendRedirect(logoutURL);
596        } catch (IOException e) {
597            String errorMessage = String.format("Unable to send redirect on %s", logoutURL);
598            log.error(errorMessage, e);
599            return false;
600        }
601
602        Cookie cookie = getCookie(request, SAML_SESSION_KEY);
603        if (cookie != null) {
604            removeCookie(response, cookie);
605        }
606
607        return true;
608    }
609
610    private void sendError(HttpServletRequest req, String key) {
611        String msg = I18NUtils.getMessageString("messages", key, null, req.getLocale());
612        req.setAttribute(LOGIN_ERROR, msg);
613    }
614
615    private KeyManager getKeyManager() {
616        if (keyManager == null) {
617            keyManager = Framework.getLocalService(KeyManager.class);
618        }
619        return keyManager;
620    }
621
622    private void addCookie(HttpServletResponse httpResponse, String name, String value) {
623        Cookie cookie = new Cookie(name, value);
624        httpResponse.addCookie(cookie);
625    }
626
627    private Cookie getCookie(HttpServletRequest httpRequest, String cookieName) {
628        Cookie cookies[] = httpRequest.getCookies();
629        if (cookies != null) {
630            for (Cookie cooky : cookies) {
631                if (cookieName.equals(cooky.getName())) {
632                    return cooky;
633                }
634            }
635        }
636        return null;
637    }
638
639    private void removeCookie(HttpServletResponse httpResponse, Cookie cookie) {
640        log.debug(String.format("Removing cookie %s.", cookie.getName()));
641        cookie.setMaxAge(0);
642        cookie.setValue("");
643        httpResponse.addCookie(cookie);
644    }
645}