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.logging.log4j.LogManager; 022import org.apache.logging.log4j.Logger; 023import org.apache.wiki.api.core.Engine; 024import org.apache.wiki.api.core.Session; 025import org.apache.wiki.api.spi.Wiki; 026import org.apache.wiki.event.WikiEventListener; 027import org.apache.wiki.event.WikiEventManager; 028import org.apache.wiki.event.WikiSecurityEvent; 029import org.apache.wiki.util.comparators.PrincipalComparator; 030 031import javax.servlet.http.HttpServletRequest; 032import javax.servlet.http.HttpSession; 033import javax.servlet.http.HttpSessionEvent; 034import javax.servlet.http.HttpSessionListener; 035import java.security.Principal; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collection; 039import java.util.Map; 040import java.util.WeakHashMap; 041import java.util.concurrent.ConcurrentHashMap; 042 043/** 044 * <p>Manages Sessions for different Engines.</p> 045 * <p>The Sessions are stored both in the remote user HttpSession and in the SessionMonitor for the Engine. 046 * This class must be configured as a session listener in the web.xml for the wiki web application.</p> 047 */ 048public class SessionMonitor implements HttpSessionListener { 049 050 private static final Logger log = LogManager.getLogger( SessionMonitor.class ); 051 052 /** Map with Engines as keys, and SessionMonitors as values. */ 053 private static final ConcurrentHashMap< Engine, SessionMonitor > c_monitors = new ConcurrentHashMap<>(); 054 055 /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */ 056 private final Map< String, Session > m_sessions = new WeakHashMap<>(); 057 058 private Engine m_engine; 059 060 private final PrincipalComparator m_comparator = new PrincipalComparator(); 061 062 /** 063 * Returns the instance of the SessionMonitor for this wiki. Only one SessionMonitor exists per Engine. 064 * 065 * @param engine the wiki engine 066 * @return the session monitor 067 */ 068 public static SessionMonitor getInstance( final Engine engine ) { 069 if( engine == null ) { 070 throw new IllegalArgumentException( "Engine cannot be null." ); 071 } 072 SessionMonitor monitor = c_monitors.get( engine ); 073 if( monitor == null ) { 074 monitor = new SessionMonitor( engine ); 075 c_monitors.put( engine, monitor ); 076 } 077 078 return monitor; 079 } 080 081 /** Construct the SessionListener */ 082 public SessionMonitor() { 083 } 084 085 private SessionMonitor( final Engine engine ) { 086 m_engine = engine; 087 } 088 089 /** 090 * Just looks for a WikiSession; does not create a new one. 091 * This method may return <code>null</code>, <em>and 092 * callers should check for this value</em>. 093 * 094 * @param session the user's HTTP session 095 * @return the WikiSession, if found 096 */ 097 private Session findSession( final HttpSession session ) { 098 final String sid = ( session == null ) ? "(null)" : session.getId(); 099 return findSession( sid ); 100 } 101 102 /** 103 * Just looks for a WikiSession; does not create a new one. 104 * This method may return <code>null</code>, <em>and 105 * callers should check for this value</em>. 106 * 107 * @param sessionId the user's HTTP session id 108 * @return the WikiSession, if found 109 */ 110 private Session findSession( final String sessionId ) { 111 Session wikiSession = null; 112 final String sid = ( sessionId == null ) ? "(null)" : sessionId; 113 final Session storedSession = m_sessions.get( sid ); 114 115 // If the weak reference returns a wiki session, return it 116 if( storedSession != null ) { 117 if( log.isDebugEnabled() ) { 118 log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" ); 119 } 120 wikiSession = storedSession; 121 } 122 123 return wikiSession; 124 } 125 126 /** 127 * <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 128 * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently 129 * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 130 * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are 131 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 132 * 133 * @param session the HTTP session 134 * @return the wiki session 135 */ 136 public final Session find( final HttpSession session ) { 137 final Session wikiSession = findSession( session ); 138 final String sid = ( session == null ) ? "(null)" : session.getId(); 139 if( wikiSession == null ) { 140 return createGuestSessionFor( sid ); 141 } 142 143 return wikiSession; 144 } 145 146 /** 147 * <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 148 * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently 149 * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 150 * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are 151 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 152 * 153 * @param sessionId the HTTP session 154 * @return the wiki session 155 */ 156 public final Session find( final String sessionId ) { 157 final Session wikiSession = findSession( sessionId ); 158 if( wikiSession == null ) { 159 return createGuestSessionFor( sessionId ); 160 } 161 162 return wikiSession; 163 } 164 165 /** 166 * Creates a new session and stashes it 167 * 168 * @param sessionId id looked for before creating the guest session 169 * @return a new guest session 170 */ 171 private Session createGuestSessionFor( final String sessionId ) { 172 if( log.isDebugEnabled() ) { 173 log.debug( "Session for session ID=" + sessionId + "... not found. Creating guestSession()" ); 174 } 175 final Session wikiSession = Wiki.session().guest( m_engine ); 176 synchronized( m_sessions ) { 177 m_sessions.put( sessionId, wikiSession ); 178 } 179 return wikiSession; 180 } 181 182 /** 183 * Removes the wiki session associated with the user's HttpRequest from the session cache. 184 * 185 * @param request the user's HTTP request 186 */ 187 public final void remove( final HttpServletRequest request ) { 188 if( request == null ) { 189 throw new IllegalArgumentException( "Request cannot be null." ); 190 } 191 remove( request.getSession() ); 192 } 193 194 /** 195 * Removes the wiki session associated with the user's HttpSession from the session cache. 196 * 197 * @param session the user's HTTP session 198 */ 199 public final void remove( final HttpSession session ) { 200 if( session == null ) { 201 throw new IllegalArgumentException( "Session cannot be null." ); 202 } 203 synchronized( m_sessions ) { 204 m_sessions.remove( session.getId() ); 205 } 206 } 207 208 /** 209 * Returns the current number of active wiki sessions. 210 * @return the number of sessions 211 */ 212 public final int sessions() 213 { 214 return userPrincipals().length; 215 } 216 217 /** 218 * <p>Returns the current wiki users as a sorted array of Principal objects. The principals are those returned by 219 * each WikiSession's {@link Session#getUserPrincipal()}'s method.</p> 220 * <p>To obtain the list of current WikiSessions, we iterate through our session Map and obtain the list of values, 221 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference} objects. Those <code>WeakReference</code>s 222 * whose <code>get()</code> method returns non-<code>null</code> values are valid sessions.</p> 223 * 224 * @return the array of user principals 225 */ 226 public final Principal[] userPrincipals() { 227 final Collection<Principal> principals = new ArrayList<>(); 228 synchronized ( m_sessions ) { 229 for ( final Session session : m_sessions.values()) { 230 principals.add( session.getUserPrincipal() ); 231 } 232 } 233 final Principal[] p = principals.toArray( new Principal[0] ); 234 Arrays.sort( p, m_comparator ); 235 return p; 236 } 237 238 /** 239 * Registers a WikiEventListener with this instance. 240 * 241 * @param listener the event listener 242 * @since 2.4.75 243 */ 244 public final synchronized void addWikiEventListener( final WikiEventListener listener ) { 245 WikiEventManager.addWikiEventListener( this, listener ); 246 } 247 248 /** 249 * Un-registers a WikiEventListener with this instance. 250 * 251 * @param listener the event listener 252 * @since 2.4.75 253 */ 254 public final synchronized void removeWikiEventListener( final WikiEventListener listener ) { 255 WikiEventManager.removeWikiEventListener( this, listener ); 256 } 257 258 /** 259 * Fires a WikiSecurityEvent to all registered listeners. 260 * 261 * @param type the event type 262 * @param principal the user principal associated with this session 263 * @param session the wiki session 264 * @since 2.4.75 265 */ 266 protected final void fireEvent( final int type, final Principal principal, final Session session ) { 267 if( WikiEventManager.isListening( this ) ) { 268 WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, session ) ); 269 } 270 } 271 272 /** 273 * Fires when the web container creates a new HTTP session. 274 * 275 * @param se the HTTP session event 276 */ 277 @Override 278 public void sessionCreated( final HttpSessionEvent se ) { 279 final HttpSession session = se.getSession(); 280 log.debug( "Created session: " + session.getId() + "." ); 281 } 282 283 /** 284 * Removes the user's WikiSession from the internal session cache when the web 285 * container destroys an HTTP session. 286 * @param se the HTTP session event 287 */ 288 @Override 289 public void sessionDestroyed( final HttpSessionEvent se ) { 290 final HttpSession session = se.getSession(); 291 for( final SessionMonitor monitor : c_monitors.values() ) { 292 final Session storedSession = monitor.findSession( session ); 293 monitor.remove( session ); 294 log.debug( "Removed session " + session.getId() + "." ); 295 if( storedSession != null ) { 296 fireEvent( WikiSecurityEvent.SESSION_EXPIRED, storedSession.getLoginPrincipal(), storedSession ); 297 } 298 } 299 } 300 301}