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: AMLoginModule.java,v 1.22 2009/11/21 01:11:56 222713 Exp $ 026 * 027 */ 028 029/* 030 * Portions Copyrighted 2010-2012 ForgeRock Inc 031 */ 032 033package com.sun.identity.authentication.spi; 034 035import java.io.IOException; 036import java.security.Principal; 037import java.util.ArrayList; 038import java.util.HashMap; 039import java.util.HashSet; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Map; 043import java.util.ResourceBundle; 044import java.util.Set; 045 046import javax.security.auth.Subject; 047import javax.security.auth.callback.Callback; 048import javax.security.auth.callback.CallbackHandler; 049import javax.security.auth.callback.ChoiceCallback; 050import javax.security.auth.callback.ConfirmationCallback; 051import javax.security.auth.callback.NameCallback; 052import javax.security.auth.callback.PasswordCallback; 053import javax.security.auth.callback.TextInputCallback; 054import javax.security.auth.callback.TextOutputCallback; 055import javax.security.auth.callback.UnsupportedCallbackException; 056import javax.security.auth.login.LoginException; 057import javax.security.auth.spi.LoginModule; 058import javax.servlet.http.HttpServletRequest; 059import javax.servlet.http.HttpServletResponse; 060 061import com.iplanet.am.sdk.AMException; 062import com.iplanet.am.sdk.AMUser; 063import com.iplanet.am.sdk.AMUserPasswordValidation; 064import com.iplanet.am.util.Misc; 065import com.sun.identity.shared.locale.AMResourceBundleCache; 066import com.sun.identity.shared.debug.Debug; 067import com.sun.identity.shared.datastruct.CollectionHelper; 068import com.iplanet.dpro.session.service.InternalSession; 069import com.iplanet.dpro.session.service.SessionCount; 070import com.iplanet.dpro.session.service.SessionConstraint; 071import com.iplanet.sso.SSOException; 072import com.iplanet.sso.SSOToken; 073import com.iplanet.sso.SSOTokenManager; 074import com.sun.identity.authentication.service.AMAuthErrorCode; 075import com.sun.identity.authentication.service.AuthD; 076import com.sun.identity.authentication.service.AuthException; 077import com.sun.identity.authentication.service.LoginStateCallback; 078import com.sun.identity.authentication.service.LoginState; 079import com.sun.identity.authentication.util.ISAuthConstants; 080import com.sun.identity.authentication.util.ISValidation; 081import com.sun.identity.common.AdministrationServiceListener; 082import com.sun.identity.idm.AMIdentityRepository; 083import com.sun.identity.idm.AMIdentity; 084import com.sun.identity.idm.IdRepoException; 085import com.sun.identity.idm.IdType; 086import com.sun.identity.idm.IdUtils; 087import com.sun.identity.common.ISAccountLockout; 088import com.sun.identity.common.DNUtils; 089import com.sun.identity.common.AccountLockoutInfo; 090import com.sun.identity.shared.Constants; 091import com.sun.identity.sm.OrganizationConfigManager; 092import com.sun.identity.sm.ServiceSchema; 093import com.sun.identity.sm.ServiceSchemaManager; 094import com.sun.identity.shared.ldap.util.DN; 095 096/** 097 * An abstract class which implements JAAS LoginModule, it provides 098 * methods to access OpenSSO services and the module 099 * xml configuration. 100 * <p> 101 * Because it is an abstract class, Login Module writers must subclass 102 * and implement init(), process(), getPrincipal() methods. 103 * <p> 104 * The Callback[] for the Login Module is dynamically generated based 105 * on the xml module configuration. The module configuration file name 106 * must be the same as the name of the class (no package name) and have the 107 * extension .xml. 108 * <p> 109 * Here is a sample module configuration file: 110 * <pre> 111 * <ModuleProperties moduleClass="LDAP" version="1.0" > 112 * <Callbacks length="2" order="1" timeout="60" header="LDAP 113 * Authentication" > 114 * <NameCallback> 115 * <Prompt> Enter UserId </Prompt> 116 * </NameCallback> 117 * <PasswordCallback echoPassword="false" > 118 * <Prompt> Enter Password </Prompt> 119 * </PasswordCallback> 120 * </Callbacks> 121 * <Callbacks length="3" order="2" timeout="120" header="Password 122 * Expiring Please Change" > 123 * <PasswordCallback echoPassword="false" > 124 * <Prompt> Enter Current Password </Prompt> 125 * </PasswordCallback> 126 * <PasswordCallback echoPassword="false" > 127 * <Prompt> Enter New Password </Prompt> 128 * </PasswordCallback> 129 * <PasswordCallback echoPassword="false" > 130 * <Prompt> Confirm New Password </Prompt> 131 * </PasswordCallback> 132 * </Callbacks> 133 * </ModuleProperties> 134 * </pre> 135 * Each Callbacks Element corresponds to one login state. 136 * When an authentication process is invoked, there will be Callback[] 137 * generated from user's Login Module for each state. All login state 138 * starts with 1, then module controls the login process, and decides what's 139 * the next state to go in the process() method. 140 * <p> 141 * In the sample module configuration shown above, state one has 142 * three Callbacks, Callback[0] is for module information, Callback[1] is 143 * for user ID, Callback[2] is for user password. When the user fills in the 144 * Callbacks, those Callback[] will be sent to the process() method, where 145 * the module writer gets the submitted Callbacks, validates them and returns. 146 * If user's password is expiring, the module writer will set the next 147 * state to 2. State two has four Callbacks to request user to change 148 * password. The process() routine is again 149 * called after user submits the Callback[]. If the module writer throws an 150 * LoginException, an 'authentication failed' page will be sent to the user. 151 * If no exception is thrown, the user will be redirected to their default 152 * page. 153 * <p> 154 * The optional 'timeout' attribute in each state is used to ensure that the 155 * user responds in a timely manner. If the time between sending the Callbacks 156 * and getting response is greater than the timeout, a timeout page will be 157 * sent. 158 * <p> 159 * There are also optional 'html' and 'image' attribute in each state. The 160 * 'html' attribute allows the module writer to use a custom HTML 161 * page for the Login UI. The 'image' attribute allows the writer to display 162 * a custom background image on each page. 163 * <p> 164 * When multiple states are available to the user, the Callback array from a 165 * previous state may be retrieved by using the <code>getCallbak(int)</code> 166 * methods. The underlying login module keeps the Callback[] from the previous 167 * states until the login process is completed. 168 * <p> 169 * If a module writer need to substitute dynamic text in next state, the writer 170 * could use the <code>getCallback()</code> method to get the Callback[] for the 171 * next state, modify the output text or prompt, then call 172 * <code>replaceCallback()</code> to update the Callback array. This allows a 173 * module writer to dynamically generate challenges, passwords or user IDs. 174 * <p> 175 * Each authentication session will create a new instance of your 176 * Login Module Java class. The reference to the class will be 177 * released once the authentication session has either succeeded 178 * or failed. It is important to note that any static data or 179 * reference to any static data in your Login module 180 * must be thread-safe. 181 * <p> 182 * 183 * For a complete sample, please refer to 184 * <install_root>/SUNWam/samples/authentication/providers 185 * 186 * @supported.api 187 */ 188public abstract class AMLoginModule implements LoginModule { 189 // list which holds both presentation and credential callbacks 190 List internal = null; 191 // list which holds only credential callbacks 192 List external = null; 193 // list which contains the original Callback list from AMModuleProperties 194 List origList = null; 195 // class name 196 private String fileName = null; 197 // if true, means this module does not hava any Callbacks defined, this 198 // is the case for anonymous/cert, which have a size 0 config file 199 boolean noCallbacks = false; 200 201 // constant for empty Callback array 202 private static Callback[] EMPTY_CALLBACK = new Callback[0]; 203 204 // state length for this module 205 private int stateLength = 0; 206 207 // resource bundle 208 private ResourceBundle bundle = null; 209 210 // login state 211 private LoginState loginState =null; 212 213 /** 214 * Holds callback handler object passed in through initialize method 215 */ 216 private CallbackHandler handler = null; 217 /** 218 * Holds subject object passed in through initialize method 219 */ 220 private Subject subject = null; 221 /** 222 * Holds shared state map passed in through initialize method 223 */ 224 private Map sharedState = null; 225 /** 226 * Holds options map passed in through initialize method 227 */ 228 private Map options = null; 229 230 private static Debug debug = Debug.getInstance("amLoginModule"); 231 232 private int currentState = ISAuthConstants.LOGIN_START; 233 234 private final String EMPTY_STRING = ""; 235 private String moduleName = null; 236 private String moduleClass = null; 237 private static final String bundleName = "amAuth"; 238 private static AuthD ad = AuthD.getAuth(); 239 private Principal principal = null; 240 // the authentication status 241 private boolean succeeded = false; 242 243 private boolean forceCallbacksRead = false; 244 245 //use Shared state by default disabled 246 private boolean isSharedState = false; 247 private boolean isStore = true; 248 private String sharedStateBehaviorPattern = ""; 249 250 // variable used in replaceHeader() 251 private String headerWithReplaceTag; 252 private boolean alreadyReplaced = false; 253 private int lastState = 0; 254 255 /** 256 * Holds handle to ResourceBundleCache to quickly get ResourceBundle for 257 * any Locale. 258 */ 259 protected static AMResourceBundleCache amCache = 260 AMResourceBundleCache.getInstance(); 261 262 263 /** 264 * Clone Callback[], and save it in the internal/external 265 * callbacks list. External callback contains all user defined 266 * Callbacks in the xml module configuration (property file), 267 * internal callback contains the external callbacks plus the 268 * PagePropertiesCallback. Note here, although 269 * Callback[] in internal/external are different, the Callback 270 * instance they pointed are actually same instance 271 * @param index indicates state of callback 272 * @param original original array of callback to be cloned 273 * @return Callback[] returns cloned callback 274 * @exception AuthLoginException if callback can not be cloned 275 */ 276 private Callback[] cloneCallbacks(int index, Callback[] original) 277 throws AuthLoginException { 278 // check if there is any callbacks in original 279 if (original == null || original.length == 0) { 280 // this is the error case where there is no Callbacks 281 // defined for a state 282 debug.error("cloneCallbacks, no callbacks in state " + (index+1)); 283 throw new AuthLoginException(bundleName, "noCallbackState", 284 new Object[]{new Integer(index + 1)}); 285 } 286 287 int len = original.length; 288 // Callback array which hold the cloned Callbacks 289 Callback[] copy = new Callback[len]; 290 // List which contains the external callbacks only 291 List extCallbacks = new ArrayList(); 292 293 // iterate through Callback array, and copy them one by one 294 // if it is an external Callback, add to the extCallback list 295 for (int i = 0; i < len; i++) { 296 if (original[i] instanceof NameCallback) { 297 String dftName = ((NameCallback)original[i]).getDefaultName(); 298 if (dftName != null && dftName.length() != 0) { 299 copy[i] = new NameCallback( 300 ((NameCallback)original[i]).getPrompt(), dftName); 301 } else { 302 copy[i] = new NameCallback( 303 ((NameCallback)original[i]).getPrompt()); 304 } 305 extCallbacks.add(copy[i]); 306 if (debug.messageEnabled()) { 307 debug.message("clone #" + i + " is NameCallback"); 308 } 309 } else if (original[i] instanceof PasswordCallback) { 310 copy[i] = new PasswordCallback( 311 ((PasswordCallback) original[i]).getPrompt(), 312 ((PasswordCallback) original[i]).isEchoOn()); 313 extCallbacks.add(copy[i]); 314 if (debug.messageEnabled()) { 315 debug.message("clone #" + i + " is PasswordCallback"); 316 } 317 } else if (original[i] instanceof TextOutputCallback) { 318 copy[i] = new TextOutputCallback( 319 ((TextOutputCallback) original[i]).getMessageType(), 320 ((TextOutputCallback) original[i]).getMessage()); 321 extCallbacks.add(copy[i]); 322 if (debug.messageEnabled()) { 323 debug.message("clone #" + i + " is TextOutputCallback"); 324 } 325 } else if (original[i] instanceof PagePropertiesCallback) { 326 // PagePropertiesCallback, no need to add to external callbacks 327 copy[i] = new PagePropertiesCallback( 328 ((PagePropertiesCallback) original[i]).getModuleName(), 329 ((PagePropertiesCallback) original[i]).getHeader(), 330 ((PagePropertiesCallback) original[i]).getImage(), 331 ((PagePropertiesCallback) original[i]).getTimeOutValue(), 332 ((PagePropertiesCallback) original[i]).getTemplateName(), 333 ((PagePropertiesCallback) original[i]).getErrorState(), 334 ((PagePropertiesCallback) original[i]).getPageState()); 335 ((PagePropertiesCallback) copy[i]).setRequire( 336 ((PagePropertiesCallback) original[i]).getRequire()); 337 ((PagePropertiesCallback) copy[i]).setAttribute( 338 ((PagePropertiesCallback) original[i]).getAttribute()); 339 ((PagePropertiesCallback) copy[i]).setInfoText( 340 ((PagePropertiesCallback) original[i]).getInfoText()); 341 if (debug.messageEnabled()) { 342 debug.message("clone #" + i + " is PagePropertiesCallback"); 343 } 344 } else if (original[i] instanceof ChoiceCallback) { 345 int selection = 346 ((ChoiceCallback)original[i]).getDefaultChoice(); 347 copy[i] = new ChoiceCallback( 348 ((ChoiceCallback) original[i]).getPrompt(), 349 ((ChoiceCallback) original[i]).getChoices(), 350 selection, 351 ((ChoiceCallback) original[i]).allowMultipleSelections()); 352 ((ChoiceCallback) copy[i]).setSelectedIndex(selection); 353 extCallbacks.add(copy[i]); 354 if (debug.messageEnabled()) { 355 debug.message("clone #" + i + " is ChoiceCallback"); 356 } 357 } else if (original[i] instanceof ConfirmationCallback) { 358 ConfirmationCallback temp = (ConfirmationCallback) original[i]; 359 String prompt = temp.getPrompt(); 360 String[] options = temp.getOptions(); 361 if (prompt == null) { 362 // no prompt 363 if (options == null) { 364 // no options 365 copy[i] = new ConfirmationCallback( 366 temp.getMessageType(), 367 temp.getOptionType(), 368 temp.getDefaultOption()); 369 } else { 370 copy[i] = new ConfirmationCallback( 371 temp.getMessageType(), 372 options, 373 temp.getDefaultOption()); 374 } 375 } else { 376 // has prompt 377 if (options == null) { 378 // no options 379 copy[i] = new ConfirmationCallback( 380 prompt, 381 temp.getMessageType(), 382 temp.getOptionType(), 383 temp.getDefaultOption()); 384 } else { 385 copy[i] = new ConfirmationCallback( 386 prompt, 387 temp.getMessageType(), 388 options, 389 temp.getDefaultOption()); 390 } 391 } 392 extCallbacks.add(copy[i]); 393 if (debug.messageEnabled()) { 394 debug.message("clone #" + i + " is ConfirmationCallback"); 395 } 396 } else if (original[i] instanceof TextInputCallback) { 397 copy[i] = new TextInputCallback( 398 ((TextInputCallback) original[i]).getPrompt()); 399 extCallbacks.add(copy[i]); 400 if (debug.messageEnabled()) { 401 debug.message("clone #" + i + " is TextInputCallback"); 402 } 403 } else if (original[i] instanceof HttpCallback) { 404 HttpCallback hc = (HttpCallback) original[i]; 405 copy[i] = new HttpCallback(hc.getAuthorizationHeader(), 406 hc.getNegotiationHeaderName(), 407 hc.getNegotiationHeaderValue(), 408 hc.getNegotiationCode()); 409 extCallbacks.add(copy[i]); 410 } else if (original[i] instanceof RedirectCallback) { 411 RedirectCallback rc = (RedirectCallback) original[i]; 412 copy[i] = new RedirectCallback(rc.getRedirectUrl(), 413 rc.getRedirectData(), 414 rc.getMethod(), 415 rc.getStatusParameter(), 416 rc.getRedirectBackUrlCookieName()); 417 extCallbacks.add(copy[i]); 418 } else { 419 debug.error("unknown callback " + original[i]); 420 } 421 // more callbacks need to be handled here if ... 422 } 423 424 // construct external Callback[] 425 Callback[] ext = new Callback[extCallbacks.size()]; 426 if (!extCallbacks.isEmpty()) { 427 Iterator it = extCallbacks.iterator(); 428 int i = 0; 429 while (it.hasNext()) { 430 ext[i++] = (Callback) it.next(); 431 } 432 } 433 434 // set external/internal callbacks 435 internal.set(index, copy); 436 external.set(index, ext); 437 438 return ext; 439 } 440 441 /** 442 * Returns an administration SSOToken for use the OpenAM APIs. 443 * 444 * <I>NB:</I>This is not the SSOToken that represents the user, if you wish 445 * to set/get user session properties use the <code>setUserSessionProperty</code> 446 * and <code>getUserSessionProperty</code> method respectively. 447 * 448 * @return An administrative <code>SSOToken</code>. 449 * @exception AuthLoginException if the authentication SSO session 450 * is null. 451 * @supported.api 452 */ 453 public SSOToken getSSOSession() throws AuthLoginException { 454 SSOToken sess = AuthD.getAuth().getSSOAuthSession(); 455 if (sess == null) { 456 throw new AuthLoginException(bundleName, "nullSess", null); 457 } 458 return sess; 459 } 460 461 /** 462 * Returns a Callback array for a specific state. 463 * <p> 464 * This method can be used to retrieve Callback[] for any state. All 465 * previous submitted Callback[] information are kept until the login 466 * process is completed. 467 * @param index order of state 468 * @return Callback array for this state, return 0-length Callback array 469 * if there is no Callback defined for this state 470 * @throws AuthLoginException if unable to read the callbacks 471 * @supported.api 472 */ 473 public Callback[] getCallback(int index) throws AuthLoginException { 474 return getCallback(index, false); 475 } 476 477 /** 478 * Return a Callback array for a specific state. 479 * <p> 480 * This method can be used to retrieve Callback[] for any state. All 481 * previous submitted Callback[] information are kept until the login 482 * process is completed. 483 * @param index order of state 484 * @param fetchOrig boolean indicating even if the callbacks for this 485 * state have been previously retrieved, get the original callbacks 486 * from AMModuleProperties, if set to "true". 487 * @return Callback array for this state, return 0-length Callback array 488 * if there is no Callback defined for this state 489 * @throws AuthLoginException if unable to read the callbacks 490 * @supported.api 491 */ 492 public Callback[] getCallback(int index, boolean fetchOrig) 493 throws AuthLoginException 494 { 495 // This method will be called by customer module, so it will 496 // return Callback[] from external callback List 497 // check if there is no callbacks defined for this module 498 if (noCallbacks || ( (isSharedState) && (!forceCallbacksRead) )) { 499 return EMPTY_CALLBACK; 500 } 501 502 if ((internal == null) || ( fetchOrig )) { 503 forceCallbacksInit(); 504 if (origList == null || origList.isEmpty()) { 505 return EMPTY_CALLBACK; 506 } 507 508 if (debug.messageEnabled()) { 509 debug.message("callback size for state " + index + "=" + 510 stateLength); 511 } 512 } 513 514 // get Callback[] for this page 515 // use index-1 as order since page index starts with 1 516 if (index > stateLength) { 517 // invalid login state 518 debug.error("getCallback, state " + index + " > " + stateLength); 519 throw new AuthLoginException(bundleName, "invalidState", 520 new Object[]{new Integer(index)}); 521 } 522 Object temp = external.get(index-1); 523 if (temp != null) { 524 return (Callback[]) temp; 525 } 526 527 // callbacks has not been retrieved for this index yet 528 // need to get it from AMModuleProperties 529 // since the Callbacks could not be shared by different instances 530 // we need to create clone copy here 531 return cloneCallbacks(index-1, (Callback[]) origList.get(index-1)); 532 } 533 534 protected void forceCallbacksInit () throws AuthLoginException { 535 if (internal == null) { 536 // get the callbacks for this class; 537 origList = AMModuleProperties.getModuleProperties(fileName); 538 if (origList == null || origList.isEmpty()) { 539 // we got file whose size is zero, this is the case for 540 // Cert/Anonymous based authentication 541 noCallbacks = true; 542 return; 543 } 544 // instantiate internal/external according to module callback size 545 stateLength = origList.size(); 546 internal = new ArrayList(); 547 external = new ArrayList(); 548 if (debug.messageEnabled()) { 549 debug.message("callback stateLength in file = " + stateLength); 550 } 551 for (int i = 0; i < stateLength; i++) { 552 internal.add(null); 553 external.add(null); 554 } 555 } 556 } 557 558 /** 559 * Replace Callback object for a specific state. 560 * @param state Order of login state 561 * @param index Index of Callback in the Callback array to be replaced 562 * for the specified state. Here index starts with 0, i.e. 0 means the 563 * first Callback in the Callback[], 1 means the second callback. 564 * @param callback Callback instance to be replaced 565 * @exception AuthLoginException if state or index is out of 566 * bound, or callback instance is null. 567 * @supported.api 568 */ 569 public void replaceCallback(int state, int index, Callback callback) 570 throws AuthLoginException { 571 if (debug.messageEnabled()) { 572 debug.message("ReplaceCallback : state=" + state + ", index=" + 573 index + ", callback=" + callback); 574 } 575 // check state length 576 if (state > stateLength) { 577 throw new AuthLoginException(bundleName, "invalidState", 578 new Object[]{new Integer(state)}); 579 } 580 // check callback length for the state 581 Callback[] ext = getCallback(state); 582 if (index < 0 || index >= ext.length) { 583 throw new AuthLoginException(bundleName, "invalidCallbackIndex", 584 new Object[]{new Integer(index)}); 585 } 586 // check callback instance 587 if (callback == null) { 588 throw new AuthLoginException(bundleName, "nullCallback", null); 589 } 590 591 // replace callback in external & internal Callback array 592 ext[index] = callback; 593 // in internal, first Callback is always PagePropertiesCallback 594 // so add one here for the index 595 ((Callback[]) internal.get(state-1))[index + 1] = callback; 596 } 597 598 /** 599 * Replace page header for a specific state. 600 * @param state Order of login state 601 * @param header header messages to be replaced 602 * @throws AuthLoginException if state is out of bound. 603 */ 604 public void replaceHeader(int state, String header) 605 throws AuthLoginException { 606 if (debug.messageEnabled()) { 607 debug.message("ReplaceHeader : state=" + state + ", header=" + 608 header); 609 } 610 611 if (lastState != state) { 612 alreadyReplaced = false; 613 } 614 lastState = state; 615 616 // check state length 617 if (state > stateLength) { 618 throw new AuthLoginException(bundleName, "invalidState", 619 new Object[]{new Integer(state)}); 620 } 621 // check callback length for the state 622 Callback[] ext = getCallback(state, true); 623 if (ext.length<=0) { 624 throw new AuthLoginException(bundleName, "invalidCallbackIndex", 625 null); 626 } 627 628 // in internal, first Callback is always PagePropertiesCallback 629 if ((header!=null)&&(header.length() != 0)) { 630 PagePropertiesCallback pc = 631 (PagePropertiesCallback)((Callback[]) internal.get(state-1))[0]; 632 // retrieve header with REPLACE tag 633 if ( !(alreadyReplaced) ) { 634 headerWithReplaceTag = pc.getHeader(); 635 } 636 // replace string 637 int idx = headerWithReplaceTag.indexOf("#REPLACE#"); 638 if (idx != -1) { 639 String newHeader = headerWithReplaceTag.substring(0, idx) + header; 640 pc.setHeader(newHeader); 641 alreadyReplaced = true; 642 }else{ 643 String newHeader = headerWithReplaceTag.substring(0, 644 headerWithReplaceTag.indexOf("<BR></BR>")) + "<BR></BR>" + header; 645 pc.setHeader(newHeader); 646 } 647 } 648 } 649 650 /** 651 * Allows you to set the info text for a specific callback. Info Text is shown 652 * under the element in the Login page. It is used in the membership module to 653 * implement in-line feedback. 654 * 655 * @param state state in which the Callback[] to be reset 656 * @param callback the callback to associate the info text 657 * @param infoText the infotext for the callback 658 * @throws AuthLoginException if state/callback is out of bounds 659 * @supported.api 660 */ 661 public void substituteInfoText(int state, int callback, String infoText) 662 throws AuthLoginException { 663 if (debug.messageEnabled()) { 664 debug.message("setInfoText : state=" + state + ", infoText=" + infoText); 665 } 666 667 // check state length 668 if (state > stateLength) { 669 throw new AuthLoginException(bundleName, "invalidState", 670 new Object[]{new Integer(state)}); 671 } 672 673 // check callback length for the state 674 Callback[] ext = getCallback(state); 675 if (ext.length<=0) { 676 throw new AuthLoginException(bundleName, "invalidCallbackIndex", null); 677 } 678 679 // in internal, first Callback is always PagePropertiesCallback 680 if ((infoText != null) && (infoText.length() != 0)) { 681 PagePropertiesCallback pc = 682 (PagePropertiesCallback)((Callback[]) internal.get(state - 1))[0]; 683 684 // substitute string 685 List<String> infoTexts = pc.getInfoText(); 686 infoTexts.set(callback, infoText); 687 pc.setInfoText(infoTexts); 688 } 689 } 690 691 /** 692 * Clears the info text for a given callback state 693 * 694 * @param state The state to clear all infotexts 695 * @throws AuthLoginException Invalid state 696 * @supported.api 697 */ 698 public void clearInfoText(int state) 699 throws AuthLoginException { 700 if (debug.messageEnabled()) { 701 debug.message("clearInfoText : state=" + state); 702 } 703 704 // check state length 705 if (state > stateLength) { 706 throw new AuthLoginException(bundleName, "invalidState", 707 new Object[]{new Integer(state)}); 708 } 709 710 // check callback length for the state 711 Callback[] ext = getCallback(state); 712 if (ext.length<=0) { 713 throw new AuthLoginException(bundleName, "invalidCallbackIndex", null); 714 } 715 716 // in internal, first Callback is always PagePropertiesCallback 717 PagePropertiesCallback pc = 718 (PagePropertiesCallback)((Callback[]) internal.get(state - 1))[0]; 719 720 // clear info text 721 List<String> infoTexts = pc.getInfoText(); 722 723 for (int i = 0; i < infoTexts.size(); i++) { 724 infoTexts.set(i, EMPTY_STRING); 725 } 726 727 pc.setInfoText(infoTexts); 728 } 729 730 /** 731 * Use this method to replace the header text from the XML file with new 732 * text. This method can be used multiple times on the same state replacing 733 * text with new text each time. Useful for modules that control their own 734 * error handling. 735 * 736 * @param state state state in which the Callback[] to be reset 737 * @param header The text of the header to be replaced 738 * @throws AuthLoginException if state is out of bounds 739 * @supported.api 740 */ 741 public void substituteHeader(int state, String header) 742 throws AuthLoginException { 743 if (debug.messageEnabled()) { 744 debug.message("substituteHeader : state=" + state + ", header=" + 745 header); 746 } 747 // check state length 748 if (state > stateLength) { 749 throw new AuthLoginException(bundleName, "invalidState", 750 new Object[]{new Integer(state)}); 751 } 752 // check callback length for the state 753 Callback[] ext = getCallback(state); 754 if (ext.length<=0) { 755 throw new AuthLoginException(bundleName, "invalidCallbackIndex", 756 null); 757 } 758 759 // in internal, first Callback is always PagePropertiesCallback 760 if ((header!=null)&&(header.length() != 0)) { 761 PagePropertiesCallback pc = 762 (PagePropertiesCallback)((Callback[]) internal.get(state-1))[0]; 763 764 // substitute string 765 pc.setHeader(header); 766 } 767 } 768 769 /** 770 * Reset a Callback instance to the original Callback for the specified 771 * state and the specified index. This will override change to the Callback 772 * instance by the <code>replaceCallback()</code> method. 773 * @param state state in which the Callback[] to be reset 774 * @param index index order of the Callback in the Callback[], index starts 775 * with 0, i.e. 0 means first callback instance, 1 means 776 * the second callback instance. 777 * @throws AuthLoginException if state or index is out of bound. 778 * @supported.api 779 */ 780 public void resetCallback(int state, int index) 781 throws AuthLoginException { 782 if (debug.messageEnabled()) { 783 debug.message("resetCallback: state=" + state + ",index=" + index); 784 } 785 // check state length 786 if (state > stateLength) { 787 throw new AuthLoginException(bundleName, "invalidState", 788 new Object[]{new Integer(state)}); 789 } 790 // check callback length for the state 791 Callback[] ext = getCallback(state); 792 if (index < 0 || index >= ext.length) { 793 throw new AuthLoginException(bundleName, "invalidCallbackIndex", 794 new Object[]{new Integer(index)}); 795 } 796 797 // get the Callback from AMModuleProperties 798 // add one to index here since first one is the PagePropertiesCallback 799 Callback callback = ((Callback[]) origList.get(state-1))[index+1]; 800 Callback newCallback = null; 801 if (callback instanceof NameCallback) { 802 newCallback = new NameCallback( 803 ((NameCallback) callback).getPrompt()); 804 } else if (callback instanceof PasswordCallback) { 805 newCallback = new PasswordCallback( 806 ((PasswordCallback) callback).getPrompt(), 807 ((PasswordCallback) callback).isEchoOn()); 808 } else if (callback instanceof ChoiceCallback) { 809 int selection = ((ChoiceCallback) callback).getDefaultChoice(); 810 newCallback = new ChoiceCallback( 811 ((ChoiceCallback) callback).getPrompt(), 812 ((ChoiceCallback) callback).getChoices(), 813 selection, 814 ((ChoiceCallback) callback).allowMultipleSelections()); 815 ((ChoiceCallback) newCallback).setSelectedIndex(selection); 816 } else { 817 // should never come here since only above three will be supported 818 debug.error("Unsupported call back instance " + callback); 819 throw new AuthLoginException(bundleName, "unknownCallback", null); 820 } 821 822 if (debug.messageEnabled()) { 823 debug.message("original=" + callback + ",new=" + newCallback); 824 } 825 826 // set external & internal callback instance 827 ((Callback[]) internal.get(state-1))[index+1] = newCallback; 828 ((Callback[]) external.get(state-1))[index] = newCallback; 829 } 830 831 /** 832 * Implements initialize() method in JAAS LoginModule class. 833 * <p> 834 * The purpose of this method is to initialize Login Module, 835 * it will call the init() method implemented by user's Login 836 * Module to do initialization. 837 * <p> 838 * This is a final method. 839 * @param subject - the Subject to be authenticated. 840 * @param callbackHandler - a CallbackHandler for communicating with the 841 * end user (prompting for usernames and passwords, for example). 842 * @param sharedState - state shared with other configured LoginModules. 843 * @param options - options specified in the login Configuration for this 844 * particular LoginModule. 845 */ 846 public final void initialize(Subject subject, 847 CallbackHandler callbackHandler, 848 java.util.Map sharedState, 849 java.util.Map options) { 850 this.subject = subject; 851 this.handler = callbackHandler; 852 this.sharedState = sharedState; 853 this.options = options; 854 // get class name 855 String className = this.getClass().getName(); 856 int index = className.lastIndexOf("."); 857 moduleClass = className.substring(index + 1); 858 moduleName = (String) options.get(ISAuthConstants. 859 MODULE_INSTANCE_NAME); 860 861 // get module properties file path 862 863 loginState = getLoginState(); 864 865 fileName = loginState.getFileName(moduleClass+ ".xml"); 866 867 // get resource bundle 868 869 bundle = amCache.getResBundle(bundleName, getLoginLocale()); 870 871 if (debug.messageEnabled()) { 872 debug.message("AMLoginModule resbundle locale="+getLoginLocale()); 873 debug.message("Login, class = " + className + 874 ", module=" + moduleName + ", file=" + fileName); 875 } 876 isSharedState = Boolean.valueOf(CollectionHelper.getMapAttr( 877 options, ISAuthConstants.SHARED_STATE_ENABLED, "false") 878 ).booleanValue(); 879 880 isStore = Boolean.valueOf(CollectionHelper.getMapAttr( 881 options, ISAuthConstants.STORE_SHARED_STATE_ENABLED, "true") 882 ).booleanValue(); 883 884 sharedStateBehaviorPattern = Misc.getMapAttr(options, 885 ISAuthConstants.SHARED_STATE_BEHAVIOR_PATTERN, 886 "tryFirstPass"); 887 888 if (debug.messageEnabled()) { 889 debug.message("AMLoginModule" + 890 ISAuthConstants.SHARED_STATE_BEHAVIOR_PATTERN + 891 " is set to " + sharedStateBehaviorPattern); 892 } 893 894 // Check for composite Advice 895 String compositeAdvice = loginState.getCompositeAdvice(); 896 if (compositeAdvice != null) { 897 if (debug.messageEnabled()) { 898 debug.message("AMLoginModule.initialize: " 899 + "Adding Composite Advice " + compositeAdvice); 900 } 901 sharedState.put(ISAuthConstants.COMPOSITE_ADVICE_XML, 902 compositeAdvice); 903 } 904 // call customer init method 905 init(subject, sharedState, options); 906 } 907 908 /** 909 * Initialize this LoginModule. 910 * <p> 911 * This is an abstract method, must be implemented by user's Login Module 912 * to initialize this LoginModule with the relevant information. If this 913 * LoginModule does not understand any of the data stored in sharedState 914 * or options parameters, they can be ignored. 915 * @param subject - the Subject to be authenticated. 916 * @param sharedState - state shared with other configured LoginModules. 917 * @param options - options specified in the login Configuration for this 918 * particular LoginModule. It contains all the global and organization 919 * attribute configuration for this module. The key of the map is the 920 * attribute name (e.g. <code>iplanet-am-auth-ldap-server</code>) as 921 * String, the value is the value of the corresponding attribute as Set. 922 * @supported.api 923 */ 924 abstract public void init(Subject subject, 925 java.util.Map sharedState, 926 java.util.Map options); 927 928 /** 929 * Abstract method must be implemented by each login module to 930 * control the flow of the login process. 931 * <p> 932 * This method takes an array of sbumitted 933 * Callback, process them and decide the order of next state to go. 934 * Return -1 if the login is successful, return 0 if the 935 * LoginModule should be ignored. 936 * @param callbacks Callback[] for this Login state 937 * @param state Order of state. State order starts with 1. 938 * @return order of next state. return -1 if authentication 939 * is successful, return 0 if the LoginModule should be ignored. 940 * @exception LoginException if login fails. 941 * @supported.api 942 */ 943 abstract public int process(Callback[] callbacks, int state) 944 throws LoginException; 945 946 /** 947 * Abstract method must be implemeted by each login module to 948 * get the user Principal 949 * @return Principal 950 * @supported.api 951 */ 952 abstract public java.security.Principal getPrincipal(); 953 954 /** 955 * This method should be overridden by each login module 956 * to destroy dispensable state fields. 957 * 958 * @supported.api 959 */ 960 public void destroyModuleState(){}; 961 962 /** 963 * This method should be overridden by each login module 964 * to do some garbage collection work after the module 965 * process is done. Typically those class wide global variables 966 * that will not be used again until a logout call should be nullified. 967 */ 968 public void nullifyUsedVars() {}; 969 970 /** 971 * Wrapper for process() to utilize AuthLoginException. 972 * @param callbacks associated with authentication 973 * @param state of callbacks 974 * @return state of auth login 975 * @exception AuthLoginException if login fails. 976 */ 977 private int wrapProcess(Callback[] callbacks, int state) 978 throws AuthLoginException { 979 try { 980 if (callbacks != null) { 981 for (int i = 0; i < callbacks.length; i++) { 982 if (callbacks[i] instanceof NameCallback) { 983 String newUser = null; 984 try { 985 newUser = IdUtils.getIdentityName( 986 ((NameCallback) callbacks[i]).getName(), 987 getRequestOrg()); 988 } catch (IdRepoException idRepoExp) { 989 //Print message and let Auth proceed. 990 debug.message( 991 "AMLoginModule.wrapProcess: Cannot get "+ 992 "username from idrepo. ", idRepoExp); 993 } 994 if (newUser != null) { 995 ((NameCallback) callbacks[i]).setName(newUser); 996 } 997 } 998 } 999 } 1000 return process(callbacks, state); 1001 } catch (LoginException e) { 1002 currentState = ISAuthConstants.LOGIN_IGNORE; 1003 setFailureModuleName(moduleName); 1004 if (e instanceof InvalidPasswordException){ 1005 setFailureID(((InvalidPasswordException)e).getTokenId()); 1006 throw new InvalidPasswordException(e); 1007 } else { 1008 throw new AuthLoginException(e); 1009 } 1010 } catch (RuntimeException re) { 1011 currentState = ISAuthConstants.LOGIN_IGNORE; 1012 setFailureModuleName(moduleName); 1013 //rethrow the exception 1014 throw re; 1015 } 1016 } 1017 1018 /** 1019 * Returns true if a module in authentication chain has already done, either 1020 * succeeded or failed. 1021 * 1022 * @return true if a module in authentication chain has already done, either 1023 * succeeded or failed. 1024 */ 1025 private boolean moduleHasDone() { 1026 return (currentState == ISAuthConstants.LOGIN_SUCCEED) || 1027 (currentState == ISAuthConstants.LOGIN_IGNORE); 1028 } 1029 1030 /** 1031 * Implements login() method in JAAS LoginModule class. 1032 * <p> 1033 * This method is responsible for retrieving corresponding Callback[] for 1034 * current state, send as requirement to user, get the submitted Callback[], 1035 * call the process() method. The process() method will decide the next 1036 * action based on those submitted Callback[]. 1037 * <p> 1038 * This method is final. 1039 * @return <code>true</code> if the authentication succeeded, or 1040 * <code>false</code> if this LoginModule should be ignored. 1041 * @throws AuthLoginException - if the authentication fails 1042 */ 1043 public final boolean login() throws AuthLoginException { 1044 if (moduleHasDone()) { 1045 debug.message("This module has already done."); 1046 if (currentState == ISAuthConstants.LOGIN_SUCCEED) { 1047 return true; 1048 } else { 1049 return false; 1050 } 1051 } else { 1052 if (debug.messageEnabled()) { 1053 debug.message("This module is not done yet. CurrentState: " 1054 + currentState); 1055 } 1056 } 1057 1058 // make one getCallback call to populate first state 1059 // this will set the noCallbacks variable 1060 if (internal == null) { 1061 getCallback(1); 1062 } 1063 // if this module does not define any Callbacks (such as Cert), 1064 // pass control right to module, then check return code from module 1065 if (noCallbacks) { 1066 currentState = wrapProcess(EMPTY_CALLBACK, 1); 1067 // check login status 1068 if (currentState == ISAuthConstants.LOGIN_SUCCEED) { 1069 setSuccessModuleName(moduleName); 1070 succeeded = true; 1071 nullifyUsedVars(); 1072 return true; 1073 } else if (currentState == ISAuthConstants.LOGIN_IGNORE) { 1074 // index = 0; 1075 setFailureModuleName(moduleName); 1076 succeeded = false; 1077 destroyModuleState(); 1078 principal = null; 1079 return false; 1080 } else { 1081 setFailureModuleName(moduleName); 1082 succeeded = false; 1083 cleanup(); 1084 throw new AuthLoginException(bundleName, "invalidCode", 1085 new Object[]{new Integer(currentState)}); 1086 } 1087 } 1088 1089 if (handler == null) { 1090 debug.error("Handler is null"); 1091 throw new AuthLoginException(bundleName, "nullHandler", null); 1092 } 1093 try { 1094 Callback[] lastCallbacks = null; 1095 boolean needToExit = false; 1096 1097 // starting from first page 1098 //currentState = 1; 1099 while (currentState != ISAuthConstants.LOGIN_SUCCEED && 1100 currentState != ISAuthConstants.LOGIN_IGNORE) { 1101 if (debug.messageEnabled()) { 1102 debug.message("Login, state = " + currentState); 1103 } 1104 if (isSharedState) { 1105 currentState = wrapProcess(EMPTY_CALLBACK, 1); 1106 isSharedState = false; 1107 continue; 1108 } 1109 // get current set of callbacks 1110 getCallback(currentState); 1111 // check if this is an error state, if so, throw exception 1112 // to terminate login process 1113 Callback[] cbks = ((Callback[]) internal.get(currentState-1)); 1114 PagePropertiesCallback callback = 1115 (PagePropertiesCallback) cbks[0]; 1116 1117 if (callback.getErrorState()) { 1118 // this is an error state 1119 setFailureModuleName(moduleName); 1120 String template = callback.getTemplateName(); 1121 String errorMessage = callback.getHeader(); 1122 if (template == null || template.length() == 0) { 1123 // this is the case which no error template is 1124 // defined, only exception message in header 1125 throw new MessageLoginException(errorMessage); 1126 } else { 1127 // send error template 1128 //setLoginFailureURL(template); 1129 setModuleErrorTemplate(template); 1130 throw new AuthLoginException(errorMessage); 1131 } 1132 } 1133 // call handler to handle the internal callbacks 1134 handler.handle(cbks); 1135 1136 // Get the page state from the PagePropertiesCallback 1137 Callback[] cbksPrev= ((Callback[])internal.get(currentState-1)); 1138 1139 PagePropertiesCallback callbackPrev = 1140 (PagePropertiesCallback) cbksPrev[0]; 1141 String pageState = callbackPrev.getPageState(); 1142 if ((pageState != null) && 1143 (pageState.length() != 0) && 1144 (!pageState.equals(Integer.toString(currentState)))) { 1145 int loginPage = Integer.parseInt(pageState); 1146 1147 //Set the current page state in PagePropertiesCallback 1148 callbackPrev.setPageState(Integer.toString(currentState)); 1149 1150 currentState = loginPage; 1151 if (debug.messageEnabled()) { 1152 debug.message("currentState from UI " + currentState); 1153 } 1154 } 1155 1156 // Get the last submitted callbacks to auth module and submit 1157 // those callbacks to do DataStore authentication if the incoming 1158 // user is special / internal user and auth module is other than 1159 // "DataStore" and "Application" auth modules. 1160 lastCallbacks = (Callback[])external.get(currentState-1); 1161 if ((!moduleName.equalsIgnoreCase("DataStore")) && 1162 (!moduleName.equalsIgnoreCase("Application"))) { 1163 if (!authenticateToDatastore(lastCallbacks)) { 1164 needToExit = true; 1165 break; 1166 } 1167 } 1168 1169 // send external callback and send to module for processing 1170 currentState = wrapProcess((Callback[]) 1171 external.get(currentState-1), currentState); 1172 1173 if (debug.messageEnabled()) { 1174 debug.message("Login NEXT State : " + currentState); 1175 } 1176 } 1177 1178 if (needToExit) { 1179 nullifyUsedVars(); 1180 throw new AuthLoginException(AMAuthErrorCode.AUTH_MODULE_DENIED); 1181 } 1182 1183 // check login status 1184 if (currentState == ISAuthConstants.LOGIN_SUCCEED) { 1185 setSuccessModuleName(moduleName); 1186 succeeded = true; 1187 nullifyUsedVars(); 1188 return true; 1189 } else { 1190 // currentState = 0; 1191 setFailureModuleName(moduleName); 1192 succeeded = false; 1193 destroyModuleState(); 1194 principal = null; 1195 return false; 1196 } 1197 } catch (IOException e) { 1198 setFailureModuleName(moduleName); 1199 if (e.getMessage().equals(AMAuthErrorCode.AUTH_TIMEOUT)) { 1200 debug.message("login timed out ", e); 1201 } else { 1202 debug.message("login ", e); 1203 } throw new AuthLoginException(e); 1204 } catch (UnsupportedCallbackException e) { 1205 setFailureModuleName(moduleName); 1206 debug.message("Login", e); 1207 throw new AuthLoginException(e); 1208 } 1209 } 1210 1211 /** 1212 * Returns authentication level that has been set for the module 1213 * 1214 * @return authentication level of this authentication session 1215 * @supported.api 1216 */ 1217 public int getAuthLevel() { 1218 // get login state for this authentication session 1219 if (loginState == null) { 1220 loginState = getLoginState(); 1221 if (loginState == null) { 1222 return 0; 1223 } 1224 } 1225 return loginState.getAuthLevel(); 1226 } 1227 1228 /** 1229 * Sets the <code>AuthLevel</code> for this session. 1230 * The authentication level being set cannot be downgraded 1231 * below that set by the module configuration. 1232 * 1233 * @param auth_level authentication level string to be set 1234 * @return <code>true</code> if setting is successful,<code>false</code> 1235 * otherwise 1236 * @supported.api 1237 */ 1238 public boolean setAuthLevel(int auth_level) { 1239 // get login state for this authentication session 1240 if (loginState == null) { 1241 loginState = getLoginState(); 1242 if (loginState == null) { 1243 // may be should throw AuthLoginException here 1244 debug.error("Unable to set auth level : " + auth_level); 1245 return false; 1246 } 1247 } 1248 loginState.setModuleAuthLevel(auth_level); 1249 return true; 1250 } 1251 1252 /** 1253 * Returns the current state in the authentication process. 1254 * 1255 * @return the current state in the authentication process. 1256 * @supported.api 1257 */ 1258 public int getCurrentState() { 1259 return currentState; 1260 } 1261 1262 /** 1263 * Returns the <code>HttpServletRequest</code> object that 1264 * initiated the call to this module. 1265 * 1266 * @return <code>HttpServletRequest</code> for this request, returns null 1267 * if the <code>HttpServletRequest</code> object could not be 1268 * obtained. 1269 * @supported.api 1270 */ 1271 public HttpServletRequest getHttpServletRequest() { 1272 // get login state for this authentication session 1273 if (loginState == null) { 1274 loginState = getLoginState(); 1275 if (loginState == null) { 1276 return null; 1277 } 1278 } 1279 return loginState.getHttpServletRequest(); 1280 } 1281 1282 /** 1283 * Returns the authentication <code>LoginState</code> 1284 * @param methodName Name of the required methd in 1285 * <code>LoginState</code> object 1286 * @return <code>com.sun.identity.authentication.service.LoginState</code> 1287 * for this authentication method. 1288 * @throws AuthLoginException if fails to get the Login state 1289 */ 1290 protected com.sun.identity.authentication.service.LoginState getLoginState( 1291 String methodName) throws AuthLoginException { 1292 if (loginState == null) { 1293 loginState = getLoginState(); 1294 if (loginState == null) { 1295 throw new AuthLoginException(bundleName, "wrongCall", 1296 new Object[]{methodName}); 1297 } 1298 } 1299 return loginState; 1300 } 1301 1302 /** 1303 * Returns the Login <code>Locale</code> for this session 1304 * @return <code>Locale</code> used for localizing text 1305 */ 1306 protected java.util.Locale getLoginLocale() { 1307 try { 1308 String loc = getLocale(); 1309 return com.sun.identity.shared.locale.Locale.getLocale(loc); 1310 } catch (AuthLoginException ex) { 1311 debug.message("unable to determine loginlocale ", ex); 1312 return java.util.Locale.ENGLISH; 1313 } 1314 } 1315 1316 /* 1317 * Returns the Login State object 1318 * @return com.sun.identity.authentication.service.LoginState 1319 */ 1320 private com.sun.identity.authentication.service.LoginState getLoginState() { 1321 Callback[] callbacks = new Callback[1]; 1322 try { 1323 callbacks[0] = new LoginStateCallback(); 1324 if (handler == null) { 1325 return null; 1326 } 1327 handler.handle(callbacks); 1328 return ((LoginStateCallback) callbacks[0]).getLoginState(); 1329 } catch (Exception e) { 1330 debug.message("Error.." ,e ); 1331 return null; 1332 } 1333 } 1334 1335 /** 1336 * Returns the <code>HttpServletResponse</code> object for the servlet 1337 * request that initiated the call to this module. The servlet response 1338 * object will be the response to the <code>HttpServletRequest</code> 1339 * received by the authentication module. 1340 * 1341 * @return <code>HttpServletResponse</code> for this request, returns null 1342 * if the <code>HttpServletResponse</code> object could not be obtained. 1343 * @supported.api 1344 */ 1345 public HttpServletResponse getHttpServletResponse() { 1346 // get login state for this authentication session 1347 if (loginState == null) { 1348 loginState = getLoginState(); 1349 if (loginState == null) { 1350 return null; 1351 } 1352 } 1353 return loginState.getHttpServletResponse(); 1354 } 1355 1356 /** 1357 * Returns the CallbackHandler object for the module. This method 1358 * will be used internally. 1359 * 1360 * @return CallbackHandler for this request, returns null if the 1361 * CallbackHandler object could not be obtained. 1362 */ 1363 public CallbackHandler getCallbackHandler() { 1364 return handler; 1365 } 1366 1367 /** 1368 * Returns the locale for this authentication session. 1369 * 1370 * @return <code>java.util.Locale</code> locale for this authentication 1371 * session. 1372 * @throws AuthLoginException if problem in accessing the 1373 locale. 1374 * @supported.api 1375 */ 1376 public String getLocale() throws AuthLoginException { 1377 // get login state for this authentication session 1378 return getLoginState("getLocale()").getLocale(); 1379 } 1380 1381 /** 1382 * Returns the number of authentication states for this 1383 * login module. 1384 * 1385 * @return the number of authentication states for this login module. 1386 * @supported.api 1387 */ 1388 public int getNumberOfStates() { 1389 return stateLength; 1390 } 1391 1392 /** 1393 * Returns the organization DN for this authentication session. 1394 * 1395 * @return organization DN. 1396 * @supported.api 1397 */ 1398 public String getRequestOrg() { 1399 // get login state for this authentication session 1400 if (loginState == null) { 1401 loginState = getLoginState(); 1402 if (loginState == null) { 1403 return null; 1404 } 1405 } 1406 return loginState.getOrgDN(); 1407 } 1408 1409 /** 1410 * Returns a unique key for this authentication session. 1411 * This key will be unique throughout an entire Web browser session. 1412 * 1413 * @return null is unable to get the key, 1414 * @supported.api 1415 */ 1416 public String getSessionId() { 1417 // get login state for this authentication session 1418 if (loginState == null) { 1419 loginState = getLoginState(); 1420 if (loginState == null) { 1421 return null; 1422 } 1423 } 1424 return loginState.getSid().toString(); 1425/* 1426 InternalSession sess = loginState.getSession(); 1427 if (sess != null) { 1428 return sess.getID().toString(); 1429 } else { 1430 return null; 1431 } 1432*/ 1433 } 1434 1435 /** 1436 * Returns the organization attributes for specified organization. 1437 * 1438 * @param orgDN Requested organization DN. 1439 * @return Map that contains all attribute key/value pairs defined 1440 * in the organization. 1441 * @throws AuthLoginException if cannot get organization profile. 1442 * @supported.api 1443 */ 1444 public Map getOrgProfile(String orgDN) throws AuthLoginException { 1445 Map orgMap = null; 1446 if (orgDN == null || orgDN.length() == 0) { 1447 // get login state for this authentication session 1448 orgDN = getLoginState("getOrgProfile(String)").getOrgDN(); 1449 } 1450 1451 try { 1452 OrganizationConfigManager orgConfigMgr = 1453 AuthD.getAuth().getOrgConfigManager(orgDN); 1454 orgMap = orgConfigMgr.getAttributes( 1455 ISAuthConstants.IDREPO_SVC_NAME); 1456 if (debug.messageEnabled()) { 1457 debug.message("orgMap is : " + orgMap); 1458 } 1459 } catch (Exception ex) { 1460 debug.message("getOrgProfile", ex); 1461 throw new AuthLoginException(ex); 1462 } 1463 return orgMap; 1464 } 1465 1466 /** 1467 * Returns service template attributes defined for the specified 1468 * organization. 1469 * 1470 * @param orgDN Organization DN. 1471 * @param serviceName Requested service name. 1472 * @return Map that contains all attribute key/value pairs defined in the 1473 * organization service template. 1474 * @throws AuthLoginException if cannot get organization service 1475 * template. 1476 * @supported.api 1477 */ 1478 public Map getOrgServiceTemplate(String orgDN, String serviceName) 1479 throws AuthLoginException { 1480 Map orgMap = null; 1481 if (orgDN == null || orgDN.length() == 0) { 1482 // get login state for this authentication session 1483 orgDN = getLoginState( 1484 "getOrgServiceTemplate(String, String)").getOrgDN(); 1485 } 1486 try { 1487 OrganizationConfigManager orgConfigMgr = 1488 AuthD.getAuth().getOrgConfigManager(orgDN); 1489 orgMap = orgConfigMgr.getServiceConfig(serviceName).getAttributes(); 1490 } 1491 catch (Exception ex) { 1492 debug.message("getOrgServiceTemplate", ex); 1493 throw new AuthLoginException(ex); 1494 } 1495 return orgMap; 1496 } 1497 1498 /** 1499 * Checks if persistent cookie is on. 1500 * 1501 * @return <code>true</code> if persistent cookie is set. 1502 * @supported.api 1503 */ 1504 public boolean getPersistentCookieOn() { 1505 // get login state for this authentication session 1506 if (loginState == null) { 1507 loginState = getLoginState(); 1508 if (loginState == null) { 1509 return false; 1510 } 1511 } 1512 return loginState.getPersistentCookieMode(); 1513 } 1514 1515 /** 1516 * Checks if dynamic profile creation is enabled. 1517 * 1518 * @return <code>true</code> if dynamic profile creation is enabled. 1519 */ 1520 public boolean isDynamicProfileCreationEnabled() { 1521 // get login state for this authentication session 1522 if (loginState == null) { 1523 loginState = getLoginState(); 1524 if (loginState == null) { 1525 return false; 1526 } 1527 } 1528 return loginState.isDynamicProfileCreationEnabled(); 1529 } 1530 1531 /** 1532 * Attempts to set the Persistent Cookie for this session. Can be called 1533 * from any state in the authentication module. It will return whether 1534 * "Core Authentication" will add the persistent cookie (name is specified 1535 * in the <code>/etc/opt/SUNWam/config/AMConfig.properties</code>: 1536 * <code>com.iplanet.am.pcookie.name</code> property) 1537 * 1538 * @return <code>true</code> when setting is successful, <code>false</code> 1539 * if the persistent cookie mode attribute is not set for the 1540 * organization. 1541 * @supported.api 1542 */ 1543 public boolean setPersistentCookieOn() { 1544 // get login state for this authentication session 1545 if (loginState == null) { 1546 loginState = getLoginState(); 1547 if (loginState == null) { 1548 return false; 1549 } 1550 } 1551 1552 if (!loginState.getPersistentCookieMode()) { 1553 return false; 1554 } 1555 1556 loginState.setPersistentCookieOn(); 1557 return true; 1558 } 1559 1560 /** 1561 * Returns service configuration attributes. 1562 * @param name Requested service name. 1563 * @return Map that contains all attribute key/value pairs defined in 1564 * the service configuration. 1565 * @throws AuthLoginException if error in accessing the service schema. 1566 * 1567 * @supported.api 1568 */ 1569 public Map getServiceConfig(String name) throws AuthLoginException { 1570 try { 1571 ServiceSchemaManager scm = new ServiceSchemaManager(name, 1572 AuthD.getAuth().getSSOAuthSession()); 1573 ServiceSchema sc = scm.getGlobalSchema(); 1574 HashMap retMap = new HashMap(); 1575 if (sc != null) { 1576 retMap.putAll(sc.getAttributeDefaults()); 1577 } 1578 1579 sc = scm.getOrganizationSchema(); 1580 if (sc != null) { 1581 retMap.putAll(sc.getAttributeDefaults()); 1582 } 1583 1584 sc = scm.getUserSchema(); 1585 if (sc != null) { 1586 retMap.putAll(sc.getAttributeDefaults()); 1587 } 1588 1589 sc = scm.getPolicySchema(); 1590 if (sc != null) { 1591 retMap.putAll(sc.getAttributeDefaults()); 1592 } 1593 1594 return retMap; 1595 } catch (Exception ex) { 1596 debug.message("getServiceConfig", ex); 1597 throw new AuthLoginException(ex); 1598 } 1599 } 1600 1601 /** 1602 * Returns the user profile for the user specified. This 1603 * method may only be called in the validate() method. 1604 * 1605 * @param userDN distinguished name os user. 1606 * @return <code>AMUser</code> object for the user's distinguished name. 1607 * @throws AuthLoginException if it fails to get the user profile for 1608 * <code>userDN</code>. 1609 * @deprecated This method has been deprecated. Please use the 1610 * IdRepo API's to get the AMIdentity object for the user. More 1611 * information on how to use the Identity Repository APIs is 1612 * available in the OpenSSO Developer's Guide. 1613 * 1614 * @supported.api 1615 */ 1616 public AMUser getUserProfile(String userDN) throws AuthLoginException{ 1617 AMUser user = null; 1618 try { 1619 user = AuthD.getAuth().getSDK().getUser(userDN); 1620 } catch (Exception ex) { 1621 debug.message("getUserProfile", ex); 1622 throw new AuthLoginException(ex); 1623 } 1624 return user; 1625 } 1626 1627 /** 1628 * Returns the property from the user session. If the session is being force 1629 * upgraded then set on the old session otherwise set on the current session. 1630 * 1631 * @param name The property name. 1632 * @return The property value. 1633 * @throws AuthLoginException if the user session is invalid. 1634 * 1635 * @supported.api 1636 */ 1637 public String getUserSessionProperty(String name) 1638 throws AuthLoginException { 1639 InternalSession sess = null; 1640 1641 if (getLoginState(null).isSessionUpgrade() && 1642 getLoginState(null).getForceFlag()) { 1643 sess = getLoginState(null).getOldSession(); 1644 } else { 1645 sess = getLoginState("getUserSessionProperty()").getSession(); 1646 } 1647 1648 if (sess != null) { 1649 return sess.getProperty(name); 1650 } else { 1651 return null; 1652 } 1653 } 1654 1655 /** 1656 * Sets a property in the user session. If the session is being force 1657 * upgraded then set on the old session otherwise set on the current session. 1658 * 1659 * @param name The property name. 1660 * @param value The property value. 1661 * @throws AuthLoginException if the user session is invalid. 1662 * 1663 * @supported.api 1664 */ 1665 public void setUserSessionProperty(String name, String value) 1666 throws AuthLoginException { 1667 InternalSession sess = null; 1668 1669 if (getLoginState(null).isSessionUpgrade() && 1670 getLoginState(null).getForceFlag()) { 1671 sess = getLoginState(null).getOldSession(); 1672 } else { 1673 sess = getLoginState("setUserSessionProperty()").getSession(); 1674 } 1675 1676 if (sess != null) { 1677 sess.putProperty(name, value); 1678 } else { 1679 throw new AuthLoginException(bundleName, "wrongCall", 1680 new Object[]{" setUserSessionProperty()"}); 1681 } 1682 } 1683 1684 /** 1685 * Returns a set of user IDs generated from the class defined 1686 * in the Core Authentication Service. Returns null if the 1687 * attribute <code>iplanet-am-auth-username-generator-enabled</code> is 1688 * set to false. 1689 * 1690 * @param attributes the keys in the <code>Map</code> contains the 1691 * attribute names and their corresponding values in 1692 * the <code>Map</code> is a <code>Set</code> that 1693 * contains the values for the attribute 1694 * @param num the maximum number of returned user IDs; 0 means there 1695 * is no limit 1696 * @return a set of auto-generated user IDs 1697 * @throws AuthLoginException if the class instantiation failed 1698 * 1699 * @supported.api 1700 */ 1701 public Set getNewUserIDs(Map attributes, int num) 1702 throws AuthLoginException { 1703 boolean enabled = getLoginState( 1704 "getNewUserIDs(Map, int)").userIDGeneratorEnabled; 1705 1706 if (!enabled) { 1707 return null; 1708 } 1709 1710 String className = getLoginState( 1711 "getNewUserIDs(Map, int)").userIDGeneratorClassName; 1712 String orgDN = getLoginState("getNewUserIDs(Map, int)").getOrgDN(); 1713 1714 // if className is null or empty, use the default user ID 1715 // generator class name 1716 if (className == null || className.length() == 0) { 1717 className = ISAuthConstants.DEFAULT_USERID_GENERATOR_CLASS; 1718 } 1719 1720 UserIDGenerator idGenerator = null; 1721 try { 1722 // instantiate the Java class 1723 Class theClass = Class.forName(className); 1724 idGenerator = (UserIDGenerator)theClass.newInstance(); 1725 1726 } catch (Exception e) { 1727 debug.message("getNewUserIDs(): unable to instantiate " + 1728 className, e); 1729 return null; 1730 } 1731 1732 return (idGenerator.generateUserIDs(orgDN, attributes, num)); 1733 } 1734 1735 /** 1736 * Sets the the login failure URL for the user. This method does not 1737 * change the URL in the user's profile. When the user authenticates 1738 * failed, this URL will be used by the authentication for the 1739 * redirect. 1740 * 1741 * @param url URL to go when authentication failed. 1742 * @throws AuthLoginException if unable to set the URL. 1743 * 1744 * @supported.api 1745 */ 1746 public void setLoginFailureURL(String url) throws AuthLoginException { 1747 getLoginState("setLoginFailureURL()").setFailureLoginURL(url); 1748 } 1749 1750 /** 1751 * Sets the error template for the module 1752 * @param templateName the error template for the module 1753 * @throws AuthLoginException when unable to set the template 1754 */ 1755 public void setModuleErrorTemplate(String templateName) 1756 throws AuthLoginException { 1757 getLoginState( 1758 "setModuleTemplate()").setModuleErrorTemplate(templateName); 1759 } 1760 1761 /** 1762 * Sets the the login successful URL for the user. This method does not 1763 * change the URL in the user's profile. When the user authenticates 1764 * successfully, this URL will be used by the authentication for the 1765 * redirect. 1766 * 1767 * @param url <code>URL</code> to go when authentication is successful. 1768 * @throws AuthLoginException if unable to set the URL. 1769 * @supported.api 1770 */ 1771 public void setLoginSuccessURL(String url) throws AuthLoginException { 1772 getLoginState("setLoginSuccessURL()").setSuccessLoginURL(url); 1773 } 1774 1775 /** 1776 * Sets the user organization. This method should only be called when the 1777 * user authenticates successfully. It allows the user authentication 1778 * module to decide in which domain the user profile should be created. 1779 * 1780 * @param orgDN The organization DN. 1781 * @throws AuthLoginException 1782 */ 1783 public void setOrg(String orgDN) throws AuthLoginException { 1784/* TODO 1785 if (orgDN.indexOf("=") == -1) { 1786 throw new AuthLoginException(bundleName, "invalidDN", 1787 new Object[]{orgDN}); 1788 } 1789 getLoginState("setOrg()").setOrg(orgDN); 1790 */ 1791 return; 1792 } 1793 1794 /** 1795 * Checks if a Callback is required to have input. 1796 * @param state Order of state. 1797 * @param index Order of the Callback in the Callback[], the index. 1798 * starts with 0. 1799 * @return <code>true</code> if the callback corresponding to the number 1800 * in the specified state is required to have value, 1801 * <code>false</code> otherwise 1802 * @supported.api 1803 */ 1804 public boolean isRequired(int state, int index) { 1805 // check state 1806 if (state > stateLength) { 1807 // invalid state, return false now 1808 return false; 1809 } 1810 // get internal callbacks for the state 1811 Callback[] callbacks = (Callback[]) internal.get(state - 1); 1812 if (callbacks == null || callbacks.length == 0) { 1813 // no callbacks defined for this state, return false 1814 return false; 1815 } 1816 // check first Callback 1817 Callback callback = callbacks[0]; 1818 if (callback instanceof PagePropertiesCallback) { 1819 List req = ((PagePropertiesCallback) callback).getRequire(); 1820 if (req == null || req.isEmpty() || index >= req.size()) { 1821 return false; 1822 } else { 1823 String tmp = (String) req.get(index); 1824 if (tmp.equalsIgnoreCase("true")) { 1825 return true; 1826 } else { 1827 return false; 1828 } 1829 } 1830 } else { 1831 return false; 1832 } 1833 } 1834 1835 /** 1836 * Returns the info text associated with a specific callback 1837 * 1838 * @param state The state to fetch the info text 1839 * @param index The callback to fetch the info text 1840 * @return The info text 1841 * @supported.api 1842 */ 1843 public String getInfoText(int state, int index) { 1844 // check state 1845 if (state > stateLength) { 1846 // invalid state, return empty string now 1847 return EMPTY_STRING; 1848 } 1849 // get internal callbacks for the state 1850 Callback[] callbacks = (Callback[]) internal.get(state - 1); 1851 if (callbacks == null || callbacks.length == 0) { 1852 // no callbacks defined for this state, return empty string 1853 return EMPTY_STRING; 1854 } 1855 // check first Callback 1856 Callback callback = callbacks[0]; 1857 if (callback instanceof PagePropertiesCallback) { 1858 List<String> infoText = ((PagePropertiesCallback) callback).getAttribute(); 1859 if (infoText == null || infoText.isEmpty() || index >= infoText.size()) { 1860 return EMPTY_STRING; 1861 } else { 1862 return infoText.get(index); 1863 } 1864 } else { 1865 return EMPTY_STRING; 1866 } 1867 } 1868 1869 /** 1870 * Returns the attribute name for the specified callback in the 1871 * specified login state. 1872 * 1873 * @param state Order of state 1874 * @param index Order of the Callback in the Callback[], the index 1875 * starts with 0. 1876 * @return Name of the attribute, empty string will be returned 1877 * if the attribute is not defined. 1878 * @supported.api 1879 */ 1880 public String getAttribute(int state, int index) { 1881 // check state 1882 if (state > stateLength) { 1883 // invalid state, return empty string now 1884 return EMPTY_STRING; 1885 } 1886 // get internal callbacks for the state 1887 Callback[] callbacks = (Callback[]) internal.get(state - 1); 1888 if (callbacks == null || callbacks.length == 0) { 1889 // no callbacks defined for this state, return empty string 1890 return EMPTY_STRING; 1891 } 1892 // check first Callback 1893 Callback callback = callbacks[0]; 1894 if (callback instanceof PagePropertiesCallback) { 1895 List req = ((PagePropertiesCallback) callback).getAttribute(); 1896 if (req == null || req.isEmpty() || index >= req.size()) { 1897 return EMPTY_STRING; 1898 } else { 1899 return (String) req.get(index); 1900 } 1901 } else { 1902 return EMPTY_STRING; 1903 } 1904 } 1905 1906 /** 1907 * Aborts the authentication process. 1908 * <p> 1909 * This JAAS LoginModule method must be implemented by user's module. 1910 * <p> 1911 * This method is called if the overall authentication 1912 * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL 1913 * LoginModules did not succeed). 1914 * If this LoginModule's own authentication attempt succeeded (checked by 1915 * retrieving the private state saved by the login method), then this 1916 * method cleans up any state that was originally saved. 1917 * 1918 * @return <code>true</code> if this method succeeded,<code>false</code> 1919 * if this LoginModule should be ignored. 1920 * @throws AuthLoginException if the abort fails 1921 * @see javax.security.auth.spi.LoginModule#abort 1922 */ 1923 public final boolean abort() throws AuthLoginException { 1924 debug.message("ABORT return.... false"); 1925 if (succeeded == false) { 1926 return false; 1927 } else { 1928 logout(); 1929 } 1930 return true; 1931 } 1932 1933 /** 1934 * Commit the authentication process (phase 2). 1935 * <p> 1936 * This JAAS LoginModule method must be implemented by user's module. 1937 * <p> 1938 * This method is called if the overall authentication 1939 * succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL 1940 * LoginModules succeeded). 1941 * <p> 1942 * If this LoginModule's own authentication attempt succeeded (checked by 1943 * retrieving the private state saved by the login method), then this 1944 * method associates relevant Principals and Credentials with the Subject 1945 * located in the LoginModule. If this LoginModule's own authentication 1946 * attempted failed, then this method removes/destroys any state that was 1947 * originally saved. 1948 * 1949 * @return <code>true</code> if this method succeeded, or <code>false</code> 1950 * if this <code>LoginModule</code> should be ignored. 1951 * @throws AuthLoginException if the commit fails 1952 * @see javax.security.auth.spi.LoginModule#commit 1953 */ 1954 public final boolean commit() throws AuthLoginException { 1955 principal = getPrincipal(); 1956 if (debug.messageEnabled()) { 1957 debug.message( 1958 "AMLoginModule.commit():Succeed,principal=" + principal); 1959 } 1960 if (succeeded == false || principal == null) { 1961 return false; 1962 } else if (!subject.getPrincipals().contains(principal)) { 1963 subject.getPrincipals().add(principal); 1964 debug.message("Done added user to principal"); 1965 } 1966 cleanup(); 1967 return true; 1968 } 1969 1970 /** 1971 * Logs out a Subject. 1972 * <p> 1973 * This JAAS LoginModule method must be implemented by user's module. 1974 * <p> 1975 * An implementation of this method might remove/destroy a Subject's 1976 * Principals and Credentials. 1977 * 1978 * @return <code>true</code> if this method succeeded, or <code>false</code> 1979 * if this LoginModule should be ignored. 1980 * @throws AuthLoginException if the logout fails 1981 * @see javax.security.auth.spi.LoginModule#logout 1982 */ 1983 public final boolean logout() throws AuthLoginException { 1984 // logging out 1985 if (subject.getPrincipals().contains(principal)) { 1986 subject.getPrincipals().remove(principal); 1987 } 1988 succeeded = false; 1989 cleanup(); 1990 return true; 1991 } 1992 1993 /** 1994 * Sets the <code>userID</code> of user who failed authentication. 1995 * This <code>userID</code> will be used to log failed authentication in 1996 * the OpenSSO error logs. 1997 * 1998 * @param userID user name of user who failed authentication. 1999 * @supported.api 2000 */ 2001 public void setFailureID(String userID) { 2002 // get login state for this authentication session 2003 if (userID == null) { 2004 return; 2005 } 2006 debug.message("setFailureID : " + userID); 2007 if (loginState == null) { 2008 loginState = getLoginState(); 2009 if (loginState == null) { 2010 // may be should throw AuthLoginException here 2011 debug.error("Unable to set set userId : " + userID); 2012 return; 2013 } 2014 } 2015 loginState.setFailedUserId(userID); 2016 return ; 2017 } 2018 2019 /** 2020 * Sets a Map of attribute value pairs to be used when the authentication 2021 * service is configured to dynamically create a user. 2022 * 2023 * @param attributeValuePairs A map containing the attributes 2024 * and its values. The key is the attribute name and the value 2025 * is a Set of values. 2026 * 2027 * @supported.api 2028 */ 2029 public void setUserAttributes(Map attributeValuePairs) { 2030 // get login state for this authentication session 2031 if (loginState == null) { 2032 loginState = getLoginState(); 2033 if (loginState == null) { 2034 debug.error("Unable to set user attributes"); 2035 return; 2036 } 2037 } 2038 loginState.setUserCreationAttributes(attributeValuePairs); 2039 return; 2040 } 2041 2042 /** 2043 * Validates the given user name by using validation plugin if exists 2044 * else it checks invalid characters in the source string. 2045 * 2046 * @param userName source string which should be validated. 2047 * @param regEx the pattern for which to search. 2048 * @throws UserNamePasswordValidationException if user name is invalid. 2049 * @supported.api 2050 */ 2051 public void validateUserName(String userName, String regEx) 2052 throws UserNamePasswordValidationException { 2053 try { 2054 AMUserPasswordValidation plugin = getUPValidationInstance(); 2055 if (plugin != null) { 2056 debug.message("Validating username..."); 2057 Map envMap = new HashMap(2); 2058 envMap.put( 2059 com.sun.identity.shared.Constants.ORGANIZATION_NAME, 2060 getRequestOrg()); 2061 plugin.validateUserID(userName, envMap); 2062 } else if (regEx != null && (regEx.length() != 0)) { 2063 if (! (ISValidation.validate(userName, regEx, debug))) { 2064 throw new UserNamePasswordValidationException(bundleName, 2065 "invalidChars", null); 2066 } 2067 } 2068 } catch (AMException ame) { 2069 if (debug.messageEnabled()) { 2070 debug.message("User Name validation Failed" + ame.getMessage()); 2071 } 2072 throw new UserNamePasswordValidationException(ame); 2073 } catch (Exception ex) { 2074 debug.message( 2075 "unKnown Exception occured during username validation"); 2076 throw new UserNamePasswordValidationException(ex); 2077 } 2078 } 2079 2080 2081 /** 2082 * Sets the moduleName of successful LoginModule. 2083 * This moduleName will be populated in the session 2084 * property "AuthType" 2085 * @param moduleName name of module 2086 */ 2087 private void setSuccessModuleName(String moduleName) { 2088 // get login state for this authentication session 2089 if (loginState == null) { 2090 loginState = getLoginState(); 2091 if (loginState == null) { 2092 debug.error("Unable to set moduleName : " + moduleName); 2093 return; 2094 } 2095 } 2096 if (debug.messageEnabled()) { 2097 debug.message("SETTING Module name.... :" + moduleName); 2098 } 2099 loginState.setSuccessModuleName(moduleName); 2100 } 2101 2102 /** 2103 * Checks if valid user exists. 2104 * 2105 * @param userDN the distinguished name of the user. 2106 * @return <code>true</code> if user exists,<code>false</code>otherwise 2107 */ 2108 public boolean isValidUserEntry(String userDN) { 2109 // TODO - IdRepo does not have an equivalent of this 2110 // this method is mainly called to validate DSAME Users 2111 // which are going to be processed differently. 2112 2113 boolean isValidUser = false; 2114 try { 2115 isValidUser = 2116 (AuthD.getAuth().getIdentity(IdType.USER, userDN, "/") != null); 2117 } catch (AuthException e) { 2118 debug.message("User Valid :" + isValidUser); 2119 } 2120 return isValidUser; 2121 } 2122 2123 /** 2124 * Checks if distinguished user name is a super admin. 2125 * 2126 * @param userDN the distinguished name of the user. 2127 * @return <code>true</code> if distinguished user name is a super admin. 2128 */ 2129 public boolean isSuperAdmin(String userDN) { 2130 boolean isSuperAdmin = AuthD.getAuth().isSuperAdmin(userDN); 2131 if (debug.messageEnabled()) { 2132 debug.message("is SuperAdmin : " + isSuperAdmin); 2133 } 2134 return isSuperAdmin; 2135 } 2136 2137 /** 2138 * Validate password for the distinguished user, this will use validation 2139 * plugin if exists to validate password 2140 * 2141 * @param userPassword source string which should be validated. 2142 * @throws UserNamePasswordValidationException if user password is invalid. 2143 * 2144 * @supported.api 2145 */ 2146 public void validatePassword(String userPassword) 2147 throws UserNamePasswordValidationException { 2148 AMUserPasswordValidation plugin = getUPValidationInstance(); 2149 try { 2150 if (plugin != null) { 2151 if (debug.messageEnabled()) { 2152 debug.message("Validating password..."); 2153 } 2154 2155 plugin.validatePassword(userPassword); 2156 } else { 2157 if (debug.messageEnabled()) { 2158 debug.message("No plugin found"); 2159 } 2160 } 2161 } catch (AMException ame) { 2162 if (debug.messageEnabled()) { 2163 debug.message("Password validation Failed " + ame.getMessage()); 2164 } 2165 2166 throw new UserNamePasswordValidationException(ame); 2167 } catch (Exception ex) { 2168 if (debug.messageEnabled()) { 2169 debug.message("Unknown Exception occured during password validation"); 2170 } 2171 2172 throw new UserNamePasswordValidationException(ex); 2173 } 2174 } 2175 2176 /* 2177 * this method instantiates and returns plugin object 2178 */ 2179 private AMUserPasswordValidation getUPValidationInstance() { 2180 2181 try { 2182 String className ; 2183 String orgDN = getRequestOrg(); 2184 if (orgDN != null){ 2185 className = getOrgPluginClassName(orgDN); 2186 } 2187 else { 2188 className = getPluginClassName(); 2189 } 2190 2191 if (debug.messageEnabled()) { 2192 debug.message("UserPasswordValidation Class Name is : " + 2193 className); 2194 } 2195 if ( (className == null) || (className.length() == 0)) { 2196 return null; 2197 } 2198 2199 AMUserPasswordValidation userPasswordInstance = 2200 (AMUserPasswordValidation) 2201 (Class.forName(className).newInstance()); 2202 return userPasswordInstance; 2203 } catch (ClassNotFoundException ce) { 2204 if (debug.messageEnabled()) { 2205 debug.message("Class not Found :", ce); 2206 } 2207 return null; 2208 } catch (Exception e) { 2209 if (debug.messageEnabled()) { 2210 debug.message("Error: ", e); 2211 } 2212 return null; 2213 } 2214 } 2215 2216 /* 2217 * this method gets plugin classname from adminstration service for the org 2218 */ 2219 private String getOrgPluginClassName(String orgDN) { 2220 try { 2221 String cachedValue = AdministrationServiceListener. 2222 getOrgPluginNameFromCache(orgDN); 2223 if (cachedValue != null) { 2224 return cachedValue; 2225 } 2226 Map config = 2227 getOrgServiceTemplate(orgDN,ISAuthConstants.ADMINISTRATION_SERVICE); 2228 String className = 2229 Misc.getServerMapAttr(config, 2230 ISAuthConstants.USERID_PASSWORD_VALIDATION_CLASS); 2231 if (debug.messageEnabled()) { 2232 debug.message("Org Plugin Class: " + className); 2233 } 2234 AdministrationServiceListener.setOrgPluginNameInCache( 2235 orgDN, className); 2236 return className; 2237 } catch (Exception ee) { 2238 debug.message("Error while getting UserPasswordValidationClass " ,ee ); 2239 return null; 2240 } 2241 } 2242 2243 /* 2244 * this method gets plugin classname from adminstration service 2245 */ 2246 private String getPluginClassName() throws AuthLoginException { 2247 String cachedValue = AdministrationServiceListener. 2248 getGlobalPluginNameFromCache(); 2249 if (cachedValue != null) { 2250 return cachedValue; 2251 } 2252 Map config = getServiceConfig(ISAuthConstants.ADMINISTRATION_SERVICE); 2253 String className = 2254 CollectionHelper.getServerMapAttr( 2255 config, ISAuthConstants.USERID_PASSWORD_VALIDATION_CLASS); 2256 if (debug.messageEnabled()) { 2257 debug.message("Plugin Class: " + className); 2258 } 2259 AdministrationServiceListener.setGlobalPluginNameInCache( 2260 className); 2261 return className; 2262 } 2263 2264 /** 2265 * Sets the moduleName of failed login module 2266 * @param moduleName - module name of the failed module 2267 */ 2268 2269 private void setFailureModuleName(String moduleName) { 2270 // get login state for this authentication session 2271 if (loginState == null) { 2272 loginState = getLoginState(); 2273 if (loginState == null) { 2274 debug.error("Unable to set moduleName : " + moduleName); 2275 return; 2276 } 2277 } 2278 if (debug.messageEnabled()) { 2279 debug.message("SETTING Failure Module name.... :" + moduleName); 2280 } 2281 loginState.setFailureModuleName(moduleName); 2282 return ; 2283 } 2284 2285 /** 2286 * Returns JAAS shared state user key. 2287 * 2288 * @return user key. 2289 */ 2290 public String getUserKey() { 2291 return ISAuthConstants.SHARED_STATE_USERNAME; 2292 } 2293 2294 /** 2295 * Returns JAAS shared state password key. 2296 * 2297 * @return password key 2298 */ 2299 public String getPwdKey() { 2300 return ISAuthConstants.SHARED_STATE_PASSWORD; 2301 } 2302 2303 // cleanup method for Auth constants 2304 private void cleanup() { 2305 principal = null; 2306 if (sharedState !=null) { 2307 sharedState.remove(ISAuthConstants.SHARED_STATE_USERNAME); 2308 sharedState.remove(ISAuthConstants.SHARED_STATE_PASSWORD); 2309 } 2310 sharedState = null; 2311 destroyModuleState(); 2312 } 2313 2314 /** 2315 * Stores user name password into shared state map 2316 * this method should be called after successfull 2317 * authentication by each individual modules. 2318 * 2319 * @param user user name 2320 * @param passwd user password 2321 */ 2322 public void storeUsernamePasswd(String user, String passwd) { 2323 // store only if store shared state is enabled 2324 if (isStore && sharedState !=null) { 2325 sharedState.put(ISAuthConstants.SHARED_STATE_USERNAME, user); 2326 sharedState.put(ISAuthConstants.SHARED_STATE_PASSWORD, passwd); 2327 } 2328 } 2329 2330 /** 2331 * Checks if shared state enabled for the module. 2332 * 2333 * @return <code>true</code> if shared state enabled for the module. 2334 */ 2335 public boolean isSharedStateEnabled() { 2336 return isSharedState; 2337 } 2338 2339 /** 2340 * Sets flag to force read call backs in auth chain process. 2341 * @param val - value to force reading call backs 2342 */ 2343 public void setForceCallbacksRead(boolean val) { 2344 forceCallbacksRead = val; 2345 } 2346 2347 /** 2348 * This method returns use first pass enabled or not 2349 * @return return true if use first pass is enabled for the module 2350 */ 2351 public boolean isUseFirstPassEnabled() { 2352 return (sharedStateBehaviorPattern != null) && 2353 sharedStateBehaviorPattern.equals("useFirstPass"); 2354 } 2355 2356 /** 2357 * Returns <code>AMIdentityRepostiory</code> handle for an organization. 2358 * 2359 * @param orgDN the organization name. 2360 * @return <code>AMIdentityRepostiory</code> object 2361 */ 2362 public AMIdentityRepository getAMIdentityRepository(String orgDN) { 2363 return AuthD.getAuth().getAMIdentityRepository(orgDN); 2364 } 2365 2366 /** 2367 * Creates <code>AMIdentity</code> in the repository. 2368 * 2369 * @param userName name of user to be created. 2370 * @param userAttributes Map of default attributes. 2371 * @param userRoles Set of default roles. 2372 * @throws IdRepoException 2373 * @throws SSOException 2374 */ 2375 public void createIdentity( 2376 String userName, 2377 Map userAttributes, 2378 Set userRoles 2379 ) throws IdRepoException, SSOException { 2380 if (loginState == null) { 2381 loginState = getLoginState(); 2382 if (loginState == null) { 2383 debug.error("Unable to create Identity: " + userName); 2384 return ; 2385 } 2386 } 2387 loginState.createUserIdentity(userName,userAttributes,userRoles); 2388 return; 2389 } 2390 2391 /** 2392 * Get the number of failed login attempts for a user when account locking 2393 * is enabled. 2394 * @return number of failed attempts, -1 id account locking is not enabled. 2395 * @throws AuthenticationException if the user name passed in is not valid 2396 * or null, or for any other error condition. 2397 * @supported.api 2398 */ 2399 public int getFailCount(AMIdentity amIdUser) throws AuthenticationException { 2400 AccountLockoutInfo acInfo = null; 2401 if (loginState == null) { 2402 loginState = getLoginState(); 2403 if (loginState == null) { 2404 throw new AuthenticationException(bundleName, "nullLoginState", 2405 null); 2406 } 2407 } 2408 ISAccountLockout isAccountLockout = new ISAccountLockout( 2409 loginState.getLoginFailureLockoutMode(), 2410 loginState.getLoginFailureLockoutTime(), 2411 loginState.getLoginFailureLockoutCount(), 2412 loginState.getLoginLockoutNotification(), 2413 loginState.getLoginLockoutUserWarning(), 2414 loginState.getLoginLockoutAttrName(), 2415 loginState.getLoginLockoutAttrValue(), 2416 loginState.getLoginFailureLockoutDuration(), 2417 loginState.getLoginFailureLockoutMultiplier(), 2418 loginState.getInvalidAttemptsDataAttrName(), 2419 bundleName); 2420 isAccountLockout.setStoreInvalidAttemptsInDS( 2421 loginState.getLoginFailureLockoutStoreInDS()); 2422 2423 try { 2424 if (!isAccountLockout.isLockoutEnabled()) { 2425 debug.message("Failure lockout mode disabled"); 2426 return -1; 2427 } else { 2428 if (debug.messageEnabled()) { 2429 debug.message("AMLogiModule.getFailCount()::" 2430 +"lockout is enabled"); 2431 } 2432 2433 String userDN = null; 2434 userDN = normalizeDN(IdUtils.getDN(amIdUser)); 2435 2436 if (acInfo == null) { 2437 acInfo = isAccountLockout.getAcInfo(userDN,amIdUser); 2438 } 2439 int failCount = acInfo.getFailCount(); 2440 if (debug.messageEnabled()) { 2441 debug.message("AMLoginModule.getFailCount:failCount " 2442 +"returned:" +failCount); 2443 } 2444 return failCount; 2445 } 2446 } catch (Exception ex) { 2447 debug.error("AMLoginModule.getFailCount:Error", ex); 2448 throw new AuthenticationException(ex.getMessage()); 2449 } 2450 } 2451 2452 /** 2453 * Get the maximum number failed login attempts permitted for a user 2454 * before when their account is locked out. 2455 * 2456 * @return the maximum number of failed attempts 2457 * @supported.api 2458 */ 2459 public int getMaximumFailCount() 2460 throws AuthenticationException { 2461 if (loginState == null) { 2462 loginState = getLoginState(); 2463 2464 if (loginState == null) { 2465 throw new AuthenticationException(bundleName, "nullLoginState", 2466 null); 2467 } 2468 } 2469 2470 return loginState.getLoginFailureLockoutCount(); 2471 } 2472 2473 /** 2474 * Increments the fail count for the given user. 2475 * 2476 * @throws AuthenticationException if the user name passed in is not valid 2477 * or null, or for any other error condition. 2478 * @supported.api 2479 */ 2480 public void incrementFailCount(String userName) 2481 throws AuthenticationException { 2482 if (loginState == null) { 2483 loginState = getLoginState(); 2484 2485 if (loginState == null) { 2486 throw new AuthenticationException(bundleName, "nullLoginState", 2487 null); 2488 } 2489 } 2490 2491 loginState.incrementFailCount(userName); 2492 } 2493 2494 /** 2495 * Returns true if the named account is locked out, false otherwise. 2496 * 2497 * @throws AuthenticationException if the user name passed in is not valid 2498 * or null, or for any other error condition. 2499 * @supported.api 2500 */ 2501 public boolean isAccountLocked(String userName) 2502 throws AuthenticationException { 2503 if (loginState == null) { 2504 loginState = getLoginState(); 2505 2506 if (loginState == null) { 2507 throw new AuthenticationException(bundleName, "nullLoginState", 2508 null); 2509 } 2510 } 2511 2512 boolean accountLocked = loginState.isAccountLocked(userName); 2513 2514 if (ad.debug.messageEnabled()) { 2515 ad.debug.message("isAccountLocked for user=" + userName + " :" + accountLocked); 2516 } 2517 2518 return accountLocked; 2519 } 2520 2521 /* returns the normalized DN */ 2522 private String normalizeDN(String userDN) { 2523 String normalizedDN = userDN; 2524 if ((userDN != null) && DN.isDN(userDN)) { 2525 normalizedDN = DNUtils.normalizeDN(userDN); 2526 } 2527 if (ad.debug.messageEnabled()) { 2528 ad.debug.message("Original DN is:" + userDN); 2529 ad.debug.message("Normalized DN is:" + normalizedDN); 2530 } 2531 return normalizedDN; 2532 } 2533 2534 /** 2535 * Authenticates to the datastore using idRepo API 2536 * 2537 * @param callbacks Array of last submitted callbacks to the 2538 * authentication module 2539 * @return <code>true</code> if success. <code>false</code> if failure 2540 * @throws <code> AuthLoginException </code> 2541 */ 2542 private boolean authenticateToDatastore(Callback[] callbacks) 2543 throws AuthLoginException { 2544 boolean retval = false; 2545 boolean needToCheck = false; 2546 Callback[] idrepoCallbacks = new Callback[2]; 2547 String userName = null; 2548 char[] password = null; 2549 2550 for (int i = 0; i < callbacks.length; i++) { 2551 if (callbacks[i] instanceof NameCallback) { 2552 NameCallback nc = (NameCallback) callbacks[i]; 2553 userName = nc.getName(); 2554 if (debug.messageEnabled()){ 2555 debug.message("AMLoginModule.authenticateToDatastore:: " 2556 + " user is : " + userName); 2557 debug.message("AMLoginModule.authenticateToDatastore:: " 2558 + " Internal users : " + LoginState.internalUsers); 2559 } 2560 2561 if (LoginState.internalUsers.contains( 2562 userName.toLowerCase())) { 2563 needToCheck = true; 2564 } else { 2565 break; 2566 } 2567 2568 } else if (callbacks[i] instanceof PasswordCallback) { 2569 PasswordCallback pc = (PasswordCallback) callbacks[i]; 2570 password = pc.getPassword(); 2571 } 2572 } 2573 if (needToCheck == false) { 2574 return true; 2575 } 2576 2577 if (debug.messageEnabled()){ 2578 debug.message("AMLoginModule.authenticateToDatastore:: " 2579 + "Authenticating Internal user to configuration store"); 2580 } 2581 NameCallback nameCallback = new NameCallback("NamePrompt"); 2582 nameCallback.setName(userName); 2583 idrepoCallbacks[0] = nameCallback; 2584 PasswordCallback passwordCallback = new PasswordCallback( 2585 "PasswordPrompt",false); 2586 passwordCallback.setPassword(password); 2587 idrepoCallbacks[1] = passwordCallback; 2588 try { 2589 AMIdentityRepository idrepo = getAMIdentityRepository( 2590 getRequestOrg()); 2591 retval = idrepo.authenticate(idrepoCallbacks); 2592 if (debug.messageEnabled()){ 2593 debug.message("AMLoginModule.authenticateToDatastore:: " + 2594 " IDRepo authentication successful"); 2595 } 2596 } catch (IdRepoException idrepoExp) { 2597 if (debug.messageEnabled()){ 2598 debug.message("AMLoginModule.authenticateToDatastore:: " 2599 + "IdRepo Exception : ", idrepoExp); 2600 } 2601 } catch (InvalidPasswordException ipe) { 2602 throw new AuthLoginException(AMAuthErrorCode.AUTH_MODULE_DENIED); 2603 } 2604 return retval; 2605 2606 } 2607 2608 /** 2609 * Returns true if the user identified by the supplied username has reached 2610 * their session quota.<br> 2611 * <i>NB</i>The existing session count is exclusive of any session created 2612 * as part of the running authentication process 2613 * 2614 * @param userName the username of the user who's session quota will be checked 2615 * @return true if the user session quota is reached, false otherwise 2616 * @supported.api 2617 */ 2618 public boolean isSessionQuotaReached(String userName) { 2619 int sessionCount = -1; 2620 int sessionQuota = -1; 2621 2622 if (userName == null || userName.equals(Constants.EMPTY)) { 2623 debug.error("AMLoginModule.isSessionQuotaReached :: called with null username"); 2624 return false; 2625 } 2626 2627 try { 2628 // Get the universal ID 2629 AMIdentity amIdUser = ad.getIdentity(IdType.USER, userName, 2630 loginState.getOrgDN()); 2631 2632 String univId = IdUtils.getUniversalId(amIdUser); 2633 2634 if (univId != null) { 2635 sessionQuota = getSessionQuota(amIdUser); 2636 sessionCount = SessionCount.getAllSessionsByUUID(univId).size(); 2637 2638 if (debug.messageEnabled()) { 2639 debug.message("AMLoginModule.isSessionQuotaReached :: univId= " 2640 + univId + " - Session Quota Reached = " + (sessionCount >= sessionQuota)); 2641 } 2642 } else { 2643 debug.error("AMLoginModule.isSessionQuotaReached :: " 2644 + "univId is null , amIdUser is " + amIdUser); 2645 return false; 2646 } 2647 } catch (Exception ex) { 2648 debug.error("AMLoginModule.getSessionQuotaLevel:: " 2649 + "Exception : ", ex); 2650 } 2651 2652 return (sessionCount >= sessionQuota); 2653 } 2654 2655 private int getSessionQuota(AMIdentity iden) { 2656 int quota = SessionConstraint.getDefaultSessionQuota(); 2657 2658 if (iden == null) { 2659 debug.error("AMLoginModule.getSessionQuota :: AMIdentity is null, returning default quota"); 2660 return quota; 2661 } 2662 2663 try { 2664 Map serviceAttrs = 2665 iden.getServiceAttributesAscending("iPlanetAMSessionService"); 2666 2667 Set s = (Set)serviceAttrs.get("iplanet-am-session-quota-limit"); 2668 Iterator attrs = s.iterator(); 2669 if (attrs.hasNext()) { 2670 String attr = (String) attrs.next(); 2671 quota = (Integer.valueOf(attr)).intValue(); 2672 } 2673 } catch (Exception ex) { 2674 debug.error("Failed to get the session quota via the "+ 2675 "IDRepo interfaces, => Use the default " + 2676 "value from the dynamic schema instead.", ex); 2677 } 2678 2679 return quota; 2680 } 2681 2682 2683 /** 2684 * Returns the set of SSOTokens for a specified user 2685 * 2686 * @param userName The username to be used to query the sessions 2687 * @return The set of SSOTokens for the user's current sessions, returns null on error 2688 * @supported.api 2689 */ 2690 public Set<SSOToken> getUserSessions(String userName) { 2691 Set<SSOToken> sessions = new HashSet<SSOToken>(); 2692 2693 if (userName == null || userName.equals(Constants.EMPTY)) { 2694 debug.error("AMLoginModule.getUserSessions :: called with null username"); 2695 return null; 2696 } 2697 2698 try { 2699 // Get the universal ID 2700 AMIdentity amIdUser = ad.getIdentity(IdType.USER, userName, loginState.getOrgDN()); 2701 2702 String univId = IdUtils.getUniversalId(amIdUser); 2703 2704 if (univId != null) { 2705 Map<String, String> currentSessions = SessionCount.getAllSessionsByUUID(univId); 2706 SSOTokenManager manager = SSOTokenManager.getInstance(); 2707 2708 for (String tokenID : currentSessions.keySet()) { 2709 sessions.add(manager.createSSOToken(tokenID)); 2710 } 2711 2712 if (debug.messageEnabled()) { 2713 debug.message("AMLoginModule.getUserSessions :: univId= " 2714 + univId + " - found sessions = " + sessions); 2715 } 2716 } else { 2717 debug.error("AMLoginModule.getUserSessions :: " 2718 + "univId is null , amIdUser is " + amIdUser); 2719 return null; 2720 } 2721 } catch (Exception ex) { 2722 debug.error("AMLoginModule.getUserSessions:: " 2723 + "Exception : ", ex); 2724 } 2725 2726 return sessions; 2727 } 2728}