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