001/** 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: SSOTokenManager.java,v 1.7 2009/02/18 23:59:36 qcheng Exp $ 026 * 027 * Portions copyright 2014-2015 ForgeRock AS. 028 */ 029 030package com.iplanet.sso; 031 032import com.iplanet.am.util.SystemProperties; 033import com.iplanet.dpro.session.SessionException; 034import com.iplanet.dpro.session.SessionID; 035import com.iplanet.services.util.I18n; 036import com.iplanet.sso.providers.dpro.SSOProviderBundle; 037import com.iplanet.ums.IUMSConstants; 038import com.sun.identity.shared.debug.Debug; 039import org.forgerock.guice.core.InjectorHolder; 040import org.forgerock.openam.sso.providers.stateless.StatelessSSOProvider; 041import org.forgerock.openam.sso.providers.stateless.StatelessSessionFactory; 042import org.forgerock.openam.utils.StringUtils; 043 044import javax.servlet.http.HttpServletRequest; 045import java.security.Principal; 046import java.util.Set; 047 048/** 049 * SSOTokenManager is the final class that is the mediator between the SSO APIs 050 * and SSO providers. When an SSO client makes an API invocation, 051 * SSOTokenManager will delegate that call to the SSO provider/plug-in. The SSO 052 * provider will execute the call and return the results to SSOTokenManager, 053 * which in turn returns the results to the SSO client. This decouples the SSO 054 * clients from the actual SSO providers. You should be able to replace the SSO 055 * provider without having to modify the SSO client. However, the clients can 056 * invoke the class methods on the objects returned by the SSOTokenManager. 057 * <p> 058 * SSOTokenManager is a singleton class; there can be, at most, only one 059 * instance of SSOTokenManager in any given JVM. <p> SSOTokenManager currently 060 * supports only two kinds of provider: Grappa and OpenAM. 061 * <p> It is assumed that the provider classes or the JAR file is in the 062 * CLASSPATH so that they can be found automatically. Providers can be 063 * configured using <code>providerimplclass</code> property. 064 * This property must be set to the complete (absolute) package name of the 065 * main class of the provider. For example, if the provider class is 066 * com.iplanet.sso.providers.dpro.SSOProviderImpl, that entire class name 067 * including package prefixes MUST be specified. The main class MUST implement 068 * the com.iplanet.sso.SSOProvider interface and MUST have a public no-arg 069 * default constructor. 070 * <p> 071 * The class <code>SSOTokenManager</code> is a <code>final</code> class that 072 * provides interfaces to create and validate <code>SSOToken</code>s. 073 * <p> 074 * It is a singleton class; an instance of this class can be obtained by calling 075 * <code>SSOTokenManager.getInstance()</code>. 076 * <p> 077 * Having obtained an instance of <code>SSOTokenManager</code>, its methods 078 * can be called to create <code>SSOToken</code>, get <code>SSOToken</code> 079 * given the <code>SSOTokenID</code> in string format, and to validate 080 * <code>SSOToken</code>s. 081 * 082 * @supported.api 083 */ 084public class SSOTokenManager { 085 086 /* 087 * SSOTokenManager is not a real PROVIDER but implements SSOProvider for 088 * consistency in the methods. 089 */ 090 091 /** 092 * Grappa SSOProvider class that will be used by default if 093 * providerimplclass property is not present. 094 */ 095 static final String GRAPPA_PROVIDER_PACKAGE = 096 "com.sun.identity.authentication.internal"; 097 098 /** 099 * Package name of the stateless SSO provider. 100 */ 101 private static final String STATELESS_PROVIDER_PACKAGE = StatelessSSOProvider.class.getPackage().getName(); 102 103 static SSOProvider grappaProvider = null; 104 105 /** 106 * DPRO SSOProvider class 107 */ 108 static SSOProvider dProProvider = null; 109 110 /** Debug class that can be used by SSOProvider implementations */ 111 public static Debug debug = Debug.getInstance(IUMSConstants.UMS_DEBUG); 112 113 // Singleton instance of SSOTokenManager 114 private static volatile SSOTokenManager instance = null; 115 116 // Guice provided, lazy initialised, Server Mode only, for Stateless Sessions. 117 private static SSOProvider statelessProvider; 118 private static StatelessSessionFactory statelessFactory; 119 120 /** 121 * Returns the singleton instance of 122 * <code>SSOTokenManager</code>. 123 * 124 * @return The singleton <code>SSOTokenManager</code> instance 125 * @throws SSOException 126 * if unable to get the singleton <code>SSOTokenManager</code> 127 * instance. 128 * @supported.api 129 */ 130 public static SSOTokenManager getInstance() throws SSOException { 131 132 /* 133 * We will use the double-checked locking pattern. Rarely entered block. 134 * Push synchronization inside it. This is the first check. 135 */ 136 137 if (instance == null) { 138 /* 139 * Only 1 thread at a time gets past the next point. Rarely executed 140 * synchronization statement and hence synchronization penalty is 141 * not paid every time this method is called. 142 */ 143 144 synchronized (SSOTokenManager.class) { 145 /* 146 * If a second thread was waiting to get here, it will now find 147 * that the instance has already been initialized, and it will 148 * not re-initialize the instance variable. This is the 149 * double-check. 150 */ 151 152 if (instance == null) { 153 /* 154 * Here is the critical section that lazy initializes the 155 * singleton variable. 156 */ 157 debug.message( 158 "Constructing a new instance of SSOTokenManager"); 159 instance = new SSOTokenManager(); 160 } 161 } 162 } 163 return (instance); 164 } 165 166 /** 167 * Note: SSOTokenManager is initialised early in system sequence. This lazy initialiser is required 168 * to smooth over this issue. 169 * @return Null if client mode, otherwise non null Guice initialised. 170 */ 171 private SSOProvider getStatelessProvider() { 172 if (!SystemProperties.isServerMode()) { 173 return null; 174 } 175 176 if (statelessProvider == null) { 177 statelessProvider = InjectorHolder.getInstance(StatelessSSOProvider.class); 178 } 179 return statelessProvider; 180 } 181 182 /** 183 * Note: SSOTokenManager is initialised early in system sequence. This lazy initialiser is required 184 * to smooth over this issue. 185 * @return Null if client mode, otherwise non null Guice initialised. 186 */ 187 private StatelessSessionFactory getStatelessFactory() { 188 if (!SystemProperties.isServerMode()) { 189 return null; 190 } 191 if (statelessFactory == null) { 192 statelessFactory = InjectorHolder.getInstance(StatelessSessionFactory.class); 193 } 194 return statelessFactory; 195 } 196 197 /** 198 * Since this class is a singleton, the constructor is suppressed. This 199 * constructor will try to find the PROVIDER jar files, load them, then find 200 * the PROVIDER mainclass, instantiate it and store it in PROVIDER. 201 * Providers can be configured using <code>providerimplclass</code> Java 202 * property. This property must be set to the complete (absolute) package 203 * name of the main class of the PROVIDER. The main class MUST implement the 204 * com.iplanet.sso.SSOProvider interface and MUST have a public no-arg 205 * default constructor. 206 */ 207 private SSOTokenManager() throws SSOException { 208 Throwable dProException = null; 209 // Obtain the Grappa PROVIDER class 210 try { 211 grappaProvider = new 212 com.sun.identity.authentication.internal.AuthSSOProvider(); 213 if (debug.messageEnabled()) { 214 debug.message("Obtained Grappa SSO Provider"); 215 } 216 } catch (Throwable e) { 217 debug.error("Unable to obtain Grappa SSO PROVIDER", e); 218 dProException = e; 219 } 220 221 // Obtain the DPRO provide class 222 try { 223 dProProvider = new com.iplanet.sso.providers.dpro.SSOProviderImpl(); 224 if (debug.messageEnabled()) { 225 debug.message("Obtained DPRO SSO Provider"); 226 } 227 } catch (Throwable e) { 228 debug.error("DPRO SSO Provider Exception", e); 229 dProException = e; 230 } 231 232 if (dProProvider == null && grappaProvider == null) { 233 debug.error("Unable to obtain either GRAPPA or DPRO SSO providers"); 234 I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG); 235 String rbName = i18n.getResBundleName(); 236 if (dProException instanceof ClassNotFoundException) 237 throw new SSOException(rbName, 238 IUMSConstants.SSO_NOPROVIDERCLASS, null); 239 else if (dProException instanceof InstantiationException) 240 throw new SSOException(rbName, 241 IUMSConstants.SSO_NOPROVIDERINSTANCE, null); 242 else if (dProException instanceof IllegalAccessException) 243 throw new SSOException(rbName, IUMSConstants.SSO_ILLEGALACCESS, 244 null); 245 else 246 throw new SSOException(dProException); 247 } 248 } 249 250 /** 251 * Get PROVIDER based on SSOToken provided 252 * @param token Single signon SSOToken 253 * @exception SSOException in case of erros when getting the PROVIDER 254 */ 255 private SSOProvider getProvider(SSOToken token) throws SSOException { 256 if (token == null) { 257 throw new SSOException(SSOProviderBundle.rbName, "ssotokennull", null); 258 } 259 260 String packageName = token.getClass().getName(); 261 if (packageName.startsWith(STATELESS_PROVIDER_PACKAGE)) { 262 return getStatelessProvider(); 263 } else if (packageName.startsWith(GRAPPA_PROVIDER_PACKAGE)) { 264 if (grappaProvider == null) { 265 I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG); 266 throw new SSOException(i18n.getResBundleName(), 267 IUMSConstants.SSO_NOPROVIDERCLASS, null); 268 } 269 return (grappaProvider); 270 } 271 return (dProProvider); 272 } 273 274 /** 275 * Creates a single sign on token from <code>HttpServletRequest</code> 276 * 277 * @param request 278 * The <code>HttpServletRequest</code> object which contains 279 * the session string. 280 * @return single sign on <code>SSOToken</code> 281 * @exception SSOException 282 * if the single sign on token cannot be created. 283 * @exception UnsupportedOperationException 284 * if this is an unsupported operation. 285 * @supported.api 286 */ 287 public SSOToken createSSOToken( 288 javax.servlet.http.HttpServletRequest request) 289 throws UnsupportedOperationException, SSOException { 290 291 if (containsJwt(request)) { 292 return getStatelessProvider().createSSOToken(request); 293 } 294 295 if (dProProvider != null) 296 return (dProProvider.createSSOToken(request)); 297 else 298 return (grappaProvider.createSSOToken(request)); 299 } 300 301 /** 302 * Creates a single sign on token after authenticating 303 * the principal with the given password. This method of creating a single 304 * sign on token should only be used for command line applications and it is 305 * forbidden to use this single sign on token in any other context (e.g. 306 * policy, federation, etc.). A token created with this method is only valid 307 * within the context of the calling application. Once the process exits the 308 * token will be destroyed. If token is created using this constructor then 309 * ONLY these methods of single sign on token is supported - 310 * 311 * <pre> 312 * getAuthType(), 313 * getHostName(), 314 * getIPAddress(), 315 * setProperty(String name, String value), 316 * getProperty(String name), 317 * isValid(), 318 * validate(). 319 * </pre> 320 * 321 * @param user 322 * Principal representing a user or service 323 * @param password 324 * The password supplied for the principal 325 * @return single sign on token 326 * @exception SSOException 327 * if the single sign on token cannot be created. 328 * @exception UnsupportedOperationException 329 * if this is an unsupported operation. 330 * @deprecated This method has been deprecated. Please use the regular LDAP 331 * authentication mechanism instead. More information on how to 332 * use the authentication programming interfaces as well as the 333 * code samples can be obtained from the "Authenticating Using 334 * OpenAM Java SDK" chapter of the OpenAM Developer's Guide. 335 */ 336 public SSOToken createSSOToken(Principal user, String password) 337 throws UnsupportedOperationException, SSOException { 338 if (dProProvider != null) 339 return (dProProvider.createSSOToken(user, password)); 340 else 341 return (grappaProvider.createSSOToken(user, password)); 342 } 343 344 /** 345 * Creates a single sign on token from the single sign 346 * on token ID. Note:-If you want to do Client's IP address validation for 347 * the single sign on token then use 348 * <code>creatSSOToken(String, String)</code> OR 349 * <code>createSSOToken(HttpServletRequest)</code>. 350 * 351 * @param tokenId 352 * Token ID of the single sign on token 353 * @return single sign on token 354 * @exception SSOException 355 * if the single sign on token cannot be created. 356 * @exception UnsupportedOperationException 357 * @supported.api 358 */ 359 public SSOToken createSSOToken(String tokenId) 360 throws UnsupportedOperationException, SSOException { 361 362 if (StringUtils.isNotBlank(tokenId) && containsJwt(tokenId)) { 363 return getStatelessProvider().createSSOToken(tokenId); 364 } 365 366 if (dProProvider != null) 367 return (dProProvider.createSSOToken(tokenId)); 368 else 369 return (grappaProvider.createSSOToken(tokenId)); 370 } 371 372 /** 373 * Creates a single sign on token from the single sign 374 * on token ID. 375 * 376 * @param tokenId 377 * Token ID of the single sign on token 378 * @param clientIP 379 * Client IP address. This must be the IP address of the 380 * client/user who is accessing the application. 381 * @return single sign on token 382 * @exception SSOException 383 * if the single sign on token cannot be created. 384 * @exception UnsupportedOperationException 385 * @supported.api 386 */ 387 public SSOToken createSSOToken(String tokenId, String clientIP) 388 throws UnsupportedOperationException, SSOException { 389 390 if (containsJwt(tokenId)) { 391 return getStatelessProvider().createSSOToken(tokenId, clientIP); 392 } 393 394 if (dProProvider != null) 395 return (dProProvider.createSSOToken(tokenId, clientIP)); 396 else 397 return (grappaProvider.createSSOToken(tokenId, clientIP)); 398 } 399 400 /** 401 * Call this function if you want to retrieve a token whose id you know, you expect to be valid 402 * (this function will not create a new token for you) and you don't want its idle time accidentally 403 * reset. 404 * 405 * @param tokenId The token id of the token you suspect is valid. 406 * @return The valid token, or null if the token id turned out to be rubbish. 407 */ 408 public SSOToken retrieveValidTokenWithoutResettingIdleTime(String tokenId) 409 throws UnsupportedOperationException, SSOException { 410 411 if (containsJwt(tokenId)) { 412 return getStatelessProvider().createSSOToken(tokenId, false, false); 413 } 414 415 if (dProProvider != null) 416 return (dProProvider.createSSOToken(tokenId, false, false)); 417 else 418 return (grappaProvider.createSSOToken(tokenId, false, false)); 419 } 420 421 /** 422 * Returns true if a single sign on token is valid. Your token may have its idle time reset. 423 * You have been warned. 424 * 425 * @param token 426 * The single sign on token object to be validated. 427 * @return true if the single sign on token is valid. 428 * @supported.api 429 */ 430 public boolean isValidToken(SSOToken token) { 431 return isValidToken(token, true); 432 } 433 434 /** 435 * Returns true if a single sign on token is valid, resetting the token's idle time 436 * if and only if the flag allows us to. 437 * 438 * @param token The single sign on token object to be validated. 439 * 440 * @return true if the single sign on token is valid. 441 * @supported.api 442 * @since 12.0.0 443 */ 444 public boolean isValidToken(SSOToken token, boolean resetIdleTime) { 445 try { 446 return (getProvider(token).isValidToken(token, resetIdleTime)); 447 } catch (SSOException ignored) { 448 return (false); 449 } 450 } 451 452 453 /** 454 * Returns true if the single sign on token is valid. 455 * 456 * @param token 457 * The single sign on token object to be validated. 458 * @exception SSOException 459 * if the single sign on token is not valid. 460 * @supported.api 461 */ 462 public void validateToken(SSOToken token) throws SSOException { 463 getProvider(token).validateToken(token); 464 } 465 466 /** 467 * Destroys a single sign on token. 468 * 469 * @param token 470 * The single sign on token object to be destroyed. 471 * @exception SSOException 472 * if there was an error while destroying the token, or the 473 * corresponding session reached its maximum session/idle 474 * time, or the session was destroyed. 475 * @supported.api 476 */ 477 public void destroyToken(SSOToken token) throws SSOException { 478 getProvider(token).destroyToken(token); 479 } 480 481 /** 482 * Refresh the Session corresponding to the single 483 * sign on token from the Session Server. This method should only be used 484 * when the client cannot wait the "session cache interval" for updates on 485 * any changes made to the session properties in the session server. If the 486 * client is remote, calling this method results in an over the wire request 487 * to the session server. 488 * 489 * @param token 490 * single sign on token 491 * @exception SSOException 492 * if the session reached its maximum session time, or the 493 * session was destroyed, or there was an error while 494 * refreshing the session. 495 * @supported.api 496 */ 497 public void refreshSession(SSOToken token) throws SSOException { 498 try { 499 getProvider(token).refreshSession(token); 500 } catch (Exception e) { 501 debug.error("Error in refreshing the session from session server"); 502 throw new SSOException(e); 503 } 504 } 505 506 /** 507 * This function will never reset the idle time of the refreshed token. Otherwise, see 508 * {@link com.iplanet.sso.SSOTokenManager#refreshSession(SSOToken)} 509 * 510 * @param token single sign on token 511 * @exception SSOException 512 * if the session reached its maximum session time, or the 513 * session was destroyed, or there was an error while 514 * refreshing the session. 515 * @since 12.0.0 516 */ 517 public void refreshSessionWithoutIdleReset(SSOToken token) throws SSOException { 518 try { 519 getProvider(token).refreshSession(token, false); 520 } catch (Exception e) { 521 debug.error("Error in refreshing the session from session server"); 522 throw new SSOException(e); 523 } 524 } 525 526 /** 527 * Destroys a single sign on token. 528 * 529 * @param destroyer 530 * The single sign on token object used to authorize the 531 * operation 532 * @param destroyed 533 * The single sign on token object to be destroyed. 534 * @throws SSOException 535 * if the there was an error during communication with session 536 * service. 537 * @supported.api 538 */ 539 public void destroyToken(SSOToken destroyer, SSOToken destroyed) 540 throws SSOException { 541 getProvider(destroyer).destroyToken(destroyer, destroyed); 542 } 543 544 /** 545 * Returns a list of single sign on token objects 546 * which correspond to valid Sessions accessible to requester. Single sign 547 * on tokens returned are restricted: they can only be used to retrieve 548 * properties and destroy sessions they represent. 549 * 550 * @param requester 551 * The single sign on token object used to authorize the 552 * operation 553 * @param server 554 * The server for which the valid sessions are to be retrieved 555 * @return Set The set of single sign on tokens representing valid Sessions. 556 * @throws SSOException 557 * if the there was an error during communication with session 558 * service. 559 * @supported.api 560 */ 561 public Set getValidSessions(SSOToken requester, String server) 562 throws SSOException { 563 return getProvider(requester).getValidSessions(requester, server); 564 } 565 566 /** 567 * Logs out of any OpenAM session associated with the token without destroying the token itself. 568 * 569 * @param token the token to log out. 570 * @throws SSOException if an error occurs. 571 * @since 13.0.0 572 */ 573 public void logout(SSOToken token) throws SSOException { 574 getProvider(token).logout(token); 575 } 576 577 /** 578 * Helper function to ensure that JWT based checks are only applied in server mode. 579 * @param object Non null HttpServletRequest, SessionID or TokenID in String format. 580 * @return True if the object contained a JWT. 581 */ 582 private boolean containsJwt(Object object) { 583 if (!SystemProperties.isServerMode()) return false; 584 585 try { 586 if (object instanceof HttpServletRequest) { 587 return getStatelessFactory().containsJwt((HttpServletRequest) object); 588 } else if (object instanceof SessionID) { 589 return getStatelessFactory().containsJwt((SessionID) object); 590 } 591 return getStatelessFactory().containsJwt(object.toString()); 592 } catch (SessionException | IllegalArgumentException e) { 593 debug.message("Error whilst inspecting JWT:\nClass: {0}\n{1}", 594 object.getClass(), object, e); 595 return false; 596 } 597 } 598}