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




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.