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 final 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 final String sid = ( session == null ) ? "(null)" : session.getId(); 098 return findSession( sid ); 099 } 100 101 /** 102 * Just looks for a WikiSession; does not create a new one. 103 * This method may return <code>null</code>, <em>and 104 * callers should check for this value</em>. 105 * 106 * @param sessionId the user's HTTP session id 107 * @return the WikiSession, if found 108 */ 109 private Session findSession( final String sessionId ) { 110 Session wikiSession = null; 111 final String sid = ( sessionId == null ) ? "(null)" : sessionId; 112 final Session storedSession = m_sessions.get( sid ); 113 114 // If the weak reference returns a wiki session, return it 115 if( storedSession != null ) { 116 if( log.isDebugEnabled() ) { 117 log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" ); 118 } 119 wikiSession = storedSession; 120 } 121 122 return wikiSession; 123 } 124 125 /** 126 * <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 127 * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently 128 * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 129 * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are 130 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 131 * 132 * @param session the HTTP session 133 * @return the wiki session 134 */ 135 public final Session find( final HttpSession session ) { 136 final Session wikiSession = findSession( session ); 137 final String sid = ( session == null ) ? "(null)" : session.getId(); 138 if( wikiSession == null ) { 139 return createGuestSessionFor( sid ); 140 } 141 142 return wikiSession; 143 } 144 145 /** 146 * <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 147 * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently 148 * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 149 * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are 150 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 151 * 152 * @param sessionId the HTTP session 153 * @return the wiki session 154 */ 155 public final Session find( final String sessionId ) { 156 final Session wikiSession = findSession( sessionId ); 157 if( wikiSession == null ) { 158 return createGuestSessionFor( sessionId ); 159 } 160 161 return wikiSession; 162 } 163 164 /** 165 * Creates a new session and stashes it 166 * 167 * @param sessionId id looked for before creating the guest session 168 * @return a new guest session 169 */ 170 private Session createGuestSessionFor( final String sessionId ) { 171 if( log.isDebugEnabled() ) { 172 log.debug( "Session for session ID=" + sessionId + "... not found. Creating guestSession()" ); 173 } 174 final Session wikiSession = Wiki.session().guest( m_engine ); 175 synchronized( m_sessions ) { 176 m_sessions.put( sessionId, wikiSession ); 177 } 178 return wikiSession; 179 } 180 181 /** 182 * Removes the wiki session associated with the user's HttpRequest from the session cache. 183 * 184 * @param request the user's HTTP request 185 */ 186 public final void remove( final HttpServletRequest request ) { 187 if( request == null ) { 188 throw new IllegalArgumentException( "Request cannot be null." ); 189 } 190 remove( request.getSession() ); 191 } 192 193 /** 194 * Removes the wiki session associated with the user's HttpSession from the session cache. 195 * 196 * @param session the user's HTTP session 197 */ 198 public final void remove( final HttpSession session ) { 199 if( session == null ) { 200 throw new IllegalArgumentException( "Session cannot be null." ); 201 } 202 synchronized( m_sessions ) { 203 m_sessions.remove( session.getId() ); 204 } 205 } 206 207 /** 208 * Returns the current number of active wiki sessions. 209 * @return the number of sessions 210 */ 211 public final int sessions() 212 { 213 return userPrincipals().length; 214 } 215 216 /** 217 * <p>Returns the current wiki users as a sorted array of Principal objects. The principals are those returned by 218 * each WikiSession's {@link Session#getUserPrincipal()}'s method.</p> 219 * <p>To obtain the list of current WikiSessions, we iterate through our session Map and obtain the list of values, 220 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference} objects. Those <code>WeakReference</code>s 221 * whose <code>get()</code> method returns non-<code>null</code> values are valid sessions.</p> 222 * 223 * @return the array of user principals 224 */ 225 public final Principal[] userPrincipals() { 226 final Collection<Principal> principals = new ArrayList<>(); 227 synchronized ( m_sessions ) { 228 for ( final Session session : m_sessions.values()) { 229 principals.add( session.getUserPrincipal() ); 230 } 231 } 232 final Principal[] p = principals.toArray( new Principal[ principals.size() ] ); 233 Arrays.sort( p, m_comparator ); 234 return p; 235 } 236 237 /** 238 * Registers a WikiEventListener with this instance. 239 * 240 * @param listener the event listener 241 * @since 2.4.75 242 */ 243 public final synchronized void addWikiEventListener( final WikiEventListener listener ) { 244 WikiEventManager.addWikiEventListener( this, listener ); 245 } 246 247 /** 248 * Un-registers a WikiEventListener with this instance. 249 * 250 * @param listener the event listener 251 * @since 2.4.75 252 */ 253 public final synchronized void removeWikiEventListener( final WikiEventListener listener ) { 254 WikiEventManager.removeWikiEventListener( this, listener ); 255 } 256 257 /** 258 * Fires a WikiSecurityEvent to all registered listeners. 259 * 260 * @param type the event type 261 * @param principal the user principal associated with this session 262 * @param session the wiki session 263 * @since 2.4.75 264 */ 265 protected final void fireEvent( final int type, final Principal principal, final Session session ) { 266 if( WikiEventManager.isListening( this ) ) { 267 WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, session ) ); 268 } 269 } 270 271 /** 272 * Fires when the web container creates a new HTTP session. 273 * 274 * @param se the HTTP session event 275 */ 276 @Override 277 public void sessionCreated( final HttpSessionEvent se ) { 278 final HttpSession session = se.getSession(); 279 log.debug( "Created session: " + session.getId() + "." ); 280 } 281 282 /** 283 * Removes the user's WikiSession from the internal session cache when the web 284 * container destroys an HTTP session. 285 * @param se the HTTP session event 286 */ 287 @Override 288 public void sessionDestroyed( final HttpSessionEvent se ) { 289 final HttpSession session = se.getSession(); 290 for( final SessionMonitor monitor : c_monitors.values() ) { 291 final Session storedSession = monitor.findSession( session ); 292 monitor.remove( session ); 293 log.debug( "Removed session " + session.getId() + "." ); 294 if( storedSession != null ) { 295 fireEvent( WikiSecurityEvent.SESSION_EXPIRED, storedSession.getLoginPrincipal(), storedSession ); 296 } 297 } 298 } 299 300}