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