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