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