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