001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.auth; 020 021import org.apache.log4j.Logger; 022import org.apache.wiki.api.core.Engine; 023import org.apache.wiki.api.core.Session; 024import org.apache.wiki.api.spi.Wiki; 025import org.apache.wiki.event.WikiEventListener; 026import org.apache.wiki.event.WikiEventManager; 027import org.apache.wiki.event.WikiSecurityEvent; 028import org.apache.wiki.util.comparators.PrincipalComparator; 029 030import javax.servlet.http.HttpServletRequest; 031import javax.servlet.http.HttpSession; 032import javax.servlet.http.HttpSessionEvent; 033import javax.servlet.http.HttpSessionListener; 034import java.security.Principal; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.Collection; 038import java.util.Map; 039import java.util.WeakHashMap; 040import java.util.concurrent.ConcurrentHashMap; 041 042/** 043 * <p>Manages Sessions for different Engines.</p> 044 * <p>The Sessions are stored both in the remote user HttpSession and in the SessionMonitor for the Engine. 045 * This class must be configured as a session listener in the web.xml for the wiki web application.</p> 046 */ 047public class SessionMonitor implements HttpSessionListener { 048 049 private static final Logger log = Logger.getLogger( SessionMonitor.class ); 050 051 /** Map with Engines as keys, and SessionMonitors as values. */ 052 private static ConcurrentHashMap< Engine, SessionMonitor > c_monitors = new ConcurrentHashMap<>(); 053 054 /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */ 055 private final Map< String, Session > m_sessions = new WeakHashMap<>(); 056 057 private Engine m_engine; 058 059 private final PrincipalComparator m_comparator = new PrincipalComparator(); 060 061 /** 062 * Returns the instance of the SessionMonitor for this wiki. Only one SessionMonitor exists per Engine. 063 * 064 * @param engine the wiki engine 065 * @return the session monitor 066 */ 067 public static SessionMonitor getInstance( final Engine engine ) { 068 if( engine == null ) { 069 throw new IllegalArgumentException( "Engine cannot be null." ); 070 } 071 SessionMonitor monitor = c_monitors.get( engine ); 072 if( monitor == null ) { 073 monitor = new SessionMonitor( engine ); 074 c_monitors.put( engine, monitor ); 075 } 076 077 return monitor; 078 } 079 080 /** Construct the SessionListener */ 081 public SessionMonitor() { 082 } 083 084 private SessionMonitor( final Engine engine ) { 085 m_engine = engine; 086 } 087 088 /** 089 * Just looks for a WikiSession; does not create a new one. 090 * This method may return <code>null</code>, <em>and 091 * callers should check for this value</em>. 092 * 093 * @param session the user's HTTP session 094 * @return the WikiSession, if found 095 */ 096 private Session findSession( final HttpSession session ) { 097 Session wikiSession = null; 098 final String sid = ( session == null ) ? "(null)" : session.getId(); 099 final Session storedSession = m_sessions.get( sid ); 100 101 // If the weak reference returns a wiki session, return it 102 if( storedSession != null ) { 103 if( log.isDebugEnabled() ) { 104 log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" ); 105 } 106 wikiSession = storedSession; 107 } 108 109 return wikiSession; 110 } 111 /** 112 * <p>Looks up the wiki session associated with a user's Http session and adds it to the session cache. This method will return the 113 * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently 114 * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 115 * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are 116 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 117 * 118 * @param session the HTTP session 119 * @return the wiki session 120 */ 121 public final Session find( final HttpSession session ) { 122 Session wikiSession = findSession( session ); 123 final String sid = ( session == null ) ? "(null)" : session.getId(); 124 125 // Otherwise, create a new guest session and stash it. 126 if( wikiSession == null ) { 127 if( log.isDebugEnabled() ) { 128 log.debug( "Looking up WikiSession for session ID=" + sid + "... not found. Creating guestSession()" ); 129 } 130 wikiSession = Wiki.session().guest( m_engine ); 131 synchronized( m_sessions ) { 132 m_sessions.put( sid, wikiSession ); 133 } 134 } 135 136 return wikiSession; 137 } 138 139 /** 140 * Removes the wiki session associated with the user's HttpRequest from the session cache. 141 * 142 * @param request the user's HTTP request 143 */ 144 public final void remove( final HttpServletRequest request ) { 145 if( request == null ) { 146 throw new IllegalArgumentException( "Request cannot be null." ); 147 } 148 remove( request.getSession() ); 149 } 150 151 /** 152 * Removes the wiki session associated with the user's HttpSession from the session cache. 153 * 154 * @param session the user's HTTP session 155 */ 156 public final void remove( final HttpSession session ) { 157 if( session == null ) { 158 throw new IllegalArgumentException( "Session cannot be null." ); 159 } 160 synchronized( m_sessions ) { 161 m_sessions.remove( session.getId() ); 162 } 163 } 164 165 /** 166 * Returns the current number of active wiki sessions. 167 * @return the number of sessions 168 */ 169 public final int sessions() 170 { 171 return userPrincipals().length; 172 } 173 174 /** 175 * <p>Returns the current wiki users as a sorted array of Principal objects. The principals are those returned by 176 * each WikiSession's {@link Session#getUserPrincipal()}'s method.</p> 177 * <p>To obtain the list of current WikiSessions, we iterate through our session Map and obtain the list of values, 178 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference} objects. Those <code>WeakReference</code>s 179 * whose <code>get()</code> method returns non-<code>null</code> values are valid sessions.</p> 180 * 181 * @return the array of user principals 182 */ 183 public final Principal[] userPrincipals() { 184 final Collection<Principal> principals = new ArrayList<>(); 185 synchronized ( m_sessions ) { 186 for ( final Session session : m_sessions.values()) { 187 principals.add( session.getUserPrincipal() ); 188 } 189 } 190 final Principal[] p = principals.toArray( new Principal[ principals.size() ] ); 191 Arrays.sort( p, m_comparator ); 192 return p; 193 } 194 195 /** 196 * Registers a WikiEventListener with this instance. 197 * 198 * @param listener the event listener 199 * @since 2.4.75 200 */ 201 public final synchronized void addWikiEventListener( final WikiEventListener listener ) { 202 WikiEventManager.addWikiEventListener( this, listener ); 203 } 204 205 /** 206 * Un-registers a WikiEventListener with this instance. 207 * 208 * @param listener the event listener 209 * @since 2.4.75 210 */ 211 public final synchronized void removeWikiEventListener( final WikiEventListener listener ) { 212 WikiEventManager.removeWikiEventListener( this, listener ); 213 } 214 215 /** 216 * Fires a WikiSecurityEvent to all registered listeners. 217 * 218 * @param type the event type 219 * @param principal the user principal associated with this session 220 * @param session the wiki session 221 * @since 2.4.75 222 */ 223 protected final void fireEvent( final int type, final Principal principal, final Session session ) { 224 if( WikiEventManager.isListening( this ) ) { 225 WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, session ) ); 226 } 227 } 228 229 /** 230 * Fires when the web container creates a new HTTP session. 231 * 232 * @param se the HTTP session event 233 */ 234 @Override 235 public void sessionCreated( final HttpSessionEvent se ) { 236 final HttpSession session = se.getSession(); 237 log.debug( "Created session: " + session.getId() + "." ); 238 } 239 240 /** 241 * Removes the user's WikiSession from the internal session cache when the web 242 * container destroys an HTTP session. 243 * @param se the HTTP session event 244 */ 245 @Override 246 public void sessionDestroyed( final HttpSessionEvent se ) { 247 final HttpSession session = se.getSession(); 248 for( final SessionMonitor monitor : c_monitors.values() ) { 249 final Session storedSession = monitor.findSession( session ); 250 monitor.remove( session ); 251 log.debug( "Removed session " + session.getId() + "." ); 252 if( storedSession != null ) { 253 fireEvent( WikiSecurityEvent.SESSION_EXPIRED, storedSession.getLoginPrincipal(), storedSession ); 254 } 255 } 256 } 257 258}