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; 042import java.util.stream.Collectors; 043 044/** 045 * <p>Manages Sessions for different Engines.</p> 046 * <p>The Sessions are stored both in the remote user HttpSession and in the SessionMonitor for the Engine. 047 * This class must be configured as a session listener in the web.xml for the wiki web application.</p> 048 */ 049public class SessionMonitor implements HttpSessionListener { 050 051 private static final Logger LOG = LogManager.getLogger( SessionMonitor.class ); 052 053 /** Map with Engines as keys, and SessionMonitors as values. */ 054 private static final ConcurrentHashMap< Engine, SessionMonitor > c_monitors = new ConcurrentHashMap<>(); 055 056 /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */ 057 private final Map< String, Session > m_sessions = new WeakHashMap<>(); 058 059 private Engine m_engine; 060 061 private final PrincipalComparator m_comparator = new PrincipalComparator(); 062 063 /** 064 * Returns the instance of the SessionMonitor for this wiki. Only one SessionMonitor exists per Engine. 065 * 066 * @param engine the wiki engine 067 * @return the session monitor 068 */ 069 public static SessionMonitor getInstance( final Engine engine ) { 070 if( engine == null ) { 071 throw new IllegalArgumentException( "Engine cannot be null." ); 072 } 073 SessionMonitor monitor = c_monitors.get( engine ); 074 if( monitor == null ) { 075 monitor = new SessionMonitor( engine ); 076 c_monitors.put( engine, monitor ); 077 } 078 079 return monitor; 080 } 081 082 /** Construct the SessionListener */ 083 public SessionMonitor() { 084 } 085 086 private SessionMonitor( final Engine engine ) { 087 m_engine = engine; 088 } 089 090 /** 091 * Just looks for a WikiSession; does not create a new one. 092 * This method may return <code>null</code>, <em>and 093 * callers should check for this value</em>. 094 * 095 * @param session the user's HTTP session 096 * @return the WikiSession, if found 097 */ 098 private Session findSession( final HttpSession session ) { 099 final String sid = ( session == null ) ? "(null)" : session.getId(); 100 return findSession( sid ); 101 } 102 103 /** 104 * Just looks for a WikiSession; does not create a new one. 105 * This method may return <code>null</code>, <em>and 106 * callers should check for this value</em>. 107 * 108 * @param sessionId the user's HTTP session id 109 * @return the WikiSession, if found 110 */ 111 private Session findSession( final String sessionId ) { 112 Session wikiSession = null; 113 final String sid = ( sessionId == null ) ? "(null)" : sessionId; 114 final Session storedSession = m_sessions.get( sid ); 115 116 // If the weak reference returns a wiki session, return it 117 if( storedSession != null ) { 118 LOG.debug( "Looking up WikiSession for session ID={}... found it", sid ); 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 LOG.debug( "Session for session ID={}... not found. Creating guestSession()", sessionId ); 172 final Session wikiSession = Wiki.session().guest( m_engine ); 173 synchronized( m_sessions ) { 174 m_sessions.put( sessionId, wikiSession ); 175 } 176 return wikiSession; 177 } 178 179 /** 180 * Removes the wiki session associated with the user's HttpRequest from the session cache. 181 * 182 * @param request the user's HTTP request 183 */ 184 public final void remove( final HttpServletRequest request ) { 185 if( request == null ) { 186 throw new IllegalArgumentException( "Request cannot be null." ); 187 } 188 remove( request.getSession() ); 189 } 190 191 /** 192 * Removes the wiki session associated with the user's HttpSession from the session cache. 193 * 194 * @param session the user's HTTP session 195 */ 196 public final void remove( final HttpSession session ) { 197 if( session == null ) { 198 throw new IllegalArgumentException( "Session cannot be null." ); 199 } 200 synchronized( m_sessions ) { 201 m_sessions.remove( session.getId() ); 202 } 203 } 204 205 /** 206 * Returns the current number of active wiki sessions. 207 * @return the number of sessions 208 */ 209 public final int sessions() 210 { 211 return userPrincipals().length; 212 } 213 214 /** 215 * <p>Returns the current wiki users as a sorted array of Principal objects. The principals are those returned by 216 * each WikiSession's {@link Session#getUserPrincipal()}'s method.</p> 217 * <p>To obtain the list of current WikiSessions, we iterate through our session Map and obtain the list of values, 218 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference} objects. Those <code>WeakReference</code>s 219 * whose <code>get()</code> method returns non-<code>null</code> values are valid sessions.</p> 220 * 221 * @return the array of user principals 222 */ 223 public final Principal[] userPrincipals() { 224 final Collection<Principal> principals; 225 synchronized ( m_sessions ) { 226 principals = m_sessions.values().stream().map(Session::getUserPrincipal).collect(Collectors.toList()); 227 } 228 final Principal[] p = principals.toArray( new Principal[0] ); 229 Arrays.sort( p, m_comparator ); 230 return p; 231 } 232 233 /** 234 * Registers a WikiEventListener with this instance. 235 * 236 * @param listener the event listener 237 * @since 2.4.75 238 */ 239 public final synchronized void addWikiEventListener( final WikiEventListener listener ) { 240 WikiEventManager.addWikiEventListener( this, listener ); 241 } 242 243 /** 244 * Un-registers a WikiEventListener with this instance. 245 * 246 * @param listener the event listener 247 * @since 2.4.75 248 */ 249 public final synchronized void removeWikiEventListener( final WikiEventListener listener ) { 250 WikiEventManager.removeWikiEventListener( this, listener ); 251 } 252 253 /** 254 * Fires a WikiSecurityEvent to all registered listeners. 255 * 256 * @param type the event type 257 * @param principal the user principal associated with this session 258 * @param session the wiki session 259 * @since 2.4.75 260 */ 261 protected final void fireEvent( final int type, final Principal principal, final Session session ) { 262 if( WikiEventManager.isListening( this ) ) { 263 WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, session ) ); 264 } 265 } 266 267 /** 268 * Fires when the web container creates a new HTTP session. 269 * 270 * @param se the HTTP session event 271 */ 272 @Override 273 public void sessionCreated( final HttpSessionEvent se ) { 274 final HttpSession session = se.getSession(); 275 LOG.debug( "Created session: " + session.getId() + "." ); 276 } 277 278 /** 279 * Removes the user's WikiSession from the internal session cache when the web 280 * container destroys an HTTP session. 281 * @param se the HTTP session event 282 */ 283 @Override 284 public void sessionDestroyed( final HttpSessionEvent se ) { 285 final HttpSession session = se.getSession(); 286 for( final SessionMonitor monitor : c_monitors.values() ) { 287 final Session storedSession = monitor.findSession( session ); 288 monitor.remove( session ); 289 LOG.debug( "Removed session " + session.getId() + "." ); 290 if( storedSession != null ) { 291 fireEvent( WikiSecurityEvent.SESSION_EXPIRED, storedSession.getLoginPrincipal(), storedSession ); 292 } 293 } 294 } 295 296}