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