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