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