001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.
018 */
019package org.apache.wiki.auth;
020
021import java.io.IOException;
022import java.security.Permission;
023import java.security.Principal;
024import java.text.MessageFormat;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028import java.util.ResourceBundle;
029import java.util.WeakHashMap;
030
031import javax.servlet.ServletException;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletResponse;
034
035import org.apache.commons.lang.StringUtils;
036import org.apache.log4j.Logger;
037import org.apache.wiki.WikiContext;
038import org.apache.wiki.WikiEngine;
039import org.apache.wiki.WikiSession;
040import org.apache.wiki.ajax.AjaxUtil;
041import org.apache.wiki.ajax.WikiAjaxDispatcherServlet;
042import org.apache.wiki.ajax.WikiAjaxServlet;
043import org.apache.wiki.api.engine.FilterManager;
044import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
045import org.apache.wiki.api.exceptions.WikiException;
046import org.apache.wiki.api.filters.PageFilter;
047import org.apache.wiki.auth.permissions.AllPermission;
048import org.apache.wiki.auth.permissions.WikiPermission;
049import org.apache.wiki.auth.user.AbstractUserDatabase;
050import org.apache.wiki.auth.user.DuplicateUserException;
051import org.apache.wiki.auth.user.UserDatabase;
052import org.apache.wiki.auth.user.UserProfile;
053import org.apache.wiki.event.WikiEventListener;
054import org.apache.wiki.event.WikiEventManager;
055import org.apache.wiki.event.WikiSecurityEvent;
056import org.apache.wiki.filters.SpamFilter;
057import org.apache.wiki.i18n.InternationalizationManager;
058import org.apache.wiki.preferences.Preferences;
059import org.apache.wiki.ui.InputValidator;
060import org.apache.wiki.util.ClassUtil;
061import org.apache.wiki.workflow.Decision;
062import org.apache.wiki.workflow.DecisionRequiredException;
063import org.apache.wiki.workflow.Fact;
064import org.apache.wiki.workflow.Step;
065import org.apache.wiki.workflow.Workflow;
066import org.apache.wiki.workflow.WorkflowBuilder;
067import org.apache.wiki.workflow.WorkflowManager;
068
069
070/**
071 * Provides a facade for obtaining user information.
072 * @since 2.3
073 */
074public class UserManager {
075
076    private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user";
077    private static final String SESSION_MESSAGES = "profile";
078    private static final String PARAM_EMAIL = "email";
079    private static final String PARAM_FULLNAME = "fullname";
080    private static final String PARAM_PASSWORD = "password";
081    private static final String PARAM_LOGINNAME = "loginname";
082    private static final String UNKNOWN_CLASS = "<unknown>";
083
084    private WikiEngine m_engine;
085
086    private static final Logger log = Logger.getLogger(UserManager.class);
087
088    /** Message key for the "save profile" message. */
089    private static final String PROP_DATABASE               = "jspwiki.userdatabase";
090
091    public static final String JSON_USERS = "users";
092
093    // private static final String  PROP_ACLMANAGER     = "jspwiki.aclManager";
094
095    /** Associates wiki sessions with profiles */
096    private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<>();
097
098    /** The user database loads, manages and persists user identities */
099    private UserDatabase     m_database;
100
101    /**
102     * Initializes the engine for its nefarious purposes.
103     * @param engine the current wiki engine
104     * @param props the wiki engine initialization properties
105     */
106    public void initialize( WikiEngine engine, Properties props )
107    {
108        m_engine = engine;
109
110        // Attach the PageManager as a listener
111        // TODO: it would be better if we did this in PageManager directly
112        addWikiEventListener( engine.getPageManager() );
113
114        //TODO: Replace with custom annotations. See JSPWIKI-566
115        WikiAjaxDispatcherServlet.registerServlet( JSON_USERS, new JSONUserModule(this), new AllPermission(null));
116    }
117
118    /**
119     * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is
120     * lazily initialized by this method, if it does not exist yet. If the
121     * initialization fails, this method will use the inner class
122     * DummyUserDatabase as a default (which is enough to get JSPWiki running).
123     * @return the dummy user database
124     * @since 2.3
125     */
126    public UserDatabase getUserDatabase()
127    {
128        // FIXME: Must not throw RuntimeException, but something else.
129        if( m_database != null )
130        {
131            return m_database;
132        }
133
134        String dbClassName = UNKNOWN_CLASS;
135
136        try
137        {
138            dbClassName = m_engine.getRequiredProperty( m_engine.getWikiProperties(), PROP_DATABASE );
139
140            log.info("Attempting to load user database class "+dbClassName);
141            final Class<?> dbClass = ClassUtil.findClass( USERDATABASE_PACKAGE, dbClassName );
142            m_database = (UserDatabase) dbClass.newInstance();
143            m_database.initialize( m_engine, m_engine.getWikiProperties() );
144            log.info("UserDatabase initialized.");
145        }
146        catch( final NoRequiredPropertyException e )
147        {
148            log.error( "You have not set the '"+PROP_DATABASE+"'. You need to do this if you want to enable user management by JSPWiki.", e );
149        }
150        catch( final ClassNotFoundException e )
151        {
152            log.error( "UserDatabase class " + dbClassName + " cannot be found", e );
153        }
154        catch( final InstantiationException e )
155        {
156            log.error( "UserDatabase class " + dbClassName + " cannot be created", e );
157        }
158        catch( final IllegalAccessException e )
159        {
160            log.error( "You are not allowed to access this user database class", e );
161        }
162        catch( final WikiSecurityException e )
163        {
164            log.error( "Exception initializing user database: " + e.getMessage(), e );
165        }
166        finally
167        {
168            if( m_database == null )
169            {
170                log.info("I could not create a database object you specified (or didn't specify), so I am falling back to a default.");
171                m_database = new DummyUserDatabase();
172            }
173        }
174
175        return m_database;
176    }
177
178    /**
179     * <p>Retrieves the {@link org.apache.wiki.auth.user.UserProfile}for the
180     * user in a wiki session. If the user is authenticated, the UserProfile
181     * returned will be the one stored in the user database; if one does not
182     * exist, a new one will be initialized and returned. If the user is
183     * anonymous or asserted, the UserProfile will <i>always</i> be newly
184     * initialized to prevent spoofing of identities. If a UserProfile needs to
185     * be initialized, its
186     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method will
187     * return <code>true</code>, and its login name will will be set
188     * automatically if the user is authenticated. Note that this method does
189     * not modify the retrieved (or newly created) profile otherwise; other
190     * fields in the user profile may be <code>null</code>.</p>
191     * <p>If a new UserProfile was created, but its
192     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method returns
193     * <code>false</code>, this method throws an {@link IllegalStateException}.
194     * This is meant as a quality check for UserDatabase providers;
195     * it should only be thrown if the implementation is faulty.</p>
196     * @param session the wiki session, which may not be <code>null</code>
197     * @return the user's profile, which will be newly initialized if the user
198     * is anonymous or asserted, or if the user cannot be found in the user
199     * database
200     */
201    public UserProfile getUserProfile( WikiSession session )
202    {
203        // Look up cached user profile
204        UserProfile profile = m_profiles.get( session );
205        boolean newProfile = profile == null;
206        Principal user = null;
207
208        // If user is authenticated, figure out if this is an existing profile
209        if ( session.isAuthenticated() )
210        {
211            user = session.getUserPrincipal();
212            try
213            {
214                profile = getUserDatabase().find( user.getName() );
215                newProfile = false;
216            }
217            catch( final NoSuchPrincipalException e )
218            {
219            }
220        }
221
222        if ( newProfile )
223        {
224            profile = getUserDatabase().newProfile();
225            if ( user != null )
226            {
227                profile.setLoginName( user.getName() );
228            }
229            if ( !profile.isNew() )
230            {
231                throw new IllegalStateException(
232                        "New profile should be marked 'new'. Check your UserProfile implementation." );
233            }
234        }
235
236        // Stash the profile for next time
237        m_profiles.put( session, profile );
238        return profile;
239    }
240
241    /**
242     * <p>
243     * Saves the {@link org.apache.wiki.auth.user.UserProfile}for the user in
244     * a wiki session. This method verifies that a user profile to be saved
245     * doesn't collide with existing profiles; that is, the login name
246     * or full name is already used by another profile. If the profile
247     * collides, a <code>DuplicateUserException</code> is thrown. After saving
248     * the profile, the user database changes are committed, and the user's
249     * credential set is refreshed; if custom authentication is used, this means
250     * the user will be automatically be logged in.
251     * </p>
252     * <p>
253     * When the user's profile is saved successfully, this method fires a
254     * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the
255     * source and the UserProfile as target. For existing profiles, if the
256     * user's full name changes, this method also fires a "name changed"
257     * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the
258     * WikiSession as the source and an array containing the old and new
259     * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows
260     * the GroupManager and PageManager can change group memberships and
261     * ACLs if needed.
262     * </p>
263     * <p>
264     * Note that WikiSessions normally attach event listeners to the
265     * UserManager, so changes to the profile will automatically cause the
266     * correct Principals to be reloaded into the current WikiSession's Subject.
267     * </p>
268     * @param session the wiki session, which may not be <code>null</code>
269     * @param profile the user profile, which may not be <code>null</code>
270     * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
271     * @throws WikiException if the save fails for some reason. If the current user does not have
272     * permission to save the profile, this will be a {@link org.apache.wiki.auth.WikiSecurityException};
273     * if if the user profile must be approved before it can be saved, it will be a
274     * {@link org.apache.wiki.workflow.DecisionRequiredException}. All other WikiException
275     * indicate a condition that is not normal is probably due to mis-configuration
276     */
277    public void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException
278    {
279        // Verify user is allowed to save profile!
280        final Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
281        if ( !m_engine.getAuthorizationManager().checkPermission( session, p ) )
282        {
283            throw new WikiSecurityException( "You are not allowed to save wiki profiles." );
284        }
285
286        // Check if profile is new, and see if container allows creation
287        final boolean newProfile = profile.isNew();
288
289        // Check if another user profile already has the fullname or loginname
290        final UserProfile oldProfile = getUserProfile( session );
291        final boolean nameChanged = ( oldProfile == null  || oldProfile.getFullname() == null )
292            ? false
293            : !( oldProfile.getFullname().equals( profile.getFullname() ) &&
294                 oldProfile.getLoginName().equals( profile.getLoginName() ) );
295        UserProfile otherProfile;
296        try
297        {
298            otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() );
299            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
300            {
301                throw new DuplicateUserException( "security.error.login.taken", profile.getLoginName() );
302            }
303        }
304        catch( final NoSuchPrincipalException e )
305        {
306        }
307        try
308        {
309            otherProfile = getUserDatabase().findByFullName( profile.getFullname() );
310            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
311            {
312                throw new DuplicateUserException( "security.error.fullname.taken", profile.getFullname() );
313            }
314        }
315        catch( final NoSuchPrincipalException e )
316        {
317        }
318
319        // For new accounts, create approval workflow for user profile save.
320        if ( newProfile && oldProfile != null && oldProfile.isNew() )
321        {
322            startUserProfileCreationWorkflow(session, profile);
323
324            // If the profile doesn't need approval, then just log the user in
325
326            try
327            {
328                final AuthenticationManager mgr = m_engine.getAuthenticationManager();
329                if ( newProfile && !mgr.isContainerAuthenticated() )
330                {
331                    mgr.login( session, null, profile.getLoginName(), profile.getPassword() );
332                }
333            }
334            catch ( final WikiException e )
335            {
336                throw new WikiSecurityException( e.getMessage(), e );
337            }
338
339            // Alert all listeners that the profile changed...
340            // ...this will cause credentials to be reloaded in the wiki session
341            fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
342        }
343
344        // For existing accounts, just save the profile
345        else
346        {
347            // If login name changed, rename it first
348            if ( nameChanged && oldProfile != null && !oldProfile.getLoginName().equals( profile.getLoginName() ) )
349            {
350                getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() );
351            }
352
353            // Now, save the profile (userdatabase will take care of timestamps for us)
354            getUserDatabase().save( profile );
355
356            if ( nameChanged )
357            {
358                // Fire an event if the login name or full name changed
359                final UserProfile[] profiles = new UserProfile[] { oldProfile, profile };
360                fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles );
361            }
362            else
363            {
364                // Fire an event that says we have new a new profile (new principals)
365                fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
366            }
367        }
368    }
369
370    public void startUserProfileCreationWorkflow( final WikiSession session, final UserProfile profile ) throws WikiException {
371        final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
372        final Principal submitter = session.getUserPrincipal();
373        final Step completionTask = m_engine.getTasksManager().buildSaveUserProfileTask( m_engine, session.getLocale() );
374
375        // Add user profile attribute as Facts for the approver (if required)
376        final boolean hasEmail = profile.getEmail() != null;
377        final Fact[] facts = new Fact[ hasEmail ? 4 : 3];
378        facts[0] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() );
379        facts[1] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() );
380        facts[2] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, submitter.getName() );
381        if ( hasEmail )
382        {
383            facts[3] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() );
384        }
385        final Workflow workflow = builder.buildApprovalWorkflow( submitter, 
386                                                                 WorkflowManager.WF_UP_CREATE_SAVE_APPROVER, 
387                                                                 null, 
388                                                                 WorkflowManager.WF_UP_CREATE_SAVE_DECISION_MESSAGE_KEY, 
389                                                                 facts, 
390                                                                 completionTask, 
391                                                                 null );
392
393        workflow.setAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE, profile );
394        m_engine.getWorkflowManager().start(workflow);
395
396        final boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
397
398        // If the profile requires approval, redirect user to message page
399        if ( approvalRequired )
400        {
401            throw new DecisionRequiredException( "This profile must be approved before it becomes active" );
402        }
403    }
404
405    /**
406     * <p> Extracts user profile parameters from the HTTP request and populates
407     * a UserProfile with them. The UserProfile will either be a copy of the
408     * user's existing profile (if one can be found), or a new profile (if not).
409     * The rules for populating the profile as as follows: </p> <ul> <li>If the
410     * <code>email</code> or <code>password</code> parameter values differ
411     * from those in the existing profile, the passed parameters override the
412     * old values.</li> <li>For new profiles, the user-supplied
413     * <code>fullname</code> parameter is always
414     * used; for existing profiles the existing value is used, and whatever
415     * value the user supplied is discarded. The wiki name is automatically
416     * computed by taking the full name and extracting all whitespace.</li>
417     * <li>In all cases, the
418     * created/last modified timestamps of the user's existing or new profile
419     * always override whatever values the user supplied.</li> <li>If
420     * container authentication is used, the login name property of the profile
421     * is set to the name of
422     * {@link org.apache.wiki.WikiSession#getLoginPrincipal()}. Otherwise,
423     * the value of the <code>loginname</code> parameter is used.</li> </ul>
424     * @param context the current wiki context
425     * @return a new, populated user profile
426     */
427    public UserProfile parseProfile( WikiContext context )
428    {
429        // Retrieve the user's profile (may have been previously cached)
430        final UserProfile profile = getUserProfile( context.getWikiSession() );
431        final HttpServletRequest request = context.getHttpRequest();
432
433        // Extract values from request stream (cleanse whitespace as needed)
434        String loginName = request.getParameter( PARAM_LOGINNAME );
435        String password = request.getParameter( PARAM_PASSWORD );
436        String fullname = request.getParameter( PARAM_FULLNAME );
437        String email = request.getParameter( PARAM_EMAIL );
438        loginName = InputValidator.isBlank( loginName ) ? null : loginName;
439        password = InputValidator.isBlank( password ) ? null : password;
440        fullname = InputValidator.isBlank( fullname ) ? null : fullname;
441        email = InputValidator.isBlank( email ) ? null : email;
442
443        // A special case if we have container authentication
444        // If authenticated, login name is always taken from container
445        if ( m_engine.getAuthenticationManager().isContainerAuthenticated() &&
446                context.getWikiSession().isAuthenticated() )
447        {
448            loginName = context.getWikiSession().getLoginPrincipal().getName();
449        }
450
451        // Set the profile fields!
452        profile.setLoginName( loginName );
453        profile.setEmail( email );
454        profile.setFullname( fullname );
455        profile.setPassword( password );
456        return profile;
457    }
458
459    /**
460     * Validates a user profile, and appends any errors to the session errors
461     * list. If the profile is new, the password will be checked to make sure it
462     * isn't null. Otherwise, the password is checked for length and that it
463     * matches the value of the 'password2' HTTP parameter. Note that we have a
464     * special case when container-managed authentication is used and the user
465     * is not authenticated; this will always cause validation to fail. Any
466     * validation errors are added to the wiki session's messages collection
467     * (see {@link WikiSession#getMessages()}.
468     * @param context the current wiki context
469     * @param profile the supplied UserProfile
470     */
471    public void validateProfile( WikiContext context, UserProfile profile )
472    {
473        final boolean isNew = profile.isNew();
474        final WikiSession session = context.getWikiSession();
475        final InputValidator validator = new InputValidator( SESSION_MESSAGES, context );
476        final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
477
478        //
479        //  Query the SpamFilter first
480        //
481        final FilterManager fm = m_engine.getFilterManager();
482        final List<PageFilter> ls = fm.getFilterList();
483        for( final PageFilter pf : ls )
484        {
485            if( pf instanceof SpamFilter )
486            {
487                if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false )
488                {
489                    session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
490                    return;
491                }
492                break;
493            }
494        }
495
496        // If container-managed auth and user not logged in, throw an error
497        if ( m_engine.getAuthenticationManager().isContainerAuthenticated()
498             && !context.getWikiSession().isAuthenticated() )
499        {
500            session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") );
501        }
502
503        validator.validateNotNull( profile.getLoginName(), rb.getString("security.user.loginname") );
504        validator.validateNotNull( profile.getFullname(), rb.getString("security.user.fullname") );
505        validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL );
506
507        // If new profile, passwords must match and can't be null
508        if ( !m_engine.getAuthenticationManager().isContainerAuthenticated() )
509        {
510            final String password = profile.getPassword();
511            if ( password == null )
512            {
513                if ( isNew )
514                {
515                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.blankpassword") );
516                }
517            }
518            else
519            {
520                final HttpServletRequest request = context.getHttpRequest();
521                final String password2 = ( request == null ) ? null : request.getParameter( "password2" );
522                if ( !password.equals( password2 ) )
523                {
524                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.passwordnomatch") );
525                }
526            }
527        }
528
529        UserProfile otherProfile;
530        final String fullName = profile.getFullname();
531        final String loginName = profile.getLoginName();
532        final String email = profile.getEmail();
533
534        // It's illegal to use as a full name someone else's login name
535        try
536        {
537            otherProfile = getUserDatabase().find( fullName );
538            if ( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) )
539            {
540                final Object[] args = { fullName };
541                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalfullname"), args ) );
542            }
543        }
544        catch ( final NoSuchPrincipalException e)
545        { /* It's clean */ }
546
547        // It's illegal to use as a login name someone else's full name
548        try
549        {
550            otherProfile = getUserDatabase().find( loginName );
551            if ( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) )
552            {
553                final Object[] args = { loginName };
554                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalloginname"), args ) );
555            }
556        }
557        catch ( final NoSuchPrincipalException e)
558        { /* It's clean */ }
559
560        // It's illegal to use multiple accounts with the same email
561        try
562        {
563            otherProfile = getUserDatabase().findByEmail( email );
564            if ( otherProfile != null
565                && !profile.getUid().equals(otherProfile.getUid()) // Issue JSPWIKI-1042
566                && !profile.equals( otherProfile ) && StringUtils.lowerCase( email ).equals( StringUtils.lowerCase(otherProfile.getEmail() ) ) )
567            {
568                final Object[] args = { email };
569                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.email.taken"), args ) );
570            }
571        }
572        catch ( final NoSuchPrincipalException e)
573        { /* It's clean */ }
574    }
575
576    /**
577     *  A helper method for returning all of the known WikiNames in this system.
578     *
579     *  @return An Array of Principals
580     *  @throws WikiSecurityException If for reason the names cannot be fetched
581     */
582    public Principal[] listWikiNames()
583        throws WikiSecurityException
584    {
585        return getUserDatabase().getWikiNames();
586    }
587
588    /**
589     * This is a database that gets used if nothing else is available. It does
590     * nothing of note - it just mostly throws NoSuchPrincipalExceptions if
591     * someone tries to log in.
592     */
593    public static class DummyUserDatabase extends AbstractUserDatabase
594    {
595
596        /**
597         * No-op.
598         * @param loginName the login name to delete
599         * @throws WikiSecurityException never...
600         */
601        @Override
602        public void deleteByLoginName( String loginName ) throws WikiSecurityException
603        {
604            // No operation
605        }
606
607        /**
608         * No-op; always throws <code>NoSuchPrincipalException</code>.
609         * @param index the name to search for
610         * @return the user profile
611         * @throws NoSuchPrincipalException never...
612         */
613        @Override
614        public UserProfile findByEmail(String index) throws NoSuchPrincipalException
615        {
616            throw new NoSuchPrincipalException("No user profiles available");
617        }
618
619        /**
620         * No-op; always throws <code>NoSuchPrincipalException</code>.
621         * @param index the name to search for
622         * @return the user profile
623         * @throws NoSuchPrincipalException never...
624         */
625        @Override
626        public UserProfile findByFullName(String index) throws NoSuchPrincipalException
627        {
628            throw new NoSuchPrincipalException("No user profiles available");
629        }
630
631        /**
632         * No-op; always throws <code>NoSuchPrincipalException</code>.
633         * @param index the name to search for
634         * @return the user profile
635         * @throws NoSuchPrincipalException never...
636         */
637        @Override
638        public UserProfile findByLoginName(String index) throws NoSuchPrincipalException
639        {
640            throw new NoSuchPrincipalException("No user profiles available");
641        }
642
643        /**
644         * No-op; always throws <code>NoSuchPrincipalException</code>.
645         * @param uid the unique identifier to search for
646         * @return the user profile
647         * @throws NoSuchPrincipalException never...
648         */
649        @Override
650        public UserProfile findByUid( String uid ) throws NoSuchPrincipalException
651        {
652            throw new NoSuchPrincipalException("No user profiles available");
653        }
654        /**
655         * No-op; always throws <code>NoSuchPrincipalException</code>.
656         * @param index the name to search for
657         * @return the user profile
658         * @throws NoSuchPrincipalException never...
659         */
660        @Override
661        public UserProfile findByWikiName(String index) throws NoSuchPrincipalException
662        {
663            throw new NoSuchPrincipalException("No user profiles available");
664        }
665
666        /**
667         * No-op.
668         * @return a zero-length array
669         * @throws WikiSecurityException never...
670         */
671        @Override
672        public Principal[] getWikiNames() throws WikiSecurityException
673        {
674            return new Principal[0];
675        }
676
677        /**
678         * No-op.
679         *
680         * @param engine the wiki engine
681         * @param props the properties used to initialize the wiki engine
682         * @throws NoRequiredPropertyException never...
683         */
684        @Override
685        public void initialize(WikiEngine engine, Properties props) throws NoRequiredPropertyException
686        {
687        }
688
689        /**
690         * No-op; always throws <code>NoSuchPrincipalException</code>.
691         * @param loginName the login name
692         * @param newName the proposed new login name
693         * @throws DuplicateUserException never...
694         * @throws WikiSecurityException never...
695         */
696        @Override
697        public void rename( String loginName, String newName ) throws DuplicateUserException, WikiSecurityException
698        {
699            throw new NoSuchPrincipalException("No user profiles available");
700        }
701
702        /**
703         * No-op.
704         * @param profile the user profile
705         * @throws WikiSecurityException never...
706         */
707        @Override
708        public void save( UserProfile profile ) throws WikiSecurityException
709        {
710        }
711
712    }
713
714    // events processing .......................................................
715
716    /**
717     * Registers a WikiEventListener with this instance.
718     * This is a convenience method.
719     * @param listener the event listener
720     */
721    public synchronized void addWikiEventListener( WikiEventListener listener )
722    {
723        WikiEventManager.addWikiEventListener( this, listener );
724    }
725
726    /**
727     * Un-registers a WikiEventListener with this instance.
728     * This is a convenience method.
729     * @param listener the event listener
730     */
731    public synchronized void removeWikiEventListener( WikiEventListener listener )
732    {
733        WikiEventManager.removeWikiEventListener( this, listener );
734    }
735
736    /**
737     *  Fires a WikiSecurityEvent of the provided type, Principal and target Object
738     *  to all registered listeners.
739     *
740     * @see org.apache.wiki.event.WikiSecurityEvent
741     * @param type       the event type to be fired
742     * @param session    the wiki session supporting the event
743     * @param profile    the user profile (or array of user profiles), which may be <code>null</code>
744     */
745    protected void fireEvent( int type, WikiSession session, Object profile )
746    {
747        if ( WikiEventManager.isListening(this) )
748        {
749            WikiEventManager.fireEvent(this,new WikiSecurityEvent(session,type,profile));
750        }
751    }
752
753    /**
754     *  Implements the JSON API for usermanager.
755     *  <p>
756     *  Even though this gets serialized whenever container shuts down/restarts,
757     *  this gets reinstalled to the session when JSPWiki starts.  This means
758     *  that it's not actually necessary to save anything.
759     */
760    public static final class JSONUserModule implements WikiAjaxServlet
761    {
762        private volatile UserManager m_manager;
763
764        /**
765         *  Create a new JSONUserModule.
766         *  @param mgr Manager
767         */
768        public JSONUserModule( UserManager mgr )
769        {
770            m_manager = mgr;
771        }
772
773        @Override
774        public String getServletMapping() {
775            return JSON_USERS;
776        }
777
778        @Override
779        public void service(HttpServletRequest req, HttpServletResponse resp, String actionName, List<String> params) throws ServletException, IOException {
780            try {
781                String uid = null;
782                if (params.size()<1) {
783                    return;
784                }
785                uid = params.get(0);
786                log.debug("uid="+uid);
787                if (StringUtils.isNotBlank(uid)) {
788                    final UserProfile prof = getUserInfo(uid);
789                    resp.getWriter().write(AjaxUtil.toJson(prof));
790                }
791            } catch (final NoSuchPrincipalException e) {
792                throw new ServletException(e);
793            }
794        }
795
796        /**
797         *  Directly returns the UserProfile object attached to an uid.
798         *
799         *  @param uid The user id (e.g. WikiName)
800         *  @return A UserProfile object
801         *  @throws NoSuchPrincipalException If such a name does not exist.
802         */
803        public UserProfile getUserInfo( String uid )
804            throws NoSuchPrincipalException
805        {
806            if( m_manager != null )
807            {
808                final UserProfile prof = m_manager.getUserDatabase().find( uid );
809
810                return prof;
811            }
812
813            throw new IllegalStateException("The manager is offline.");
814        }
815    }
816}