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