001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2015 ForgeRock AS. 015 */ 016package org.forgerock.openig.handler.saml; 017 018import static java.lang.String.format; 019import static org.forgerock.openig.heap.Keys.ENVIRONMENT_HEAP_KEY; 020 021import java.io.File; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030 031import javax.servlet.ServletException; 032import javax.servlet.http.HttpServletRequest; 033import javax.servlet.http.HttpServletResponse; 034 035import org.forgerock.http.Handler; 036import org.forgerock.http.header.LocationHeader; 037import org.forgerock.http.protocol.Form; 038import org.forgerock.http.protocol.Request; 039import org.forgerock.http.protocol.Response; 040import org.forgerock.http.protocol.Status; 041import org.forgerock.http.session.Session; 042import org.forgerock.http.session.SessionContext; 043import org.forgerock.json.JsonValue; 044import org.forgerock.openig.config.Environment; 045import org.forgerock.openig.heap.GenericHeapObject; 046import org.forgerock.openig.heap.GenericHeaplet; 047import org.forgerock.openig.heap.HeapException; 048import org.forgerock.services.context.AttributesContext; 049import org.forgerock.services.context.Context; 050import org.forgerock.util.promise.NeverThrowsException; 051import org.forgerock.util.promise.Promise; 052import org.forgerock.util.promise.Promises; 053 054import com.sun.identity.common.ShutdownManager; 055import com.sun.identity.plugin.session.SessionException; 056import com.sun.identity.saml2.assertion.Assertion; 057import com.sun.identity.saml2.assertion.AuthnStatement; 058import com.sun.identity.saml2.assertion.Subject; 059import com.sun.identity.saml2.common.SAML2Constants; 060import com.sun.identity.saml2.common.SAML2Exception; 061import com.sun.identity.saml2.common.SAML2Utils; 062import com.sun.identity.saml2.jaxb.entityconfig.SPSSOConfigElement; 063import com.sun.identity.saml2.meta.SAML2MetaManager; 064import com.sun.identity.saml2.profile.CacheObject; 065import com.sun.identity.saml2.profile.LogoutUtil; 066import com.sun.identity.saml2.profile.SPACSUtils; 067import com.sun.identity.saml2.profile.SPCache; 068import com.sun.identity.saml2.profile.SPSSOFederate; 069import com.sun.identity.saml2.profile.SPSingleLogout; 070import com.sun.identity.saml2.servlet.SPSingleLogoutServiceSOAP; 071 072/** 073 * The SAML federation handler. 074 */ 075public class SamlFederationHandler extends GenericHeapObject implements Handler { 076 077 /** Default Realm is always / in this case. */ 078 private static final String DEFAULT_REALM = "/"; 079 080 /** 081 * Marker for already-completed CHF Response (response filled through Servlet API). 082 */ 083 private static final Response RESPONSE_ALREADY_COMPLETED = null; 084 085 /** The attribute mapping. */ 086 private final Map<String, String> attributeMapping; 087 088 /** The value contained in the assertion subject is set as the value of the attribute subjectName in the session. */ 089 private final String subjectMapping; 090 091 /** The delimiter to use when there are multiple contexts in the assertion. */ 092 private final String authnContextDelimiter; 093 094 /** The name to use when placing context values into the session. */ 095 private final String authnContext; 096 097 private final String sessionIndexMapping; 098 099 private final String redirectURI; 100 101 private final String logoutURI; 102 103 private final String assertionConsumerEndpoint; 104 105 private final String sPinitiatedSSOEndpoint; 106 107 private final String singleLogoutEndpoint; 108 109 /** IDP Single Logout SOAP Endpoint. */ 110 private final String singleLogoutEndpointSoap; 111 112 /** SP Single Logout Endpoint. */ 113 private final String sPinitiatedSLOEndpoint; 114 115 /** 116 * Constructs a federation handler according to the specified parameters. 117 * 118 * @param attributeMapping 119 * The attribute mapping. 120 * @param subjectMapping 121 * The value contained in the assertion subject is set as the value of the attribute subjectName in the 122 * session. 123 * @param authnContextDelimiter 124 * The delimiter to use when there are multiple contexts in the assertion. 125 * @param authnContext 126 * The name to use when placing context values into the session. 127 * @param sessionIndexMapping 128 * The IDP's sessionIndex for the user is sent in the assertion. 129 * @param redirectURI 130 * The redirectURI should be set to the page the Form-Filter recognizes as the login page for the target 131 * application. 132 * @param logoutURI 133 * The logoutURI should be set to the URI which logs the user out of the target application. 134 * @param assertionConsumerEndpoint 135 * The assertionMapping defines how to transform the attributes from the incoming assertion to attribute 136 * value pairs in the session. 137 * @param sPinitiatedSSOEndpoint 138 * The default value is SPInitiatedSSO. 139 * @param singleLogoutEndpoint 140 * The default value of fedletSLO is the same as the Fedlet. 141 * @param singleLogoutEndpointSoap 142 * IDP Single Logout SOAP Endpoint. 143 * @param sPinitiatedSLOEndpoint 144 * SP Single Logout Endpoint. 145 */ 146 SamlFederationHandler(Map<String, String> attributeMapping, 147 String subjectMapping, 148 String authnContextDelimiter, 149 String authnContext, 150 String sessionIndexMapping, 151 String redirectURI, 152 String logoutURI, 153 String assertionConsumerEndpoint, 154 String sPinitiatedSSOEndpoint, 155 String singleLogoutEndpoint, 156 String singleLogoutEndpointSoap, 157 String sPinitiatedSLOEndpoint) { 158 this.attributeMapping = Collections.unmodifiableMap(attributeMapping); 159 this.subjectMapping = subjectMapping; 160 this.authnContextDelimiter = authnContextDelimiter; 161 this.authnContext = authnContext; 162 this.sessionIndexMapping = sessionIndexMapping; 163 this.redirectURI = redirectURI; 164 this.logoutURI = logoutURI; 165 this.assertionConsumerEndpoint = assertionConsumerEndpoint; 166 this.sPinitiatedSSOEndpoint = sPinitiatedSSOEndpoint; 167 this.singleLogoutEndpoint = singleLogoutEndpoint; 168 this.singleLogoutEndpointSoap = singleLogoutEndpointSoap; 169 this.sPinitiatedSLOEndpoint = sPinitiatedSLOEndpoint; 170 } 171 172 @Override 173 public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) { 174 HttpServletRequest servletRequest = adaptRequest(context, request); 175 HttpServletResponse servletResponse = adaptResponse(context); 176 177 Session session = context.asContext(SessionContext.class).getSession(); 178 try { 179 String path = request.getUri().getPath(); 180 if (path.indexOf(assertionConsumerEndpoint) > 0) { 181 return complete(serviceAssertionConsumer(session, servletRequest, servletResponse)); 182 } else if (path.indexOf(sPinitiatedSSOEndpoint) > 0) { 183 return complete(serviceSPInitiatedSSO(request, servletRequest, servletResponse)); 184 } else if (path.indexOf(sPinitiatedSLOEndpoint) > 0) { 185 return complete(serviceSPInitiatedSLO(request, session, servletRequest, servletResponse)); 186 } else if (path.indexOf(singleLogoutEndpointSoap) > 0) { 187 return complete(serviceIDPInitiatedSLOSOAP(servletRequest, servletResponse)); 188 } else if (path.indexOf(singleLogoutEndpoint) > 0) { 189 return complete(serviceIDPInitiatedSLO(request, session, servletRequest, servletResponse)); 190 } else { 191 logger.warning(format("FederationServlet warning: URI not in service %s", request.getUri())); 192 return complete(RESPONSE_ALREADY_COMPLETED); 193 } 194 } catch (IOException ioe) { 195 return complete(withError(servletResponse, ioe.getMessage())); 196 } catch (ServletException se) { 197 return complete(withError(servletResponse, se.getMessage())); 198 } catch (SAML2Exception sme) { 199 return complete(withError(servletResponse, sme.getMessage())); 200 } catch (SessionException se) { 201 return complete(withError(servletResponse, se.getMessage())); 202 } 203 } 204 205 private static Promise<Response, NeverThrowsException> complete(Response response) { 206 return Promises.newResultPromise(response); 207 } 208 209 /** 210 * Whether IDP or SP initiated, the final request ends up here. 211 * <p> 212 * The assertion is validated, attributes are retrieved from and set in the HttpSession where downstream filters 213 * can access them and pass them on to the target application. 214 */ 215 private Response serviceAssertionConsumer(Session session, 216 HttpServletRequest request, 217 HttpServletResponse response) throws IOException, 218 ServletException, 219 SAML2Exception, 220 SessionException { 221 Map<?, ?> map = SPACSUtils.processResponseForFedlet(request, response); 222 addAttributesToSession(session, map); 223 /* 224 * Redirect back to the original target application's login page and let the filters take over. If the relayURI 225 * is set in the assertion we must use that, otherwise we will use the configured value, which should be the 226 * login page for the target application. 227 */ 228 String relayURI = (String) map.get(SAML2Constants.RELAY_STATE); 229 String uri = isRelayURIProvided(relayURI) ? relayURI : redirectURI; 230 return sendRedirect(uri); 231 } 232 233 private boolean isRelayURIProvided(String relayURI) { 234 return relayURI != null && !relayURI.isEmpty(); 235 } 236 237 /** 238 * Store attribute value pairs in the session based on the assertionMapping found in config file. 239 * <p> 240 * The intent is to have a filter use one of these attributes as the subject and possibly the password. The 241 * presence of these attributes in the Session implies the assertion has been processed and validated. 242 * 243 * @param session 244 * Request's {@link Session} 245 * @param assertion 246 * SAML assertion content 247 */ 248 private void addAttributesToSession(final Session session, Map<?, ?> assertion) { 249 Map<?, ?> attributeStatement = (Map<?, ?>) assertion.get(SAML2Constants.ATTRIBUTE_MAP); 250 if (attributeStatement != null) { 251 for (String key : attributeMapping.keySet()) { 252 HashSet<?> t = (HashSet<?>) attributeStatement.get(attributeMapping.get(key)); 253 if (t != null) { 254 String sessionValue = (String) t.iterator().next(); 255 session.put(key, sessionValue); 256 } else { 257 logger.warning(format("FederationServlet: Warning no assertion attribute found for : %s", 258 attributeMapping.get(key))); 259 } 260 } 261 } else { 262 logger.warning("FederationServlet attribute statement was not present in assertion"); 263 } 264 if (subjectMapping != null) { 265 String subjectValue = ((Subject) assertion.get(SAML2Constants.SUBJECT)).getNameID().getValue(); 266 session.put(subjectMapping, subjectValue); 267 logger.debug(format("FederationServlet adding subject to session: %s = %s", subjectMapping, 268 subjectValue)); 269 } 270 271 if (sessionIndexMapping != null) { 272 String sessionIndexValue = (String) assertion.get(SAML2Constants.SESSION_INDEX); 273 session.put(sessionIndexMapping, sessionIndexValue); 274 logger.debug(format("FederationServlet adding session index: %s = %s", sessionIndexMapping, 275 sessionIndexValue)); 276 } 277 278 if (authnContext != null) { 279 @SuppressWarnings("unchecked") 280 List<AuthnStatement> authnStatements = ((Assertion) assertion.get(SAML2Constants.ASSERTION)) 281 .getAuthnStatements(); 282 StringBuilder authnContextValues = new StringBuilder(); 283 for (AuthnStatement authnStatement : authnStatements) { 284 String authnContextValue = authnStatement.getAuthnContext().getAuthnContextClassRef(); 285 if (authnContextValue != null && !authnContextValue.isEmpty()) { 286 authnContextValues.append(authnContextValue); 287 authnContextValues.append(authnContextDelimiter); 288 } 289 } 290 if (authnContextValues.length() > 0) { 291 // remove the last delimiter as it is redundant 292 authnContextValues.deleteCharAt(authnContextValues.length() - 1); 293 session.put(authnContext, authnContextValues.toString()); 294 logger.debug(format("FederationServlet adding authentication contexts to session: %s = %s", 295 authnContext, authnContextValues)); 296 } 297 } 298 } 299 300 @SuppressWarnings("unchecked") 301 private static Response serviceSPInitiatedSSO(Request request, 302 HttpServletRequest servletRequest, 303 HttpServletResponse servletResponse) throws SAML2Exception { 304 Form form = request.getForm(); 305 String metaAlias = form.getFirst(SAML2Constants.METAALIAS); 306 if (metaAlias == null || metaAlias.length() == 0) { 307 SAML2MetaManager manager = new SAML2MetaManager(); 308 List<String> spMetaAliases = 309 manager.getAllHostedServiceProviderMetaAliases(DEFAULT_REALM); 310 if (spMetaAliases != null && !spMetaAliases.isEmpty()) { 311 metaAlias = spMetaAliases.get(0); 312 } 313 } 314 String idpEntityID = form.getFirst(SAML2Constants.IDPENTITYID); 315 Map<String, List<?>> paramsMap = SAML2Utils.getParamsMap(servletRequest); 316 List<String> list = new ArrayList<>(); 317 list.add(SAML2Constants.NAMEID_TRANSIENT_FORMAT); 318 319 // next line testing to see if we can change the name format 320 paramsMap.put(SAML2Constants.NAMEID_POLICY_FORMAT, list); 321 322 // TODO: add option to specify artifact 323 if (paramsMap.get(SAML2Constants.BINDING) == null) { 324 // use POST binding 325 list = new ArrayList<>(); 326 list.add(SAML2Constants.HTTP_POST); 327 paramsMap.put(SAML2Constants.BINDING, list); 328 } 329 if (idpEntityID == null || idpEntityID.length() == 0) { 330 SAML2MetaManager manager = new SAML2MetaManager(); 331 List<String> idpEntities = manager.getAllRemoteIdentityProviderEntities(DEFAULT_REALM); 332 if (idpEntities != null && !idpEntities.isEmpty()) { 333 idpEntityID = idpEntities.get(0); 334 } 335 } 336 if (metaAlias == null || idpEntityID == null) { 337 throw new SAML2Exception("No metadata for SP or IDP"); 338 } 339 SPSSOFederate.initiateAuthnRequest(servletRequest, servletResponse, metaAlias, idpEntityID, paramsMap); 340 return RESPONSE_ALREADY_COMPLETED; 341 } 342 343 /** 344 * This implementation is based on the {@literal spSingleLogoutInit.jsp} from OpenAM. 345 * Expects to find the {@literal NameID} and {@literal SessionIndex} in the session, these are stored during the 346 * IDP login process. 347 * <p> 348 * Optional request parameters are: 349 * <ul> 350 * <li>{@literal RelayState} - the target URL on successful Single Logout.</li> 351 * <li>{@literal spEntityID} - SP entity ID. When it is missing, first SP from metadata is used.</li> 352 * <li>{@literal idpEntityID} - IDP entity ID. When it is missing, first IDP from metadata is used.</li> 353 * <li>{@literal binding} - binding used for this request and when not set it will use the default binding of 354 * the IDP.</li> 355 * </ul> 356 */ 357 @SuppressWarnings("unchecked") 358 private Response serviceSPInitiatedSLO(Request request, Session session, 359 HttpServletRequest servletRequest, 360 HttpServletResponse servletResponse) 361 throws IOException, SAML2Exception { 362 logger.debug("FederationServlet.serviceSPInitiatedSLO entering"); 363 364 // Retrieve these values from the session, if they do not exist then the session has expired 365 // or this is being called before we have authenticated to the IDP 366 String nameID = (String) session.get(subjectMapping); 367 String sessionIndex = (String) session.get(sessionIndexMapping); 368 if (nameID == null || nameID.isEmpty() || sessionIndex == null || sessionIndex.isEmpty()) { 369 throw new SAML2Exception(SAML2Utils.bundle.getString("nullNameID")); 370 } 371 372 Form form = request.getForm(); 373 SAML2MetaManager manager = new SAML2MetaManager(); 374 String relayState = form.getFirst(SAML2Constants.RELAY_STATE); 375 String spEntityID = form.getFirst(SAML2Constants.SPENTITYID); 376 String binding = form.getFirst(SAML2Constants.BINDING); 377 String idpEntityID = form.getFirst(SAML2Constants.IDPENTITYID); 378 379 // If the idpEntityID has not been specified then read it from the IDP metadata. 380 if (idpEntityID == null || idpEntityID.isEmpty()) { 381 List<String> idpEntities = manager.getAllRemoteIdentityProviderEntities(DEFAULT_REALM); 382 if (idpEntities != null && !idpEntities.isEmpty()) { 383 // Just take the first one since only one is supported 384 idpEntityID = idpEntities.get(0); 385 } 386 } 387 logger.debug(format("FederationServlet.serviceSPInitiatedSLO idpEntityID: %s", idpEntityID)); 388 389 390 String metaAlias = null; 391 // If the spEntityID has not been specified then read it from the SP metadata. 392 if (spEntityID == null || spEntityID.isEmpty()) { 393 List<String> spMetaAliases = manager.getAllHostedServiceProviderMetaAliases(DEFAULT_REALM); 394 if (spMetaAliases != null && !spMetaAliases.isEmpty()) { 395 // Just take the first one since only one is supported 396 metaAlias = spMetaAliases.get(0); 397 spEntityID = manager.getEntityByMetaAlias(metaAlias); 398 } 399 } else { 400 SPSSOConfigElement spConfig = manager.getSPSSOConfig(DEFAULT_REALM, spEntityID); 401 if (spConfig != null) { 402 metaAlias = spConfig.getMetaAlias(); 403 } 404 } 405 logger.debug(format("FederationServlet.serviceSPInitiatedSLO metaAlias: %s", metaAlias)); 406 logger.debug(format("FederationServlet.serviceSPInitiatedSLO spEntityID: %s", spEntityID)); 407 408 409 if (metaAlias == null || idpEntityID == null) { 410 throw new SAML2Exception("No metadata for SP or IDP"); 411 } 412 413 // If the binding has not been specified then look up the IDP's default binding. 414 if (binding == null) { 415 binding = LogoutUtil.getSLOBindingInfo(servletRequest, metaAlias, SAML2Constants.SP_ROLE, idpEntityID); 416 } 417 418 if (!SAML2Utils.isSPProfileBindingSupported(DEFAULT_REALM, spEntityID, SAML2Constants.SLO_SERVICE, binding)) { 419 logger.error(format("FederationServlet.serviceSPInitiatedSLO unsupported binding: %s", binding)); 420 throw new SAML2Exception(SAML2Utils.bundle.getString("unsupportedBinding")); 421 } 422 423 logger.debug(format("FederationServlet.serviceSPInitiatedSLO binding: %s", binding)); 424 425 HashMap<String, String> paramsMap = new HashMap<>(7); 426 paramsMap.put(SAML2Constants.INFO_KEY, spEntityID + "|" + idpEntityID + "|" + nameID); 427 paramsMap.put(SAML2Constants.SESSION_INDEX, sessionIndex); 428 paramsMap.put(SAML2Constants.METAALIAS, metaAlias); 429 paramsMap.put(SAML2Constants.IDPENTITYID, idpEntityID); 430 paramsMap.put(SAML2Constants.ROLE, SAML2Constants.SP_ROLE); 431 paramsMap.put(SAML2Constants.BINDING, binding); 432 433 // If the relayState has not been specified try to use the SP default otherwise set it to the logoutURI if set. 434 if (relayState == null || relayState.isEmpty()) { 435 relayState = SAML2Utils.getAttributeValueFromSSOConfig(DEFAULT_REALM, spEntityID, SAML2Constants.SP_ROLE, 436 SAML2Constants.DEFAULT_RELAY_STATE); 437 if (relayState == null || (relayState.isEmpty() && logoutURI != null && !logoutURI.isEmpty())) { 438 relayState = logoutURI; 439 } 440 } 441 if (relayState != null && !relayState.isEmpty()) { 442 paramsMap.put(SAML2Constants.RELAY_STATE, relayState); 443 } 444 445 logger.debug(format("FederationServlet.serviceSPInitiatedSLO relayState: %s", relayState)); 446 447 SPSingleLogout.initiateLogoutRequest(servletRequest, servletResponse, binding, paramsMap); 448 449 if (SAML2Constants.SOAP.equalsIgnoreCase(binding)) { 450 return sendRedirect(relayState); 451 } 452 453 return RESPONSE_ALREADY_COMPLETED; 454 } 455 456 private Response serviceIDPInitiatedSLO(final Request request, 457 final Session session, 458 HttpServletRequest servletRequest, 459 HttpServletResponse servletResponse) 460 throws SAML2Exception, SessionException, IOException { 461 logger.debug("FederationServlet.serviceIDPInitiatedSLO entering"); 462 463 String relayState = getLogoutRelayState(request); 464 logger.debug(format("FederationServlet.serviceIDPInitiatedSLO relayState : %s", relayState)); 465 466 Form form = request.getForm(); 467 // Check if this is a request as part of an IDP initiated SLO 468 String samlRequest = form.getFirst(SAML2Constants.SAML_REQUEST); 469 Response response = RESPONSE_ALREADY_COMPLETED; 470 if (samlRequest != null) { 471 logger.debug("FederationServlet.serviceIDPInitiatedSLO processing IDP request"); 472 SPSingleLogout.processLogoutRequest(servletRequest, servletResponse, samlRequest, relayState); 473 } else { 474 // Otherwise it might be a response from the IDP as part of a SP initiated SLO 475 String samlResponse = form.getFirst(SAML2Constants.SAML_RESPONSE); 476 if (samlResponse != null) { 477 logger.debug("FederationServlet.serviceIDPInitiatedSLO processing IDP response"); 478 SPSingleLogout.processLogoutResponse(servletRequest, servletResponse, samlResponse, relayState); 479 if (relayState != null) { 480 response = sendRedirect(relayState); 481 } 482 } 483 } 484 485 cleanSession(session); 486 return response; 487 } 488 489 private Response serviceIDPInitiatedSLOSOAP(HttpServletRequest request, HttpServletResponse response) 490 throws IOException, ServletException { 491 492 logger.debug("FederationServlet.serviceIDPInitiatedSLOSOAP entering"); 493 494 SPSingleLogoutServiceSOAP spSingleLogoutServiceSOAP = new SPSingleLogoutServiceSOAP(); 495 spSingleLogoutServiceSOAP.doPost(request, response); 496 497 return RESPONSE_ALREADY_COMPLETED; 498 } 499 500 private String getLogoutRelayState(Request request) { 501 502 Form form = request.getForm(); 503 String relayState = form.getFirst(SAML2Constants.RELAY_STATE); 504 if (relayState != null) { 505 // Check the SP cache for the actual relayState value as the relayState is 506 // often passed as an ID to the IDP as part of the SP initiated SLO. Based on 507 // code from spSingleLogoutPOST.jsp in OpenAM 508 CacheObject tmpRs = (CacheObject) SPCache.relayStateHash.remove(relayState); 509 if (tmpRs != null) { 510 relayState = (String) tmpRs.getObject(); 511 } 512 } 513 514 if (relayState == null || (relayState.isEmpty() && logoutURI != null && !logoutURI.isEmpty())) { 515 relayState = logoutURI; 516 } 517 518 return relayState; 519 } 520 521 /** Clean the session at the end of the SLO process. */ 522 private void cleanSession(final Session session) { 523 logger.debug("End of SLO - Processing to session cleanup"); 524 session.remove(subjectMapping); 525 session.remove(sessionIndexMapping); 526 session.remove(authnContext); 527 528 if (attributeMapping != null) { 529 for (final String key : attributeMapping.keySet()) { 530 session.remove(key); 531 } 532 } 533 } 534 535 private Response withError(HttpServletResponse response, String message) { 536 final String msg = format("SSO Failed: %s", message); 537 logger.error(msg); 538 if (!response.isCommitted()) { 539 return sendError(Status.INTERNAL_SERVER_ERROR, msg); 540 } 541 return null; 542 } 543 544 private static Response sendError(Status status, String message) { 545 Response response = new Response(); 546 response.setStatus(status); 547 response.getEntity().setString(message); 548 return response; 549 } 550 551 private static Response sendRedirect(String redirectUri) { 552 Response response = new Response(); 553 // Redirect with a 302 (Found) status code 554 response.setStatus(Status.FOUND); 555 // Web container was rebasing location header against server URL 556 // Not useful if relayState is already (and always) an absolute URL 557 response.getHeaders().put(LocationHeader.NAME, redirectUri); 558 return response; 559 } 560 561 private static HttpServletResponse adaptResponse(Context context) { 562 AttributesContext attributesContext = context.asContext(AttributesContext.class); 563 return (HttpServletResponse) attributesContext.getAttributes().get(HttpServletResponse.class.getName()); 564 } 565 566 private static HttpServletRequest adaptRequest(Context context, Request req) { 567 AttributesContext attributesContext = context.asContext(AttributesContext.class); 568 HttpServletRequest request = (HttpServletRequest) attributesContext.getAttributes() 569 .get(HttpServletRequest.class.getName()); 570 return new RequestAdapter(request, req); 571 } 572 573 /** 574 * Reads the actual federation servlet from the JSON configuration file. 575 */ 576 public static class Heaplet extends GenericHeaplet { 577 @Override 578 public Object create() throws HeapException { 579 final Map<String, String> attributeMapping = new HashMap<>(); 580 JsonValue mappings = config.get("assertionMapping").expect(Map.class); 581 if (mappings != null) { 582 for (String key : mappings.keys()) { 583 attributeMapping.put(key, mappings.get(key).asString()); 584 } 585 } 586 final String authnContextDelimiter = config.get("authnContextDelimiter").defaultTo("|").asString(); 587 final String authnContext = config.get("authnContext").asString(); 588 final String redirectURI = config.get("redirectURI").asString(); 589 final String logoutURI = config.get("logoutURI").asString(); 590 // Give subjectMapping and sessionIndexMapping a default value as they are needed when doing SP initiated 591 // SLO 592 final String subjectMapping = config.get("subjectMapping").defaultTo("subjectMapping").asString(); 593 final String sessionIndexMapping = config.get("sessionIndexMapping").defaultTo("sessionIndexMapping") 594 .asString(); 595 final String assertionConsumerEndpoint = config.get("assertionConsumerEndpoint") 596 .defaultTo("fedletapplication").asString(); 597 final String sPinitiatedSSOEndpoint = config.get("SPinitiatedSSOEndpoint").defaultTo("SPInitiatedSSO") 598 .asString(); 599 final String singleLogoutEndpoint = config.get("singleLogoutEndpoint").defaultTo("fedletSloRedirect") 600 .asString(); 601 final String singleLogoutEndpointSoap = config.get("singleLogoutEndpointSoap").defaultTo("fedletSloSoap") 602 .asString(); 603 final String sPinitiatedSLOEndpoint = config.get("SPinitiatedSLOEndpoint").defaultTo("SPInitiatedSLO") 604 .asString(); 605 /* 606 * Get the gateway configuration directory and set it as a system property to override the default openFed 607 * location. Federation config files will reside in the SAML directory. 608 */ 609 Environment environment = heap.get(ENVIRONMENT_HEAP_KEY, Environment.class); 610 String samlDirectory = new File(environment.getBaseDirectory(), "SAML").getPath(); 611 logger.info(format("FederationServlet init directory: %s", samlDirectory)); 612 Properties p = System.getProperties(); 613 p.setProperty("com.sun.identity.fedlet.home", samlDirectory); 614 System.setProperties(p); 615 616 return new SamlFederationHandler(attributeMapping, 617 subjectMapping, 618 authnContextDelimiter, 619 authnContext, 620 sessionIndexMapping, 621 redirectURI, 622 logoutURI, 623 assertionConsumerEndpoint, 624 sPinitiatedSSOEndpoint, 625 singleLogoutEndpoint, 626 singleLogoutEndpointSoap, 627 sPinitiatedSLOEndpoint); 628 } 629 630 @Override 631 public void destroy() { 632 // Automatically shutdown the fedlet 633 ShutdownManager manager = ShutdownManager.getInstance(); 634 if (manager.acquireValidLock()) { 635 try { 636 manager.shutdown(); 637 } finally { 638 manager.releaseLockAndNotify(); 639 } 640 } 641 super.destroy(); 642 } 643 } 644}