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 org.apache.commons.lang3.StringUtils;
022import org.apache.log4j.Logger;
023import org.apache.wiki.auth.AuthenticationManager;
024import org.apache.wiki.auth.GroupPrincipal;
025import org.apache.wiki.auth.NoSuchPrincipalException;
026import org.apache.wiki.auth.SessionMonitor;
027import org.apache.wiki.auth.UserManager;
028import org.apache.wiki.auth.WikiPrincipal;
029import org.apache.wiki.auth.authorize.Group;
030import org.apache.wiki.auth.authorize.GroupManager;
031import org.apache.wiki.auth.authorize.Role;
032import org.apache.wiki.auth.user.UserDatabase;
033import org.apache.wiki.auth.user.UserProfile;
034import org.apache.wiki.event.WikiEvent;
035import org.apache.wiki.event.WikiEventListener;
036import org.apache.wiki.event.WikiSecurityEvent;
037
038import javax.security.auth.Subject;
039import javax.servlet.http.HttpServletRequest;
040import javax.servlet.http.HttpSession;
041import java.security.AccessControlException;
042import java.security.Principal;
043import java.security.PrivilegedAction;
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.HashMap;
047import java.util.HashSet;
048import java.util.LinkedHashSet;
049import java.util.Locale;
050import java.util.Map;
051import java.util.Set;
052
053/**
054 * <p>Represents a long-running wiki session, with an associated user Principal,
055 * user Subject, and authentication status. This class is initialized with
056 * minimal, default-deny values: authentication is set to <code>false</code>,
057 * and the user principal is set to <code>null</code>.</p>
058 * <p>The WikiSession class allows callers to:</p>
059 * <ul>
060 *   <li>Obtain the authentication status of the user via
061 *     {@link #isAnonymous()} and {@link #isAuthenticated()}</li>
062 *   <li>Query the session for Principals representing the
063 *     user's identity via {@link #getLoginPrincipal()},
064 *     {@link #getUserPrincipal()} and {@link #getPrincipals()}</li>
065 *   <li>Store, retrieve and clear UI messages via
066 *     {@link #addMessage(String)}, {@link #getMessages(String)}
067 *     and {@link #clearMessages(String)}</li>
068 * </ul>
069 * <p>To keep track of the Principals each user posseses, each WikiSession
070 * stores a JAAS Subject. Various login processes add or remove Principals
071 * when users authenticate or log out.</p>
072 * <p>WikiSession implements the {@link org.apache.wiki.event.WikiEventListener}
073 * interface and listens for group add/change/delete events fired by
074 * event sources the WikiSession is registered with. Normally,
075 * {@link org.apache.wiki.auth.AuthenticationManager} registers each WikiSession
076 * with the {@link org.apache.wiki.auth.authorize.GroupManager}
077 * so it can catch group events. Thus, when a user is added to a
078 * {@link org.apache.wiki.auth.authorize.Group}, a corresponding
079 * {@link org.apache.wiki.auth.GroupPrincipal} is injected into
080 * the Subject's Principal set. Likewise, when the user is removed from
081 * the Group or the Group is deleted, the GroupPrincipal is removed
082 * from the Subject. The effect that this strategy produces is extremely
083 * beneficial: when someone adds a user to a wiki group, that user
084 * <em>immediately</em> gains the privileges associated with that
085 * group; he or she does not need to re-authenticate.
086 * </p>
087 * <p>In addition to methods for examining individual <code>WikiSession</code>
088 * objects, this class also contains a number of static methods for
089 * managing WikiSessions for an entire wiki. These methods allow callers
090 * to find, query and remove WikiSession objects, and
091 * to obtain a list of the current wiki session users.</p>
092 * <p>WikiSession encloses a protected static class, {@link SessionMonitor},
093 * to keep track of WikiSessions registered with each wiki.</p>
094 */
095public final class WikiSession implements WikiEventListener
096{
097
098    /** An anonymous user's session status. */
099    public static final String  ANONYMOUS             = "anonymous";
100
101    /** An asserted user's session status. */
102    public static final String  ASSERTED              = "asserted";
103
104    /** An authenticated user's session status. */
105    public static final String  AUTHENTICATED         = "authenticated";
106
107    private static final int    ONE                   = 48;
108
109    private static final int    NINE                  = 57;
110
111    private static final int    DOT                   = 46;
112
113    private static final Logger log                   = Logger.getLogger( WikiSession.class );
114
115    private static final String ALL                   = "*";
116
117    private static ThreadLocal<WikiSession> c_guestSession = new ThreadLocal<>();
118
119    private final Subject       m_subject             = new Subject();
120
121    private final Map<String,Set<String>> m_messages  = new HashMap<>();
122
123    /** The WikiEngine that created this session. */
124    private WikiEngine          m_engine              = null;
125
126    private String              m_status              = ANONYMOUS;
127
128    private Principal           m_userPrincipal       = WikiPrincipal.GUEST;
129
130    private Principal           m_loginPrincipal      = WikiPrincipal.GUEST;
131
132    private Locale              m_cachedLocale        = Locale.getDefault();
133
134    /**
135     * Returns <code>true</code> if one of this WikiSession's user Principals
136     * can be shown to belong to a particular wiki group. If the user is
137     * not authenticated, this method will always return <code>false</code>.
138     * @param group the group to test
139     * @return the result
140     */
141    protected boolean isInGroup( Group group )
142    {
143        for ( Principal principal : getPrincipals() )
144        {
145          if ( isAuthenticated() && group.isMember( principal ) )
146          {
147              return true;
148          }
149        }
150        return false;
151    }
152
153    /**
154     * Private constructor to prevent WikiSession from being instantiated
155     * directly.
156     */
157    private WikiSession()
158    {
159    }
160
161    /**
162     * Returns <code>true</code> if the user is considered asserted via
163     * a session cookie; that is, the Subject contains the Principal
164     * Role.ASSERTED.
165     * @return Returns <code>true</code> if the user is asserted
166     */
167    public boolean isAsserted()
168    {
169        return m_subject.getPrincipals().contains( Role.ASSERTED );
170    }
171
172    /**
173     * Returns the authentication status of the user's session. The user is
174     * considered authenticated if the Subject contains the Principal
175     * Role.AUTHENTICATED. If this method determines that an earlier
176     * LoginModule did not inject Role.AUTHENTICATED, it will inject one
177     * if the user is not anonymous <em>and</em> not asserted.
178     * @return Returns <code>true</code> if the user is authenticated
179     */
180    public boolean isAuthenticated()
181    {
182        // If Role.AUTHENTICATED is in principals set, always return true.
183        if ( m_subject.getPrincipals().contains( Role.AUTHENTICATED ) )
184        {
185            return true;
186        }
187
188        // With non-JSPWiki LoginModules, the role may not be there, so
189        // we need to add it if the user really is authenticated.
190        if ( !isAnonymous() && !isAsserted() )
191        {
192            // Inject AUTHENTICATED role
193            m_subject.getPrincipals().add( Role.AUTHENTICATED );
194            return true;
195        }
196
197        return false;
198    }
199
200    /**
201     * <p>Determines whether the current session is anonymous. This will be
202     * true if any of these conditions are true:</p>
203     * <ul>
204     *   <li>The session's Principal set contains
205     *       {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}</li>
206     *   <li>The session's Principal set contains
207     *       {@link org.apache.wiki.auth.WikiPrincipal#GUEST}</li>
208     *   <li>The Principal returned by {@link #getUserPrincipal()} evaluates
209     *       to an IP address.</li>
210     * </ul>
211     * <p>The criteria above are listed in the order in which they are
212     * evaluated.</p>
213     * @return whether the current user's identity is equivalent to an IP
214     * address
215     */
216    public boolean isAnonymous()
217    {
218        Set<Principal> principals = m_subject.getPrincipals();
219        return principals.contains( Role.ANONYMOUS ) ||
220                 principals.contains( WikiPrincipal.GUEST ) ||
221                 isIPV4Address( getUserPrincipal().getName() );
222    }
223
224    /**
225     * <p> Returns the Principal used to log in to an authenticated session. The
226     * login principal is determined by examining the Subject's Principal set
227     * for PrincipalWrappers or WikiPrincipals with type designator
228     * <code>LOGIN_NAME</code>; the first one found is the login principal.
229     * If one is not found, this method returns the first principal that isn't
230     * of type Role or GroupPrincipal. If neither of these conditions hold, this method returns
231     * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
232     * @return the login Principal. If it is a PrincipalWrapper containing an
233     * externally-provided Principal, the object returned is the Principal, not
234     * the wrapper around it.
235     */
236    public Principal getLoginPrincipal()
237    {
238        return m_loginPrincipal;
239    }
240
241    /**
242     * <p>Returns the primary user Principal associated with this session. The
243     * primary user principal is determined as follows:</p> <ol> <li>If the
244     * Subject's Principal set contains WikiPrincipals, the first WikiPrincipal
245     * with type designator <code>WIKI_NAME</code> or (alternatively)
246     * <code>FULL_NAME</code> is the primary Principal.</li>
247     *   <li>For all other cases, the first Principal in the Subject's principal
248     *       collection that that isn't of type Role or GroupPrincipal is the primary.</li>
249     * </ol>
250     * If no primary user Principal is found, this method returns
251     * {@link org.apache.wiki.auth.WikiPrincipal#GUEST}.
252     * @return the primary user Principal
253     */
254    public Principal getUserPrincipal()
255    {
256        return m_userPrincipal;
257    }
258
259    /**
260     *  Returns a cached Locale object for this user.  It's better to use
261     *  WikiContext's corresponding getBundle() method, since that will actually
262     *  react if the user changes the locale in the middle, but if that's not
263     *  available (or, for some reason, you need the speed), this method can
264     *  also be used.  The Locale expires when the WikiSession expires, and
265     *  currently there is no way to reset the Locale.
266     *
267     *  @return A cached Locale object
268     *  @since 2.5.96
269     */
270    public Locale getLocale()
271    {
272        return m_cachedLocale;
273    }
274
275    /**
276     * Adds a message to the generic list of messages associated with the
277     * session. These messages retain their order of insertion and remain until
278     * the {@link #clearMessages()} method is called.
279     * @param message the message to add; if <code>null</code> it is ignored.
280     */
281    public void addMessage(String message)
282    {
283        addMessage( ALL, message );
284    }
285
286
287    /**
288     * Adds a message to the specific set of messages associated with the
289     * session. These messages retain their order of insertion and remain until
290     * the {@link #clearMessages()} method is called.
291     * @param topic the topic to associate the message to;
292     * @param message the message to add
293     */
294    public void addMessage(String topic, String message)
295    {
296        if ( topic == null )
297        {
298            throw new IllegalArgumentException( "addMessage: topic cannot be null." );
299        }
300        Set<String> messages = m_messages.get( topic );
301        if (messages == null )
302        {
303            messages = new LinkedHashSet<>();
304            m_messages.put( topic, messages );
305        }
306        messages.add( StringUtils.defaultString( message ) );
307    }
308
309    /**
310     * Clears all messages associated with this session.
311     */
312    public void clearMessages()
313    {
314        m_messages.clear();
315    }
316
317    /**
318     * Clears all messages associated with a session topic.
319     * @param topic the topic whose messages should be cleared.
320     */
321    public void clearMessages( String topic )
322    {
323        Set<String> messages = m_messages.get( topic );
324        if ( messages != null )
325        {
326            m_messages.clear();
327        }
328    }
329
330    /**
331     * Returns all generic messages associated with this session.
332     * The messages stored with the session persist throughout the
333     * session unless they have been reset with {@link #clearMessages()}.
334     * @return the current messages.
335     */
336    public String[] getMessages()
337    {
338        return getMessages( ALL );
339    }
340
341    /**
342     * Returns all messages associated with a session topic.
343     * The messages stored with the session persist throughout the
344     * session unless they have been reset with {@link #clearMessages(String)}.
345     * @return the current messages.
346     * @param topic The topic
347     */
348    public String[] getMessages( String topic )
349    {
350        Set<String> messages = m_messages.get( topic );
351        if ( messages == null || messages.size() == 0 )
352        {
353            return new String[0];
354        }
355        return messages.toArray( new String[messages.size()] );
356    }
357
358    /**
359     * Returns all user Principals associated with this session. User principals
360     * are those in the Subject's principal collection that aren't of type Role or
361     * of type GroupPrincipal. This is a defensive copy.
362     * @return Returns the user principal
363     * @see org.apache.wiki.auth.AuthenticationManager#isUserPrincipal(Principal)
364     */
365    public Principal[] getPrincipals()
366    {
367        ArrayList<Principal> principals = new ArrayList<>();
368
369        // Take the first non Role as the main Principal
370        for( Principal principal : m_subject.getPrincipals() )
371        {
372            if ( AuthenticationManager.isUserPrincipal( principal ) )
373            {
374                principals.add( principal );
375            }
376        }
377
378        return principals.toArray( new Principal[principals.size()] );
379    }
380
381    /**
382     * Returns an array of Principal objects that represents the groups and
383     * roles that the user associated with a WikiSession possesses. The array is
384     * built by iterating through the Subject's Principal set and extracting all
385     * Role and GroupPrincipal objects into a list. The list is returned as an
386     * array sorted in the natural order implied by each Principal's
387     * <code>getName</code> method. Note that this method does <em>not</em>
388     * consult the external Authorizer or GroupManager; it relies on the
389     * Principals that have been injected into the user's Subject at login time,
390     * or after group creation/modification/deletion.
391     * @return an array of Principal objects corresponding to the roles the
392     *         Subject possesses
393     */
394    public Principal[] getRoles()
395    {
396        Set<Principal> roles = new HashSet<>();
397
398        // Add all of the Roles possessed by the Subject directly
399        roles.addAll( m_subject.getPrincipals( Role.class ) );
400
401        // Add all of the GroupPrincipals possessed by the Subject directly
402        roles.addAll( m_subject.getPrincipals( GroupPrincipal.class ) );
403
404        // Return a defensive copy
405        Principal[] roleArray = roles.toArray( new Principal[roles.size()] );
406        Arrays.sort( roleArray, WikiPrincipal.COMPARATOR );
407        return roleArray;
408    }
409
410    /**
411     * Removes the wiki session associated with the user's HTTP request
412     * from the cache of wiki sessions, typically as part of a logout
413     * process.
414     * @param engine the wiki engine
415     * @param request the users's HTTP request
416     */
417    public static void removeWikiSession( WikiEngine engine, HttpServletRequest request )
418    {
419        if ( engine == null || request == null )
420        {
421            throw new IllegalArgumentException( "Request or engine cannot be null." );
422        }
423        SessionMonitor monitor = SessionMonitor.getInstance( engine );
424        monitor.remove( request.getSession() );
425    }
426
427    /**
428     * Returns <code>true</code> if the WikiSession's Subject
429     * possess a supplied Principal. This method eliminates the need
430     * to externally request and inspect the JAAS subject.
431     * @param principal the Principal to test
432     * @return the result
433     */
434    public boolean hasPrincipal( Principal principal )
435    {
436        return m_subject.getPrincipals().contains( principal );
437
438    }
439
440    /**
441     * Listens for WikiEvents generated by source objects such as the
442     * GroupManager. This method adds Principals to the private Subject managed
443     * by the WikiSession.
444     * @see org.apache.wiki.event.WikiEventListener#actionPerformed(org.apache.wiki.event.WikiEvent)
445     */
446    @Override
447    public void actionPerformed( WikiEvent event )
448    {
449        if ( event instanceof WikiSecurityEvent )
450        {
451            WikiSecurityEvent e = (WikiSecurityEvent)event;
452            if ( e.getTarget() != null )
453            {
454                switch (e.getType() )
455                {
456                    case WikiSecurityEvent.GROUP_ADD:
457                    {
458                        Group group = (Group)e.getTarget();
459                        if ( isInGroup( group ) )
460                        {
461                            m_subject.getPrincipals().add( group.getPrincipal() );
462                        }
463                        break;
464                    }
465                    case WikiSecurityEvent.GROUP_REMOVE:
466                    {
467                        Group group = (Group)e.getTarget();
468                        if ( m_subject.getPrincipals().contains( group.getPrincipal() ) )
469                        {
470                            m_subject.getPrincipals().remove( group.getPrincipal() );
471                        }
472                        break;
473                    }
474                    case WikiSecurityEvent.GROUP_CLEAR_GROUPS:
475                    {
476                        m_subject.getPrincipals().removeAll( m_subject.getPrincipals( GroupPrincipal.class ) );
477                        break;
478                    }
479                    case WikiSecurityEvent.LOGIN_INITIATED:
480                    {
481                        // Do nothing
482                        break;
483                    }
484                    case WikiSecurityEvent.PRINCIPAL_ADD:
485                    {
486                        WikiSession target = (WikiSession)e.getTarget();
487                        if ( this.equals( target ) && m_status.equals(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.equals(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}