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.logging.log4j.LogManager;
023import org.apache.logging.log4j.Logger;
024import org.apache.wiki.ajax.AjaxUtil;
025import org.apache.wiki.ajax.WikiAjaxDispatcherServlet;
026import org.apache.wiki.ajax.WikiAjaxServlet;
027import org.apache.wiki.api.core.Context;
028import org.apache.wiki.api.core.Engine;
029import org.apache.wiki.api.core.Session;
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.DummyUserDatabase;
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.FilterManager;
043import org.apache.wiki.filters.SpamFilter;
044import org.apache.wiki.i18n.InternationalizationManager;
045import org.apache.wiki.pages.PageManager;
046import org.apache.wiki.preferences.Preferences;
047import org.apache.wiki.tasks.TasksManager;
048import org.apache.wiki.ui.InputValidator;
049import org.apache.wiki.util.ClassUtil;
050import org.apache.wiki.util.TextUtil;
051import org.apache.wiki.workflow.Decision;
052import org.apache.wiki.workflow.DecisionRequiredException;
053import org.apache.wiki.workflow.Fact;
054import org.apache.wiki.workflow.Step;
055import org.apache.wiki.workflow.Workflow;
056import org.apache.wiki.workflow.WorkflowBuilder;
057import org.apache.wiki.workflow.WorkflowManager;
058
059import jakarta.servlet.ServletException;
060import jakarta.servlet.http.HttpServletRequest;
061import jakarta.servlet.http.HttpServletResponse;
062import java.io.IOException;
063import java.security.Permission;
064import java.security.Principal;
065import java.text.MessageFormat;
066import java.util.List;
067import java.util.Map;
068import java.util.NoSuchElementException;
069import java.util.Properties;
070import java.util.ResourceBundle;
071import java.util.WeakHashMap;
072import org.apache.wiki.auth.authorize.Group;
073import org.apache.wiki.auth.authorize.GroupManager;
074import tools.jackson.databind.ObjectMapper;
075import tools.jackson.databind.node.ObjectNode;
076
077
078
079/**
080 * Default implementation for {@link UserManager}.
081 *
082 * @since 2.3
083 */
084public class DefaultUserManager implements UserManager {
085
086    private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user";
087    public static final String SESSION_MESSAGES = "profile";
088    private static final String PARAM_EMAIL = "email";
089    private static final String PARAM_FULLNAME = "fullname";
090    private static final String PARAM_PASSWORD = "password";
091    private static final String PARAM_LOGINNAME = "loginname";
092    private static final String UNKNOWN_CLASS = "<unknown>";
093
094    private Engine m_engine;
095
096    private static final Logger LOG = LogManager.getLogger( DefaultUserManager.class);
097
098    /** Associates wiki sessions with profiles */
099    private final Map< Session, UserProfile > m_profiles = new WeakHashMap<>();
100
101    /** The user database loads, manages and persists user identities */
102    private UserDatabase m_database;
103
104    /** {@inheritDoc} */
105    @Override
106    public void initialize( final Engine engine, final Properties props ) {
107        m_engine = engine;
108
109        // Attach the PageManager as a listener
110        // TODO: it would be better if we did this in PageManager directly
111        addWikiEventListener( engine.getManager( PageManager.class ) );
112
113        //TODO: Replace with custom annotations. See JSPWIKI-566
114        WikiAjaxDispatcherServlet.registerServlet( JSON_USERS, new JSONUserModule(this), new AllPermission(null));
115    }
116
117    /** {@inheritDoc} */
118    @Override
119    public UserDatabase getUserDatabase() {
120        if( m_database != null ) {
121            return m_database;
122        }
123
124        String dbClassName = UNKNOWN_CLASS;
125
126        try {
127            dbClassName = TextUtil.getRequiredProperty( m_engine.getWikiProperties(), PROP_DATABASE );
128
129            LOG.info( "Attempting to load user database class {}", dbClassName );
130            m_database = ClassUtil.buildInstance( USERDATABASE_PACKAGE, dbClassName );
131            m_database.initialize( m_engine, m_engine.getWikiProperties() );
132            LOG.info( "UserDatabase initialized." );
133        } catch( final NoSuchElementException | NoRequiredPropertyException e ) {
134            LOG.error( "You have not set the '{}'. You need to do this if you want to enable user management by JSPWiki.", PROP_DATABASE, e );
135        } catch( final ReflectiveOperationException e ) {
136            LOG.error( "UserDatabase {} cannot be instantiated", dbClassName, e );
137        } catch( final WikiSecurityException e ) {
138            LOG.error( "Exception initializing user database: {}", e.getMessage(), e );
139        } finally {
140            if( m_database == null ) {
141                LOG.info( "I could not create a database object you specified (or didn't specify), so I am falling back to a default." );
142                m_database = new DummyUserDatabase();
143            }
144        }
145
146        return m_database;
147    }
148
149    /** {@inheritDoc} */
150    @Override
151    public UserProfile getUserProfile( final Session session ) {
152        // Look up cached user profile
153        UserProfile profile = m_profiles.get( session );
154        boolean newProfile = profile == null;
155        Principal user = null;
156
157        // If user is authenticated, figure out if this is an existing profile
158        if ( session.isAuthenticated() ) {
159            user = session.getUserPrincipal();
160            try {
161                profile = getUserDatabase().findByWikiName( user.getName());
162                newProfile = false;
163            } catch( final NoSuchPrincipalException e ) { 
164                LOG.debug(e.getMessage(), e);
165            }
166        }
167
168        if ( newProfile ) {
169            profile = getUserDatabase().newProfile();
170            if ( user != null ) {
171                profile.setLoginName( user.getName() );
172            } else {
173                LOG.warn("new profile however the user principal is null. this shouldn't happen");
174            }
175            if ( !profile.isNew() ) {
176                throw new IllegalStateException( "New profile should be marked 'new'. Check your UserProfile implementation." );
177            }
178        }
179
180        // Stash the profile for next time
181        m_profiles.put( session, profile );
182        return profile;
183    }
184
185    /** {@inheritDoc} */
186    @Override
187    public void setUserProfile( final Context context, final UserProfile profile ) throws DuplicateUserException, WikiException {
188        final Session session = context.getWikiSession();
189        // Verify user is allowed to save profile!
190        final Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
191        if ( !m_engine.getManager( AuthorizationManager.class ).checkPermission( session, p ) ) {
192            throw new WikiSecurityException( "You are not allowed to save wiki profiles." );
193        }
194
195        // Check if profile is new, and see if container allows creation
196        final boolean newProfile = profile.isNew();
197
198        // Check if another user profile already has the fullname or loginname
199        final UserProfile oldProfile = getUserProfile( session );
200        final boolean nameChanged = ( oldProfile != null && oldProfile.getFullname() != null ) &&
201                                    !( oldProfile.getFullname().equals( profile.getFullname() ) &&
202                                    oldProfile.getLoginName().equals( profile.getLoginName() ) );
203        UserProfile otherProfile;
204        try {
205            otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() );
206            if( otherProfile != null && !otherProfile.equals( oldProfile ) ) {
207                throw new DuplicateUserException( "security.error.login.taken", profile.getLoginName() );
208            }
209        } catch (final NoSuchPrincipalException e) {
210            LOG.debug(e.getMessage(), e);
211        }
212        try {
213            otherProfile = getUserDatabase().findByFullName( profile.getFullname() );
214            if( otherProfile != null && !otherProfile.equals( oldProfile ) ) {
215                throw new DuplicateUserException( "security.error.fullname.taken", profile.getFullname() );
216            }
217        } catch( final NoSuchPrincipalException e ) {
218            LOG.debug(e.getMessage(), e);
219        }
220
221        // For new accounts, create approval workflow for user profile save.
222        if( newProfile && oldProfile != null && oldProfile.isNew() ) {
223            startUserProfileCreationWorkflow( context, profile );
224
225            // If the profile doesn't need approval, then just log the user in
226
227            try {
228                final AuthenticationManager mgr = m_engine.getManager( AuthenticationManager.class );
229                if( !mgr.isContainerAuthenticated() ) {
230                    mgr.login( session, null, profile.getLoginName(), profile.getPassword() );
231                }
232            } catch( final WikiException e ) {
233                throw new WikiSecurityException( e.getMessage(), e );
234            }
235
236            // Alert all listeners that the profile changed...
237            // ...this will cause credentials to be reloaded in the wiki session
238            fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
239        } else { // For existing accounts, just save the profile
240            // If login name changed, rename it first
241            if( nameChanged && !oldProfile.getLoginName().equals( profile.getLoginName() ) ) {
242                getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() );
243            }
244
245            // Now, save the profile (userdatabase will take care of timestamps for us)
246            getUserDatabase().save( profile );
247
248            if( nameChanged ) {
249                // Fire an event if the login name or full name changed
250                final UserProfile[] profiles = new UserProfile[] { oldProfile, profile };
251                fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles );
252            } else {
253                // Fire an event that says we have new a new profile (new principals)
254                fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
255            }
256        }
257        m_profiles.put( session, profile );
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public void startUserProfileCreationWorkflow( final Context context, final UserProfile profile ) throws WikiException {
263        final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
264        final Principal submitter = context.getWikiSession().getUserPrincipal();
265        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveUserProfileTask( context.getWikiSession().getLocale() );
266
267        // Add user profile attribute as Facts for the approver (if required)
268        final boolean hasEmail = profile.getEmail() != null;
269        final Fact[] facts = new Fact[ hasEmail ? 4 : 3 ];
270        facts[ 0 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() );
271        facts[ 1 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() );
272        facts[ 2 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, submitter.getName() );
273        if ( hasEmail ) {
274            facts[ 3 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() );
275        }
276        final Workflow workflow = builder.buildApprovalWorkflow( submitter,
277                                                                 WorkflowManager.WF_UP_CREATE_SAVE_APPROVER,
278                                                                 null,
279                                                                 WorkflowManager.WF_UP_CREATE_SAVE_DECISION_MESSAGE_KEY,
280                                                                 facts,
281                                                                 completionTask,
282                                                                 null );
283
284        workflow.setAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE, profile );
285        workflow.start( context );
286
287        final boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
288
289        // If the profile requires approval, redirect user to message page
290        if ( approvalRequired ) {
291            throw new DecisionRequiredException( "This profile must be approved before it becomes active" );
292        }
293    }
294
295    /** {@inheritDoc} */
296    @Override
297    public UserProfile parseProfile( final Context context ) {
298        // Retrieve the user's profile (may have been previously cached)
299        final UserProfile profile = getUserProfile( context.getWikiSession() );
300        final HttpServletRequest request = context.getHttpRequest();
301
302        // Extract values from request stream (cleanse whitespace as needed)
303        String loginName = request.getParameter( PARAM_LOGINNAME );
304        String password = request.getParameter( PARAM_PASSWORD );
305        String fullname = request.getParameter( PARAM_FULLNAME );
306        String email = request.getParameter( PARAM_EMAIL );
307        loginName = StringUtils.trim( loginName );
308        password = InputValidator.isBlank( password ) ? null : password;
309        fullname = StringUtils.trim( fullname );
310        email = StringUtils.trim( email );
311
312        // A special case if we have container authentication: if authenticated, login name is always taken from container
313        if ( m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated() && context.getWikiSession().isAuthenticated() ) {
314            loginName = context.getWikiSession().getLoginPrincipal().getName();
315        }
316
317        // Set the profile fields!
318        profile.setLoginName( loginName );
319        profile.setEmail( email );
320        profile.setFullname( fullname );
321        profile.setPassword( password );
322        return profile;
323    }
324
325    /** {@inheritDoc} */
326    @Override
327    public void validateProfile( final Context context, final UserProfile profile ) {
328        final Session session = context.getWikiSession();
329        final InputValidator validator = new InputValidator( SESSION_MESSAGES, context );
330        final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
331
332        //  Query the SpamFilter first
333        final FilterManager fm = m_engine.getManager( FilterManager.class );
334        final List< PageFilter > ls = fm.getFilterList();
335        for( final PageFilter pf : ls ) {
336            if( pf instanceof SpamFilter ) {
337                if( !( ( SpamFilter )pf ).isValidUserProfile( context, profile ) ) {
338                    session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
339                    return;
340                }
341                break;
342            }
343        }
344
345        // If container-managed auth and user not logged in, throw an error
346        if ( m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated()
347             && !context.getWikiSession().isAuthenticated() ) {
348            session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") );
349        }
350
351        validator.validateNotNull( profile.getLoginName(), rb.getString("security.user.loginname") );
352        validator.validateNotNull( profile.getFullname(), rb.getString("security.user.fullname") );
353        validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL );
354
355        if( !m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated() ) {
356            // passwords must match and can't be null
357            
358            //this is the new password
359            final String newpassword = profile.getPassword();
360            if( newpassword == null ) {
361                session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.blankpassword" ) );
362            } else {
363                final HttpServletRequest request = context.getHttpRequest();
364                //the existing password
365                final String existingPassword = ( request == null ) ? null : request.getParameter( "password0" );
366                //the new password confirmation
367                final String passwordConfirmation = ( request == null ) ? null : request.getParameter( "password2" );
368                if (!newpassword.equals(passwordConfirmation)) {
369                    //password confirmation does not match
370                    session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.passwordnomatch" ) );
371                }
372                if( !profile.isNew() && (existingPassword==null || existingPassword.equals( newpassword ) ) ) {
373                    //existing account and the existing password matches the new password
374                    session.addMessage( SESSION_MESSAGES, "existing password matches the proposed new one" );
375                }
376                if( !profile.isNew() && !getUserDatabase().validatePassword( profile.getLoginName(), existingPassword ) ) {
377                    //existing account and the provided password does not match what we currently have
378                    session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.passwordnomatch" ) );
379                }
380                List<String> msg = PasswordComplexityVerifier.validate(passwordConfirmation, existingPassword, context);
381                for (String s : msg) {
382                    session.addMessage( SESSION_MESSAGES, s );
383                }
384                int reuseCount = Integer.parseInt(m_engine.getWikiProperties().getProperty("jspwiki.credentials.reuseCount", "-1"));
385                if (reuseCount > 0) {
386                    //if it's set to 0 or less, we don't store it so we can skip this check
387                    if (!m_database.validatePasswordReuse(profile.getLoginName(), passwordConfirmation)) {
388                        //password reuse detected
389                        session.addMessage(SESSION_MESSAGES,
390                                MessageFormat.format(rb.getString("security.error.passwordReuseError"), reuseCount));
391                    }
392                }
393            }
394        }
395
396        UserProfile otherProfile;
397        final String fullName = profile.getFullname();
398        final String loginName = profile.getLoginName();
399        final String wikiName = profile.getWikiName();
400        final String email = profile.getEmail();
401
402        
403        if ("true".equalsIgnoreCase(m_engine.getWikiProperties().getProperty(Engine.PROP_USE_2_X_ACL_LOGIC, "false"))) {
404            // It's illegal to use as a full name someone else's login name
405            try {
406                otherProfile = getUserDatabase().findByFullName(fullName );
407                if( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) ) {
408                    final Object[] args = { fullName };
409                    session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalfullname" ), args ) );
410                }
411            } catch( final NoSuchPrincipalException e ) {
412                LOG.debug(e.getMessage(), e);
413                /* It's clean */ 
414            }
415
416            // It's illegal to use as a login name someone else's full name
417            try {
418                otherProfile = getUserDatabase().findByLoginName(loginName );
419                if( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) {
420                    final Object[] args = { loginName };
421                    session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalloginname" ), args ) );
422                }
423            } catch( final NoSuchPrincipalException e ) { 
424                LOG.debug(e.getMessage(), e);
425                /* It's clean */ 
426            }
427        } else {
428            //JSPWIKI-130, v3+ behavior
429            // It is legal to use as a full name someone else's login name
430
431            // It's illegal to use as a login name someone else's full name
432            try {
433                otherProfile = getUserDatabase().findByLoginName(loginName );
434                if( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) {
435                    final Object[] args = { loginName };
436                    session.addMessage( SESSION_MESSAGES, MessageFormat.format( 
437                            rb.getString( "security.error.illegalloginname" ), args ) );
438                }
439            } catch( final NoSuchPrincipalException e ) { 
440                LOG.debug(e.getMessage(), e);
441                /* It's clean */ 
442            }
443            //it's illegal to use a username, email or wiki name as a group name
444            try {
445                Group[] groups = m_engine.getManager(GroupManager.class).getGroupDatabase().groups();
446                for (Group grp : groups) {
447                    if (grp.getName().equals(loginName)) {
448                        final Object[] args = {loginName};
449                        session.addMessage(SESSION_MESSAGES,
450                                MessageFormat.format(rb.getString("security.error.illegalloginname"), args));
451                    }
452                    if (grp.getName().equals(wikiName)) {
453                        final Object[] args = {wikiName};
454                        session.addMessage(SESSION_MESSAGES,
455                                MessageFormat.format(rb.getString("security.error.illegalloginname"), args));
456                    }
457                    if (grp.getName().equals(email)) {
458                        final Object[] args = {email};
459                        session.addMessage(SESSION_MESSAGES,
460                                MessageFormat.format(rb.getString("security.error.illegalloginname"), args));
461                    }
462                }
463            } catch (WikiSecurityException ex) {
464                session.addMessage(SESSION_MESSAGES,
465                        "Processing failed. see log for details.");
466                LOG.error("failed to query for groups", ex);
467            }
468            //wiki names must be unique as well.
469            try {
470                otherProfile = getUserDatabase().findByWikiName(wikiName );
471                if( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) {
472                    final Object[] args = { loginName };
473                    session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalloginname" ), args ) );
474                }
475            } catch( final NoSuchPrincipalException e ) { 
476                LOG.debug(e.getMessage(), e);
477                /* It's clean */ 
478            }
479        }
480        
481
482        // It's illegal to use multiple accounts with the same email
483        if (email != null && email.trim().length() > 0) {
484            try {
485                otherProfile = getUserDatabase().findByEmail( email );
486                if( otherProfile != null && !profile.getUid().equals( otherProfile.getUid() ) // Issue JSPWIKI-1042
487                        && !profile.equals( otherProfile ) && StringUtils.lowerCase( email )
488                        .equals( StringUtils.lowerCase( otherProfile.getEmail() ) ) ) {
489                    final Object[] args = { email };
490                    session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.email.taken" ), args ) );
491                }
492            } catch( final NoSuchPrincipalException e ) { 
493                LOG.debug(e.getMessage(), e);
494                /* It's clean */ 
495            }
496        }
497    }
498
499    /** {@inheritDoc} */
500    @Override
501    public Principal[] listWikiNames() throws WikiSecurityException {
502        return getUserDatabase().getWikiNames();
503    }
504
505    // events processing .......................................................
506
507    /**
508     * Registers a WikiEventListener with this instance.
509     * This is a convenience method.
510     * @param listener the event listener
511     */
512    @Override public synchronized void addWikiEventListener( final WikiEventListener listener ) {
513        WikiEventManager.addWikiEventListener( this, listener );
514    }
515
516    /**
517     * Un-registers a WikiEventListener with this instance.
518     * This is a convenience method.
519     * @param listener the event listener
520     */
521    @Override public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
522        WikiEventManager.removeWikiEventListener( this, listener );
523    }
524
525    /**
526     *  Implements the JSON API for usermanager.
527     *  <p>
528     *  Even though this gets serialized whenever container shuts down/restarts, this gets reinstalled to the session when JSPWiki starts.
529     *  This means that it's not actually necessary to save anything.
530     */
531    public static final class JSONUserModule implements WikiAjaxServlet {
532
533        private final DefaultUserManager m_manager;
534
535        /**
536         *  Create a new JSONUserModule.
537         *  @param mgr Manager
538         */
539        public JSONUserModule( final DefaultUserManager mgr )
540        {
541            m_manager = mgr;
542        }
543
544        @Override
545        public String getServletMapping() {
546            return JSON_USERS;
547        }
548
549        @Override
550        public void service( final HttpServletRequest req, final HttpServletResponse resp, final String actionName, final List<String> params) throws ServletException, IOException {
551            try {
552                if( params.isEmpty() ) {
553                    return;
554                }
555                resp.setContentType("application/json");
556                final String uid = params.get(0);
557                LOG.debug("uid="+uid);
558                if (StringUtils.isNotBlank(uid)) {
559                    final UserProfile prof = getUserInfo(uid);
560                    //clone the object
561                    ObjectMapper om = new ObjectMapper();
562                    ObjectNode node = om.convertValue(prof, ObjectNode.class);
563                    node.remove("password");
564                    node.remove("previousHashedCredentials");
565                    resp.getWriter().write(node.toString());
566                }
567            } catch (final NoSuchPrincipalException e) {
568                    throw new ServletException(e);
569            }
570        }
571
572        /**
573         *  Directly returns the UserProfile object attached to an uid.
574         *
575         *  @param uid The user id (e.g. WikiName)
576         *  @return A UserProfile object
577         *  @throws NoSuchPrincipalException If such a name does not exist.
578         */
579        public UserProfile getUserInfo( final String uid ) throws NoSuchPrincipalException {
580            if( m_manager != null ) {
581                return m_manager.getUserDatabase().findByUid( uid );
582            }
583
584            throw new IllegalStateException( "The manager is offline." );
585        }
586    }
587
588}