Remove use of X-Guacamole-Error-Message header, return null rather than throwing...
[guacamole.git] / src / main / java / net / sourceforge / guacamole / net / basic / BasicGuacamoleTunnelServlet.java
index 2055e87..a04f5e7 100644 (file)
@@ -18,88 +18,207 @@ package net.sourceforge.guacamole.net.basic;
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+import java.io.IOException;
+import java.util.Collection;
 import java.util.Map;
-import net.sourceforge.guacamole.net.auth.AuthenticationProvider;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import net.sourceforge.guacamole.GuacamoleException;
 import net.sourceforge.guacamole.net.InetGuacamoleSocket;
 import net.sourceforge.guacamole.protocol.GuacamoleConfiguration;
 import net.sourceforge.guacamole.properties.GuacamoleProperties;
 import net.sourceforge.guacamole.net.GuacamoleSocket;
-import net.sourceforge.guacamole.servlet.GuacamoleSession;
 import net.sourceforge.guacamole.net.GuacamoleTunnel;
-import net.sourceforge.guacamole.net.basic.properties.BasicGuacamoleProperties;
+import net.sourceforge.guacamole.net.auth.Credentials;
+import net.sourceforge.guacamole.net.basic.event.SessionListenerCollection;
+import net.sourceforge.guacamole.net.event.TunnelCloseEvent;
+import net.sourceforge.guacamole.net.event.TunnelConnectEvent;
+import net.sourceforge.guacamole.net.event.listener.TunnelCloseListener;
+import net.sourceforge.guacamole.net.event.listener.TunnelConnectListener;
 import net.sourceforge.guacamole.protocol.ConfiguredGuacamoleSocket;
-import net.sourceforge.guacamole.servlet.GuacamoleTunnelServlet;
+import net.sourceforge.guacamole.servlet.GuacamoleHTTPTunnelServlet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class BasicGuacamoleTunnelServlet extends GuacamoleTunnelServlet {
+/**
+ * Connects users to a tunnel associated with the authorized configuration
+ * having the given ID.
+ * 
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleTunnelServlet extends AuthenticatingHttpServlet {
 
     private Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
-    
-    private AuthenticationProvider authProvider;
 
     @Override
-    public void init() throws ServletException {
+    protected void authenticatedService(
+            Map<String, GuacamoleConfiguration> configs,
+            HttpServletRequest request, HttpServletResponse response)
+    throws IOException, ServletException {
+        
+        // If authenticated, respond as tunnel
+        tunnelServlet.service(request, response);
+        
+    }
 
-        // Get auth provider instance
-        try {
-            authProvider = GuacamoleProperties.getProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
-        }
-        catch (GuacamoleException e) {
-            logger.error("Error getting authentication provider from properties.", e);
-            throw new ServletException(e);
+    /**
+     * Notifies all listeners in the given collection that a tunnel has been
+     * connected.
+     * 
+     * @param listeners A collection of all listeners that should be notified.
+     * @param credentials The credentials associated with the authentication
+     *                    request that connected the tunnel.
+     * @return true if all listeners are allowing the tunnel to connect,
+     *         or if there are no listeners, and false if any listener is
+     *         canceling the connection. Note that once one listener cancels,
+     *         no other listeners will run.
+     * @throws GuacamoleException If any listener throws an error while being
+     *                            notified. Note that if any listener throws an
+     *                            error, the connect is canceled, and no other
+     *                            listeners will run.
+     */
+    private boolean notifyConnect(Collection listeners,
+            Credentials credentials, GuacamoleTunnel tunnel)
+            throws GuacamoleException {
+        
+        // Build event for auth success
+        TunnelConnectEvent event = new TunnelConnectEvent(credentials, tunnel);
+        
+        // Notify all listeners
+        for (Object listener : listeners) {
+            if (listener instanceof TunnelConnectListener) {
+
+                // Cancel immediately if hook returns false
+                if (!((TunnelConnectListener) listener).tunnelConnected(event))
+                    return false;
+                
+            }
         }
 
+        return true;
+        
     }
 
-    @Override
-    protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
-
-        HttpSession httpSession = request.getSession(true);
-
-        // Get ID of connection
-        String id = request.getParameter("id");
+    /**
+     * Notifies all listeners in the given collection that a tunnel has been
+     * closed.
+     * 
+     * @param listeners A collection of all listeners that should be notified.
+     * @param credentials The credentials associated with the authentication
+     *                    request that closed the tunnel.
+     * @return true if all listeners are allowing the tunnel to close,
+     *         or if there are no listeners, and false if any listener is
+     *         canceling the close. Note that once one listener cancels,
+     *         no other listeners will run.
+     * @throws GuacamoleException If any listener throws an error while being
+     *                            notified. Note that if any listener throws an
+     *                            error, the close is canceled, and no other
+     *                            listeners will run.
+     */
+    private boolean notifyClose(Collection listeners,
+            Credentials credentials, GuacamoleTunnel tunnel)
+            throws GuacamoleException {
         
-        // Get authorized configs
-        Map<String, GuacamoleConfiguration> configs = (Map<String, GuacamoleConfiguration>) 
-                httpSession.getAttribute("GUAC_CONFIGS");
-
-        // If no configs in session, not authorized
-        if (configs == null)
-            throw new GuacamoleException("No authorized configurations.");
-
-        // Get authorized config
-        GuacamoleConfiguration config = configs.get(id);
-        if (config == null) {
-            logger.error("Error retrieving authorized configuration id={}.", id);
-            throw new GuacamoleException("Unknown configuration ID.");
-        }
+        // Build event for auth success
+        TunnelCloseEvent event = new TunnelCloseEvent(credentials, tunnel);
         
-        logger.info("Successful connection from {} to \"{}\".", request.getRemoteAddr(), id);
-
-        // Configure and connect socket
-        String hostname = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME);
-        int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT);
-
-        GuacamoleSocket socket = new ConfiguredGuacamoleSocket(
-                new InetGuacamoleSocket(hostname, port),
-                config
-        );
+        // Notify all listeners
+        for (Object listener : listeners) {
+            if (listener instanceof TunnelCloseListener) {
+
+                // Cancel immediately if hook returns false
+                if (!((TunnelCloseListener) listener).tunnelClosed(event))
+                    return false;
+                
+            }
+        }
 
-        // Associate socket with tunnel
-        GuacamoleTunnel tunnel = new GuacamoleTunnel(socket);
+        return true;
+        
+    }
 
-        // Attach tunnel to session
-        GuacamoleSession session = new GuacamoleSession(httpSession);
-        session.attachTunnel(tunnel);
+    /**
+     * Wrapped GuacamoleHTTPTunnelServlet which will handle all authenticated
+     * requests.
+     */
+    private GuacamoleHTTPTunnelServlet tunnelServlet = new GuacamoleHTTPTunnelServlet() {
+
+        @Override
+        protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
+
+            HttpSession httpSession = request.getSession(true);
+            
+            // Get listeners
+            final SessionListenerCollection listeners;
+            try {
+                listeners = new SessionListenerCollection(httpSession);
+            }
+            catch (GuacamoleException e) {
+                logger.error("Failed to retrieve listeners. Authentication canceled.", e);
+                throw e;
+            }
+
+            // Get ID of connection
+            String id = request.getParameter("id");
+            
+            // Get credentials
+            final Credentials credentials = getCredentials(httpSession);
+            
+            // Get authorized configs
+            Map<String, GuacamoleConfiguration> configs = getConfigurations(httpSession);
+
+            // If no configs/credentials in session, not authorized
+            if (credentials == null || configs == null)
+                throw new GuacamoleException("Cannot connect - user not logged in.");
+
+            // Get authorized config
+            GuacamoleConfiguration config = configs.get(id);
+            if (config == null) {
+                logger.error("Configuration id={} not found.", id);
+                return null;
+            }
+            
+            logger.info("Successful connection from {} to \"{}\".", request.getRemoteAddr(), id);
+
+            // Configure and connect socket
+            String hostname = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME);
+            int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT);
+
+            GuacamoleSocket socket = new ConfiguredGuacamoleSocket(
+                    new InetGuacamoleSocket(hostname, port),
+                    config
+            );
+
+            // Associate socket with tunnel
+            GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) {
+
+                @Override
+                public void close() throws GuacamoleException {
+
+                    // Only close if not canceled
+                    if (!notifyClose(listeners, credentials, this))
+                        throw new GuacamoleException("Tunnel close canceled by listener.");
+                    
+                    // Close if no exception due to listener
+                    super.close();
+                    
+                }
+                
+            };
+
+            // Notify listeners about connection
+            if (!notifyConnect(listeners, credentials, tunnel)) {
+                logger.info("Connection canceled by listener.");
+                return null;
+            }
+            
+            return tunnel;
 
-        return tunnel;
+        }
 
-    }
+    };
 
 }