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;
020
021import java.security.AccessControlException;
022import java.security.Principal;
023import java.security.PrivilegedAction;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.LinkedHashSet;
029import java.util.Locale;
030import java.util.Map;
031import java.util.Set;
032
033import javax.security.auth.Subject;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpSession;
036
037import org.apache.commons.lang.StringUtils;
038import org.apache.log4j.Logger;
039import org.apache.wiki.auth.AuthenticationManager;
040import org.apache.wiki.auth.GroupPrincipal;
041import org.apache.wiki.auth.NoSuchPrincipalException;
042import org.apache.wiki.auth.SessionMonitor;
043import org.apache.wiki.auth.UserManager;
044import org.apache.wiki.auth.WikiPrincipal;
045import org.apache.wiki.auth.authorize.Group;
046import org.apache.wiki.auth.authorize.GroupManager;
047import org.apache.wiki.auth.authorize.Role;
048import org.apache.wiki.auth.user.UserDatabase;
049import org.apache.wiki.auth.user.UserProfile;
050import org.apache.wiki.event.WikiEvent;
051import org.apache.wiki.event.WikiEventListener;
052import org.apache.wiki.event.WikiSecurityEvent;
053
054/**
055 * <p>Represents a long-running wiki session, with an associated user Principal,
056 * user Subject, and authentication status. This class is initialized with
057 * minimal, default-deny values: authentication is set to <code>false</code>,
058 * and the user principal is set to <code>null</code>.</p>
059 * <p>The WikiSession class allows callers to:</p>
060 * <ul>
061 *   <li>Obtain the authentication status of the user via
062 *     {@link #isAnonymous()} and {@link #isAuthenticated()}</li>
063 *   <li>Query the session for Principals representing the
064 *     user's identity via {@link #getLoginPrincipal()},
065 *     {@link #getUserPrincipal()} and {@link #getPrincipals()}</li>
066 *   <li>Store, retrieve and clear UI messages via
067 *     {@link #addMessage(String)}, {@link #getMessages(String)}
068 *     and {@link #clearMessages(String)}</li>
069 * </ul>
070 * <p>To keep track of the Principals each user posseses, each WikiSession
071 * stores a JAAS Subject. Various login processes add or remove Principals
072 * when users authenticate or log out.</p>
073 * <p>WikiSession implements the {@link org.apache.wiki.event.WikiEventListener}
074 * interface and listens for group add/change/delete events fired by
075 * event sources the WikiSession is registered with. Normally,
076 * {@link org.apache.wiki.auth.AuthenticationManager} registers each WikiSession
077 * with the {@link org.apache.wiki.auth.authorize.GroupManager}
078 * so it can catch group events. Thus, when a user is added to a
079 * {@link org.apache.wiki.auth.authorize.Group}, a corresponding
080 * {@link org.apache.wiki.auth.GroupPrincipal} is injected into
081 * the Subject's Principal set. Likewise, when the user is removed from
082 * the Group or the Group is deleted, the GroupPrincipal is removed
083 * from the Subject. The effect that this strategy produces is extremely
084 * beneficial: when someone adds a user to a wiki group, that user
085 * <em>immediately</em> gains the privileges associated with that
086 * group; he or she does not need to re-authenticate.
087 * </p>
088 * <p>In addition to methods for examining individual <code>WikiSession</code>
089 * objects, this class also contains a number of static methods for
090 * managing WikiSessions for an entire wiki. These methods allow callers
091 * to find, query and remove WikiSession objects, and
092 * to obtain a list of the current wiki session users.</p>
093 * <p>WikiSession encloses a protected static class, {@link SessionMonitor},
094 * to keep track of WikiSessions registered with each wiki.</p>
095 */
096public final class WikiSession implements WikiEventListener
097{
098
099    /** An anonymous user's session status. */
100    public static final String  ANONYMOUS             = "anonymous";
101
102    /** An asserted user's session status. */
103    public static final String  ASSERTED              = "asserted";
104
105    /** An authenticated user's session status. */
106    public static final String  AUTHENTICATED         = "authenticated";
107
108    private static final int    ONE                   = 48;
109
110    private static final int    NINE                  = 57;
111
112    private static final int    DOT                   = 46;
113
114    private static final Logger log                   = Logger.getLogger( WikiSession.class );
115
116    private static final String ALL                   = "*";
117
118    private static ThreadLocal<WikiSession> c_guestSession = new ThreadLocal<>();
119
120    private final Subject       m_subject             = new Subject();
121
122    private final Map<String,Set<String>> m_messages  = new HashMap<>();
123
124    /** The WikiEngine that created this session. */
125    private WikiEngine          m_engine              = null;
126
127    private String              m_status              = ANONYMOUS;
128
129    private Principal           m_userPrincipal       = WikiPrincipal.GUEST;
130
131    private Principal           m_loginPrincipal      = WikiPrincipal.GUEST;
132
133    private Locale              m_cachedLocale        = Locale.getDefault();
134
135    /**
136     * Returns <code>true</code> if one of this WikiSession's user Principals
137     * can be shown to belong to a particular wiki group. If the user is
138     * not authenticated, this method will always return <code>false</code>.
139     * @param group the group to test
140     * @return the result
141     */
142    protected boolean isInGroup( Group group )
143    {
144        for ( Principal principal : getPrincipals() )
145        {
146          if ( isAuthenticated() && group.isMember( principal ) )
147          {
148              return true;
149          }
150        }
151        return false;
152    }
153
154    /**
155     * Private constructor to prevent WikiSession from being instantiated
156     * directly.
157     */
158    private WikiSession()
159    {
160    }
161
162    /**
163     * Returns <code>true</code> if the user is considered asserted via
164     * a session cookie; that is, the Subject contains the Principal
165     * Role.ASSERTED.
166     * @return Returns <code>true</code> if the user is asserted
167     */
168    public boolean isAsserted()
169    {
170        return m_subject.getPrincipals().contains( Role.ASSERTED );
171    }
172
173    /**
174     * Returns the authentication status of the user's session. The user is
175     * considered authenticated if the Subject contains the Principal
176     * Role.AUTHENTICATED. If this method determines that an earlier
177     * LoginModule did not inject Role.AUTHENTICATED, it will inject one
178     * if the user is not anonymous <em>and</em> not asserted.
179     * @return Returns <code>true</code> if the user is authenticated
180     */
181    public boolean isAuthenticated()
182    {
183        // If Role.AUTHENTICATED is in principals set, always return true.
184        if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) )
185        {
186            return true;
187        }
188
189        // With non-JSPWiki LoginModules, the role may not be there, so
190        // we need to add it if the user really is authenticated.
191        if ( !isAnonymous() && !isAsserted() )
192        {
193            // Inject AUTHENTICATED role
194            m_subject.getPrincipals().add( Role.AUTHENTICATED );
195            return true;
196        }
197
198        return false;
199    }
200
201    /**
202     * <p>Determines whether the current session is anonymous. This will be
203     * true if any of these conditions are true:</p>
204     * <ul>
205     *   <li>The session's Principal set contains
206     *       {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li>
207     *   <li>The session's Principal set contains
208     *       {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li>
209     *   <li>The Principal returned by {@link #getUserPrincipal()} evaluates
210     *       to an IP address.</li>
211     * </ul>
212     * <p>The criteria above are listed in the order in which they are
213     * evaluated.</p>
214     * @return whether the current user's identity is equivalent to an IP
215     * address
216     */
217    public boolean isAnonymous()
218    {
219        Set<Principal> principals = m_subject.getPrincipals();
220        return principals.contains( Role.ANONYMOUS ) ||
221                 principals.contains( WikiPrincipal.GUEST ) ||
222                 isIPV4Address( getUserPrincipal().getName() );
223    }
224
225    /**
226     * <p> Returns the Principal used to log in to an authenticated session. The
227     * login principal is determined by examining the Subject's Principal set
228     * for PrincipalWrappers or WikiPrincipals with type designator
229     * <code>LOGIN_NAME</code>; the first one found is the login principal.
230     * If one is not found, this method returns the first principal that isn't
231     * of type Role or GroupPrincipal. If neither of these conditions hold, this method returns
232     * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
233     * @return the login Principal. If it is a PrincipalWrapper containing an
234     * externally-provided Principal, the object returned is the Principal, not
235     * the wrapper around it.
236     */
237    public Principal getLoginPrincipal()
238    {
239        return m_loginPrincipal;
240    }
241
242    /**
243     * <p>Returns the primary user Principal associated with this session. The
244     * primary user principal is determined as follows:</p> <ol> <li>If the
245     * Subject's Principal set contains WikiPrincipals, the first WikiPrincipal
246     * with type designator <code>WIKI_NAME</code> or (alternatively)
247     * <code>FULL_NAME</code> is the primary Principal.</li>
248     *   <li>For all other cases, the first Principal in the Subject's principal
249     *       collection that that isn't of type Role or GroupPrincipal is the primary.</li>
250     * </ol>
251     * If no primary user Principal is found, this method returns
252     * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
253     * @return the primary user Principal
254     */
255    public Principal getUserPrincipal()
256    {
257        return m_userPrincipal;
258    }
259
260    /**
261     *  Returns a cached Locale object for this user.  It's better to use
262     *  WikiContext's corresponding getBundle() method, since that will actually
263     *  react if the user changes the locale in the middle, but if that's not
264     *  available (or, for some reason, you need the speed), this method can
265     *  also be used.  The Locale expires when the WikiSession expires, and
266     *  currently there is no way to reset the Locale.
267     *
268     *  @return A cached Locale object
269     *  @since 2.5.96
270     */
271    public Locale getLocale()
272    {
273        return m_cachedLocale;
274    }
275
276    /**
277     * Adds a message to the generic list of messages associated with the
278     * session. These messages retain their order of insertion and remain until
279     * the {@link #clearMessages()} method is called.
280     * @param message the message to add; if <code>null</code> it is ignored.
281     */
282    public void addMessage(String message)
283    {
284        addMessage( ALL, message );
285    }
286
287
288    /**
289     * Adds a message to the specific set of messages associated with the
290     * session. These messages retain their order of insertion and remain until
291     * the {@link #clearMessages()} method is called.
292     * @param topic the topic to associate the message to;
293     * @param message the message to add
294     */
295    public void addMessage(String topic, String message)
296    {
297        if ( topic == null )
298        {
299            throw new IllegalArgumentException( "addMessage: topic cannot be null." );
300        }
301        Set<String> messages = m_messages.get( topic );
302        if (messages == null )
303        {
304            messages = new LinkedHashSet<>();
305            m_messages.put( topic, messages );
306        }
307        messages.add( StringUtils.defaultString( message ) );
308    }
309
310    /**
311     * Clears all messages associated with this session.
312     */
313    public void clearMessages()
314    {
315        m_messages.clear();
316    }
317
318    /**
319     * Clears all messages associated with a session topic.
320     * @param topic the topic whose messages should be cleared.
321     */
322    public void clearMessages( String topic )
323    {
324        Set<String> messages = m_messages.get( topic );
325        if ( messages != null )
326        {
327            m_messages.clear();
328        }
329    }
330
331    /**
332     * Returns all generic messages associated with this session.
333     * The messages stored with the session persist throughout the
334     * session unless they have been reset with {@link #clearMessages()}.
335     * @return the current messages.
336     */
337    public String[] getMessages()
338    {
339        return getMessages( ALL );
340    }
341
342    /**
343     * Returns all messages associated with a session topic.
344     * The messages stored with the session persist throughout the
345     * session unless they have been reset with {@link #clearMessages(String)}.
346     * @return the current messages.
347     * @param topic The topic
348     */
349    public String[] getMessages( String topic )
350    {
351        Set<String> messages = m_messages.get( topic );
352        if ( messages == null || messages.size() == 0 )
353        {
354            return new String[0];
355        }
356        return messages.toArray( new String[messages.size()] );
357    }
358
359    /**
360     * Returns all user Principals associated with this session. User principals
361     * are those in the Subject's principal collection that aren't of type Role or
362     * of type GroupPrincipal. This is a defensive copy.
363     * @return Returns the user principal
364     * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal)
365     */
366    public Principal[] getPrincipals()
367    {
368        ArrayList<Principal> principals = new ArrayList<>();
369
370        // Take the first non Role as the main Principal
371        for( Principal principal : m_subject.getPrincipals() )
372        {
373            if ( AuthenticationManager.isUserPrincipal( principal ) )
374            {
375                principals.add( principal );
376            }
377        }
378
379        return principals.toArray( new Principal[principals.size()] );
380    }
381
382    /**
383     * Returns an array of Principal objects that represents the groups and
384     * roles that the user associated with a WikiSession possesses. The array is
385     * built by iterating through the Subject's Principal set and extracting all
386     * Role and GroupPrincipal objects into a list. The list is returned as an
387     * array sorted in the natural order implied by each Principal's
388     * <code>getName</code> method. Note that this method does <em>not</em>
389     * consult the external Authorizer or GroupManager; it relies on the
390     * Principals that have been injected into the user's Subject at login time,
391     * or after group creation/modification/deletion.
392     * @return an array of Principal objects corresponding to the roles the
393     *         Subject possesses
394     */
395    public Principal[] getRoles()
396    {
397        Set<Principal> roles = new HashSet<>();
398
399        // Add all of the Roles possessed by the Subject directly
400        roles.addAll( m_subject.getPrincipals( Role.class ) );
401
402        // Add all of the GroupPrincipals possessed by the Subject directly
403        roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) );
404
405        // Return a defensive copy
406        Principal[] roleArray = roles.toArray( new Principal[roles.size()] );
407        Arrays.sort( roleArray, WikiPrincipal.COMPARATOR );
408        return roleArray;
409    }
410
411    /**
412     * Removes the wiki session associated with the user's HTTP request
413     * from the cache of wiki sessions, typically as part of a logout
414     * process.
415     * @param engine the wiki engine
416     * @param request the users's HTTP request
417     */
418    public static void removeWikiSession( WikiEngine engine, HttpServletRequest request )
419    {
420        if ( engine == null || request == null )
421        {
422            throw new IllegalArgumentException( "Request or engine cannot be null." );
423        }
424        SessionMonitor monitor = SessionMonitor.getInstance( engine );
425        monitor.remove( request.getSession() );
426    }
427
428    /**
429     * Returns <code>true</code> if the WikiSession's Subject
430     * possess a supplied Principal. This method eliminates the need
431     * to externally request and inspect the JAAS subject.
432     * @param principal the Principal to test
433     * @return the result
434     */
435    public boolean hasPrincipal( Principal principal )
436    {
437        return m_subject.getPrincipals().contains( principal );
438
439    }
440
441    /**
442     * Listens for WikiEvents generated by source objects such as the
443     * GroupManager. This method adds Principals to the private Subject managed
444     * by the WikiSession.
445     * @see org.apache.wiki.event.WikiEventListener#actionPerformed(org.apache.wiki.event.WikiEvent)
446     */
447    @Override
448    public void actionPerformed( WikiEvent event )
449    {
450        if ( event instanceof WikiSecurityEvent )
451        {
452            WikiSecurityEvent e = (WikiSecurityEvent)event;
453            if ( e.getTarget() != null )
454            {
455                switch (e.getType() )
456                {
457                    case WikiSecurityEvent.GROUP_ADD:
458                    {
459                        Group group = (Group)e.getTarget();
460                        if ( isInGroup( group ) )
461                        {
462                            m_subject.getPrincipals().add( group.getPrincipal() );
463                        }
464                        break;
465                    }
466                    case WikiSecurityEvent.GROUP_REMOVE:
467                    {
468                        Group group = (Group)e.getTarget();
469                        if ( m_subject.getPrincipals().contains( group.getPrincipal() ) )
470                        {
471                            m_subject.getPrincipals().remove( group.getPrincipal() );
472                        }
473                        break;
474                    }
475                    case WikiSecurityEvent.GROUP_CLEAR_GROUPS:
476                    {
477                        m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) );
478                        break;
479                    }
480                    case WikiSecurityEvent.LOGIN_INITIATED:
481                    {
482                        // Do nothing
483                    }
484                    case WikiSecurityEvent.PRINCIPAL_ADD:
485                    {
486                        WikiSession target = (WikiSession)e.getTarget();
487                        if ( this.equals( target ) && m_status == AUTHENTICATED )
488                        {
489                            Set<Principal> principals = m_subject.getPrincipals();
490                            principals.add( (Principal)e.getPrincipal());
491                        }
492                        break;
493                    }
494                    case WikiSecurityEvent.LOGIN_ANONYMOUS:
495                    {
496                        WikiSession target = (WikiSession)e.getTarget();
497                        if ( this.equals( target ) )
498                        {
499                            m_status = ANONYMOUS;
500                            
501                            // Set the login/user principals and login status
502                            Set<Principal> principals = m_subject.getPrincipals();
503                            m_loginPrincipal = (Principal)e.getPrincipal();
504                            m_userPrincipal = m_loginPrincipal;
505                            
506                            // Add the login principal to the Subject, and set the built-in roles
507                            principals.clear();
508                            principals.add( m_loginPrincipal );
509                            principals.add( Role.ALL );
510                            principals.add( Role.ANONYMOUS );
511                        }
512                        break;
513                    }
514                    case WikiSecurityEvent.LOGIN_ASSERTED:
515                    {
516                        WikiSession target = (WikiSession)e.getTarget();
517                        if ( this.equals( target ) )
518                        {
519                            m_status = ASSERTED;
520                            
521                            // Set the login/user principals and login status
522                            Set<Principal> principals = m_subject.getPrincipals();
523                            m_loginPrincipal = (Principal)e.getPrincipal();
524                            m_userPrincipal = m_loginPrincipal;
525                            
526                            // Add the login principal to the Subject, and set the built-in roles
527                            principals.clear();
528                            principals.add( m_loginPrincipal );
529                            principals.add( Role.ALL );
530                            principals.add( Role.ASSERTED );
531                        }
532                        break;
533                    }
534                    case WikiSecurityEvent.LOGIN_AUTHENTICATED:
535                    {
536                        WikiSession target = (WikiSession)e.getTarget();
537                        if ( this.equals( target ) )
538                        {
539                            m_status = AUTHENTICATED;
540                            
541                            // Set the login/user principals and login status
542                            Set<Principal> principals = m_subject.getPrincipals();
543                            m_loginPrincipal = (Principal)e.getPrincipal();
544                            m_userPrincipal = m_loginPrincipal;
545                            
546                            // Add the login principal to the Subject, and set the built-in roles
547                            principals.clear();
548                            principals.add( m_loginPrincipal );
549                            principals.add( Role.ALL );
550                            principals.add( Role.AUTHENTICATED );
551                            
552                            // Add the user and group principals
553                            injectUserProfilePrincipals();  // Add principals for the user profile
554                            injectGroupPrincipals();  // Inject group principals
555                        }
556                        break;
557                    }
558                    case WikiSecurityEvent.PROFILE_SAVE:
559                    {
560                        WikiSession source = e.getSrc();
561                        if ( this.equals( source ) )
562                        {
563                            injectUserProfilePrincipals();  // Add principals for the user profile
564                            injectGroupPrincipals();  // Inject group principals
565                        }
566                        break;
567                    }
568                    case WikiSecurityEvent.PROFILE_NAME_CHANGED:
569                    {
570                        // Refresh user principals based on new user profile
571                        WikiSession source = e.getSrc();
572                        if ( this.equals( source ) && m_status == AUTHENTICATED )
573                        {
574                            // To prepare for refresh, set the new full name as the primary principal
575                            UserProfile[] profiles = (UserProfile[])e.getTarget();
576                            UserProfile newProfile = profiles[1];
577                            if ( newProfile.getFullname() == null )
578                            {
579                                throw new IllegalStateException( "User profile FullName cannot be null." );
580                            }
581                            
582                            Set<Principal> principals = m_subject.getPrincipals();
583                            m_loginPrincipal = new WikiPrincipal( newProfile.getLoginName() );
584                            
585                            // Add the login principal to the Subject, and set the built-in roles
586                            principals.clear();
587                            principals.add( m_loginPrincipal );
588                            principals.add( Role.ALL );
589                            principals.add( Role.AUTHENTICATED );
590                            
591                            // Add the user and group principals
592                            injectUserProfilePrincipals();  // Add principals for the user profile
593                            injectGroupPrincipals();  // Inject group principals
594                        }
595                        break;
596                    }
597
598                    //
599                    //  No action, if the event is not recognized.
600                    //
601                    default:
602                        break;
603                }
604            }
605        }
606    }
607
608    /**
609     * Invalidates the WikiSession and resets its Subject's
610     * Principals to the equivalent of a "guest session".
611     */
612    public void invalidate()
613    {
614        m_subject.getPrincipals().clear();
615        m_subject.getPrincipals().add( WikiPrincipal.GUEST );
616        m_subject.getPrincipals().add( Role.ANONYMOUS );
617        m_subject.getPrincipals().add( Role.ALL );
618        m_userPrincipal = WikiPrincipal.GUEST;
619        m_loginPrincipal = WikiPrincipal.GUEST;
620    }
621
622    /**
623     * Injects GroupPrincipal objects into the user's Principal set based on the
624     * groups the user belongs to. For Groups, the algorithm first calls the
625     * {@link GroupManager#getRoles()} to obtain the array of GroupPrincipals
626     * the authorizer knows about. Then, the method
627     * {@link GroupManager#isUserInRole(WikiSession, Principal)} is called for
628     * each Principal. If the user is a member of the group, an equivalent
629     * GroupPrincipal is injected into the user's principal set. Existing
630     * GroupPrincipals are flushed and replaced. This method should generally be
631     * called after a user's {@link org.apache.wiki.auth.user.UserProfile} is
632     * saved. If the wiki session is null, or there is no matching user profile,
633     * the method returns silently.
634     */
635    protected void injectGroupPrincipals()
636    {
637        // Flush the existing GroupPrincipals
638        m_subject.getPrincipals().removeAll( m_subject.getPrincipals(GroupPrincipal.class) );
639        
640        // Get the GroupManager and test for each Group
641        GroupManager manager = m_engine.getGroupManager();
642        for ( Principal group : manager.getRoles() )
643        {
644            if ( manager.isUserInRole( this, group ) )
645            {
646                m_subject.getPrincipals().add( group );
647            }
648        }
649    }
650
651    /**
652     * Adds Principal objects to the Subject that correspond to the
653     * logged-in user's profile attributes for the wiki name, full name
654     * and login name. These Principals will be WikiPrincipals, and they
655     * will replace all other WikiPrincipals in the Subject. <em>Note:
656     * this method is never called during anonymous or asserted sessions.</em>
657     */
658    protected void injectUserProfilePrincipals()
659    {
660        // Search for the user profile
661        String searchId = m_loginPrincipal.getName();
662        if ( searchId == null )
663        {
664            // Oh dear, this wasn't an authenticated user after all
665            log.info("Refresh principals failed because WikiSession had no user Principal; maybe not logged in?");
666            return;
667        }
668
669        // Look up the user and go get the new Principals
670        UserDatabase database = m_engine.getUserManager().getUserDatabase();
671        if ( database == null )
672        {
673            throw new IllegalStateException( "User database cannot be null." );
674        }
675        try
676        {
677            UserProfile profile = database.find( searchId );
678            Principal[] principals = database.getPrincipals( profile.getLoginName() );
679            for ( Principal principal : principals )
680            {
681                // Add the Principal to the Subject
682                m_subject.getPrincipals().add( principal );
683                
684                // Set the user principal if needed; we prefer FullName, but the WikiName will also work
685                boolean isFullNamePrincipal = ( principal instanceof WikiPrincipal && ((WikiPrincipal)principal).getType() == WikiPrincipal.FULL_NAME );
686                if ( isFullNamePrincipal )
687                {
688                   m_userPrincipal = principal; 
689                }
690                else if ( !( m_userPrincipal instanceof WikiPrincipal ) )
691                {
692                    m_userPrincipal = principal; 
693                }
694            }
695        }
696        catch ( NoSuchPrincipalException e )
697        {
698            // We will get here if the user has a principal but not a profile
699            // For example, it's a container-managed user who hasn't set up a profile yet
700            log.warn("User profile '" + searchId + "' not found. This is normal for container-auth users who haven't set up a profile yet.");
701        }
702    }
703
704    /**
705     * <p>Returns the status of the wiki session as a text string. Valid values are:</p>
706     * <ul>
707     *   <li>{@link #AUTHENTICATED}</li>
708     *   <li>{@link #ASSERTED}</li>
709     *   <li>{@link #ANONYMOUS}</li>
710     * </ul>
711     * @return the user's session status
712     */
713    public String getStatus()
714    {
715        return m_status;
716    }
717
718    /**
719     * <p>Static factory method that returns the WikiSession object associated with
720     * the current HTTP request. This method looks up the associated HttpSession
721     * in an internal WeakHashMap and attempts to retrieve the WikiSession. If
722     * not found, one is created. This method is guaranteed to always return a
723     * WikiSession, although the authentication status is unpredictable until
724     * the user attempts to log in. If the servlet request parameter is
725     * <code>null</code>, a synthetic {@link #guestSession(WikiEngine)}is returned.</p>
726     * <p>When a session is created, this method attaches a WikiEventListener
727     * to the GroupManager so that changes to groups are detected automatically.</p>
728     * @param engine the wiki engine
729     * @param request the servlet request object
730     * @return the existing (or newly created) wiki session
731     */
732    public static WikiSession getWikiSession( WikiEngine engine, HttpServletRequest request )
733    {
734        // If request is null, return guest session
735        if ( request == null )
736        {
737            if ( log.isDebugEnabled() )
738            {
739                log.debug( "Looking up WikiSession for NULL HttpRequest: returning guestSession()" );
740            }
741            return staticGuestSession( engine );
742        }
743
744        // Look for a WikiSession associated with the user's Http Session
745        // and create one if it isn't there yet.
746        HttpSession session = request.getSession();
747        SessionMonitor monitor = SessionMonitor.getInstance( engine );
748        WikiSession wikiSession = monitor.find( session );
749
750        // Attach reference to wiki engine
751        wikiSession.m_engine = engine;
752
753        wikiSession.m_cachedLocale = request.getLocale();
754
755        return wikiSession;
756    }
757
758    /**
759     * Static factory method that creates a new "guest" session containing a single
760     * user Principal {@link org.apache.wiki.auth.WikiPrincipal#GUEST},
761     * plus the role principals {@link Role#ALL} and
762     * {@link Role#ANONYMOUS}. This method also adds the session as a listener
763     * for GroupManager, AuthenticationManager and UserManager events.
764     * @param engine the wiki engine
765     * @return the guest wiki session
766     */
767    public static WikiSession guestSession( WikiEngine engine )
768    {
769        WikiSession session = new WikiSession();
770        session.m_engine = engine;
771        session.invalidate();
772
773        // Add the session as listener for GroupManager, AuthManager, UserManager events
774        GroupManager groupMgr = engine.getGroupManager();
775        AuthenticationManager authMgr = engine.getAuthenticationManager();
776        UserManager userMgr = engine.getUserManager();
777        groupMgr.addWikiEventListener( session );
778        authMgr.addWikiEventListener( session );
779        userMgr.addWikiEventListener( session );
780
781        return session;
782    }
783
784    /**
785     *  Returns a static guest session, which is available for this
786     *  thread only.  This guest session is used internally whenever
787     *  there is no HttpServletRequest involved, but the request is
788     *  done e.g. when embedding JSPWiki code.
789     *
790     *  @param engine WikiEngine for this session
791     *  @return A static WikiSession which is shared by all in this
792     *          same Thread.
793     */
794    // FIXME: Should really use WeakReferences to clean away unused sessions.
795
796    private static WikiSession staticGuestSession( WikiEngine engine )
797    {
798        WikiSession session = c_guestSession.get();
799
800        if( session == null )
801        {
802            session = guestSession( engine );
803
804            c_guestSession.set( session );
805        }
806
807        return session;
808    }
809
810    /**
811     * Returns the total number of active wiki sessions for a
812     * particular wiki. This method delegates to the wiki's
813     * {@link SessionMonitor#sessions()} method.
814     * @param engine the wiki session
815     * @return the number of sessions
816     */
817    public static int sessions( WikiEngine engine )
818    {
819        SessionMonitor monitor = SessionMonitor.getInstance( engine );
820        return monitor.sessions();
821    }
822
823    /**
824     * Returns Principals representing the current users known
825     * to a particular wiki. Each Principal will correspond to the
826     * value returned by each WikiSession's {@link #getUserPrincipal()}
827     * method. This method delegates to {@link SessionMonitor#userPrincipals()}.
828     * @param engine the wiki engine
829     * @return an array of Principal objects, sorted by name
830     */
831    public static Principal[] userPrincipals( WikiEngine engine )
832    {
833        SessionMonitor monitor = SessionMonitor.getInstance( engine );
834        return monitor.userPrincipals();
835    }
836
837    /**
838     * Wrapper for
839     * {@link javax.security.auth.Subject#doAsPrivileged(Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)}
840     * that executes an action with the privileges posssessed by a
841     * WikiSession's Subject. The action executes with a <code>null</code>
842     * AccessControlContext, which has the effect of running it "cleanly"
843     * without the AccessControlContexts of the caller.
844     * @param session the wiki session
845     * @param action the privileged action
846     * @return the result of the privileged action; may be <code>null</code>
847     * @throws java.security.AccessControlException if the action is not permitted
848     * by the security policy
849     */
850    public static Object doPrivileged( WikiSession session, PrivilegedAction<?> action ) throws AccessControlException
851    {
852        return Subject.doAsPrivileged( session.m_subject, action, null );
853    }
854
855    /**
856     * Verifies whether a String represents an IPv4 address. The algorithm is
857     * extremely efficient and does not allocate any objects.
858     * @param name the address to test
859     * @return the result
860     */
861    protected static boolean isIPV4Address( String name )
862    {
863        if ( name.charAt( 0 ) == DOT || name.charAt( name.length() - 1 ) == DOT )
864        {
865            return false;
866        }
867
868        int[] addr = new int[]
869        { 0, 0, 0, 0 };
870        int currentOctet = 0;
871        for( int i = 0; i < name.length(); i++ )
872        {
873            int ch = name.charAt( i );
874            boolean isDigit = ch >= ONE && ch <= NINE;
875            boolean isDot = ch == DOT;
876            if ( !isDigit && !isDot )
877            {
878                return false;
879            }
880            if ( isDigit )
881            {
882                addr[currentOctet] = 10 * addr[currentOctet] + ( ch - ONE );
883                if ( addr[currentOctet] > 255 )
884                {
885                    return false;
886                }
887            }
888            else if ( name.charAt( i - 1 ) == DOT )
889            {
890                return false;
891            }
892            else
893            {
894                currentOctet++;
895            }
896        }
897        return  currentOctet == 3;
898    }
899
900}