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