* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import net.sourceforge.guacamole.net.auth.AuthenticationProvider;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
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.GuacamoleSecurityException;
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.auth.UserConfiguration;
-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
- UserConfiguration userConfig = (UserConfiguration)
- httpSession.getAttribute("GUAC_USER_CONFIG");
-
- // If no configs in session, not authorized
- if (userConfig == null)
- throw new GuacamoleException("No authorized configurations.");
-
- // Get authorized config
- GuacamoleConfiguration config = userConfig.getConfiguration(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 GuacamoleSecurityException("Cannot connect - user not logged in.");
+
+ // Get authorized config
+ GuacamoleConfiguration config = configs.get(id);
+ if (config == null) {
+ logger.warn("Configuration id={} not found.", id);
+ throw new GuacamoleSecurityException("Requested configuration is not authorized.");
+ }
+
+ 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;
+ }
- }
+ };
}