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 }