diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java new file mode 100644 index 000000000..6b1755a59 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/RestrictedFilter.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.protocol.GuacamoleStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Filter which enforces authentication. If no user context is associated with + * the current HTTP session, or no HTTP session exists, the request is denied. + * + * @author Michael Jumper + */ +public class RestrictedFilter implements Filter { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(RestrictedFilter.class); + + @Override + public void init(FilterConfig config) throws ServletException { + // No configuration + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) resp; + + // Pull user context from session + UserContext context = null; + HttpSession session = request.getSession(false); + if (session != null) + context = AuthenticatingFilter.getUserContext(session); + + // If authenticated, proceed with rest of chain + if (context != null) + chain.doFilter(req, resp); + + // Otherwise, deny entire request + else { + final GuacamoleStatus status = GuacamoleStatus.CLIENT_UNAUTHORIZED; + final String message = "Not authenticated"; + + logger.warn("Client request rejected: {}", message); + response.addHeader("Guacamole-Status-Code", Integer.toString(status.getGuacamoleStatusCode())); + response.addHeader("Guacamole-Error-Message", message); + response.sendError(status.getHttpStatusCode()); + } + + } + + @Override + public void destroy() { + // No destruction needed + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/AuthenticatingWebSocketServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/AuthenticatingWebSocketServlet.java deleted file mode 100644 index dac92885b..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/AuthenticatingWebSocketServlet.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.websocket.jetty; - -import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; -import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketServlet; -import org.glyptodon.guacamole.net.basic.AuthenticatingFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A WebSocket servlet wrapped around an AuthenticatingHttpServlet. - * - * @author Michael Jumper - */ -public abstract class AuthenticatingWebSocketServlet extends WebSocketServlet { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(AuthenticatingWebSocketServlet.class); - - /** - * Wrapped authenticating servlet. - */ - private final RestrictedHttpServlet auth_servlet = new RestrictedHttpServlet() { - - @Override - protected void restrictedService(UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - try { - // If authenticated, service request - service_websocket_request(request, response); - } - catch (IOException e) { - throw new GuacamoleServerException( - "Cannot service WebSocket request (I/O error).", e); - } - catch (ServletException e) { - throw new GuacamoleServerException( - "Cannot service WebSocket request (internal error).", e); - } - - } - - }; - - @Override - public void init() throws ServletException { - super.init(); - auth_servlet.init(); - } - - @Override - protected void service(HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException { - - // Authenticate all inbound requests - auth_servlet.service(request, response); - - } - - /** - * Actually services the given request, bypassing the service() override - * and the authentication scheme. - * - * @param request The HttpServletRequest to service. - * @param response The associated HttpServletResponse. - * @throws IOException If an I/O error occurs while handling the request. - * @throws ServletException If an internal error occurs while handling the - * request. - */ - private void service_websocket_request(HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException { - - // Bypass override and service WebSocket request - super.service(request, response); - - } - - @Override - public WebSocket doWebSocketConnect(HttpServletRequest request, - String protocol) { - - // Get session and user context - HttpSession session = request.getSession(true); - UserContext context = AuthenticatingFilter.getUserContext(session); - - // Ensure user logged in - if (context == null) { - logger.warn("User no longer logged in upon WebSocket connect."); - return null; - } - - // Connect WebSocket - return authenticatedConnect(context, request, protocol); - - } - - /** - * Function called after the credentials given in the request (if any) - * are authenticated. If the current session is not associated with - * valid credentials, this function will not be called. - * - * @param context The current UserContext. - * @param request The HttpServletRequest being serviced. - * @param protocol The protocol being used over the WebSocket connection. - * @return A connected WebSocket. - */ - protected abstract WebSocket authenticatedConnect( - UserContext context, - HttpServletRequest request, String protocol); - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java index c5e1d9bbb..38c68b6f3 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java @@ -25,36 +25,19 @@ package org.glyptodon.guacamole.net.basic.websocket.jetty; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.eclipse.jetty.websocket.WebSocket; import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; /** - * Authenticating tunnel servlet implementation which uses WebSocket as a - * tunnel backend, rather than HTTP. + * Tunnel servlet implementation which uses WebSocket as a tunnel backend, + * rather than HTTP, properly parsing connection IDs included in the connection + * request. */ -public class BasicGuacamoleWebSocketTunnelServlet extends AuthenticatingWebSocketServlet { - - /** - * Wrapped GuacamoleHTTPTunnelServlet which will handle all authenticated - * requests. - */ - private GuacamoleWebSocketTunnelServlet tunnelServlet = - new GuacamoleWebSocketTunnelServlet() { - - @Override - protected GuacamoleTunnel doConnect(HttpServletRequest request) - throws GuacamoleException { - return BasicTunnelRequestUtility.createTunnel(request); - } - - }; +public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { @Override - protected WebSocket authenticatedConnect(UserContext context, - HttpServletRequest request, String protocol) { - return tunnelServlet.doWebSocketConnect(request, protocol); + protected GuacamoleTunnel doConnect(HttpServletRequest request) + throws GuacamoleException { + return BasicTunnelRequestUtility.createTunnel(request); } } - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/AuthenticatingWebSocketServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/AuthenticatingWebSocketServlet.java deleted file mode 100644 index c52a9410c..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/AuthenticatingWebSocketServlet.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.websocket.tomcat; - -import java.io.IOException; -import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.RestrictedHttpServlet; -import org.apache.catalina.websocket.StreamInbound; -import org.apache.catalina.websocket.WebSocketServlet; -import org.glyptodon.guacamole.net.basic.AuthenticatingFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A WebSocket servlet wrapped around an AuthenticatingHttpServlet. - * - * @author Michael Jumper - */ -public abstract class AuthenticatingWebSocketServlet extends WebSocketServlet { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(AuthenticatingWebSocketServlet.class); - - /** - * Wrapped authenticating servlet. - */ - private final RestrictedHttpServlet auth_servlet = new RestrictedHttpServlet() { - - @Override - protected void restrictedService(UserContext context, - HttpServletRequest request, HttpServletResponse response) - throws GuacamoleException { - - try { - // If authenticated, service request - service_websocket_request(request, response); - } - catch (IOException e) { - throw new GuacamoleServerException( - "Cannot service WebSocket request (I/O error).", e); - } - catch (ServletException e) { - throw new GuacamoleServerException( - "Cannot service WebSocket request (internal error).", e); - } - - } - - }; - - @Override - public void init() throws ServletException { - super.init(); - auth_servlet.init(); - } - - @Override - protected void service(HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException { - - // Authenticate all inbound requests - auth_servlet.service(request, response); - - } - - /** - * Actually services the given request, bypassing the service() override - * and the authentication scheme. - * - * @param request The HttpServletRequest to service. - * @param response The associated HttpServletResponse. - * @throws IOException If an I/O error occurs while handling the request. - * @throws ServletException If an internal error occurs while handling the - * request. - */ - private void service_websocket_request(HttpServletRequest request, - HttpServletResponse response) - throws IOException, ServletException { - - // Bypass override and service WebSocket request - super.service(request, response); - - } - - @Override - protected String selectSubProtocol(List subProtocols) { - - // Search for expected protocol - for (String protocol : subProtocols) - if ("guacamole".equals(protocol)) - return "guacamole"; - - // Otherwise, fail - return null; - - } - - @Override - public StreamInbound createWebSocketInbound(String protocol, - HttpServletRequest request) { - - // Get session and user context - HttpSession session = request.getSession(true); - UserContext context = AuthenticatingFilter.getUserContext(session); - - // Ensure user logged in - if (context == null) { - logger.warn("User no longer logged in upon WebSocket connect."); - return null; - } - - // Connect WebSocket - return authenticatedConnect(context, request, protocol); - - } - - /** - * Function called after the credentials given in the request (if any) - * are authenticated. If the current session is not associated with - * valid credentials, this function will not be called. - * - * @param context The current UserContext. - * @param request The HttpServletRequest being serviced. - * @param protocol The protocol being used over the WebSocket connection. - * @return A completed WebSocket connection. - */ - protected abstract StreamInbound authenticatedConnect( - UserContext context, - HttpServletRequest request, String protocol); - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java index bd011e8f4..32daff448 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java @@ -25,36 +25,19 @@ package org.glyptodon.guacamole.net.basic.websocket.tomcat; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.apache.catalina.websocket.StreamInbound; import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; /** - * Authenticating tunnel servlet implementation which uses WebSocket as a - * tunnel backend, rather than HTTP. + * Tunnel servlet implementation which uses WebSocket as a tunnel backend, + * rather than HTTP, properly parsing connection IDs included in the connection + * request. */ -public class BasicGuacamoleWebSocketTunnelServlet extends AuthenticatingWebSocketServlet { - - /** - * Wrapped GuacamoleHTTPTunnelServlet which will handle all authenticated - * requests. - */ - private GuacamoleWebSocketTunnelServlet tunnelServlet = - new GuacamoleWebSocketTunnelServlet() { - - @Override - protected GuacamoleTunnel doConnect(HttpServletRequest request) - throws GuacamoleException { - return BasicTunnelRequestUtility.createTunnel(request); - } - - }; +public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { @Override - protected StreamInbound authenticatedConnect(UserContext context, - HttpServletRequest request, String protocol) { - return tunnelServlet.createWebSocketInbound(protocol, request); - } + protected GuacamoleTunnel doConnect(HttpServletRequest request) + throws GuacamoleException { + return BasicTunnelRequestUtility.createTunnel(request); + }; } - diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java index 1cf8d90c6..1cd462f96 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java @@ -27,6 +27,7 @@ import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.io.GuacamoleReader; @@ -74,6 +75,19 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { } + @Override + protected String selectSubProtocol(List subProtocols) { + + // Search for expected protocol + for (String protocol : subProtocols) + if ("guacamole".equals(protocol)) + return "guacamole"; + + // Otherwise, fail + return null; + + } + @Override public StreamInbound createWebSocketInbound(String protocol, HttpServletRequest request) { diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml index 89498f54a..e0b7ed579 100644 --- a/guacamole/src/main/webapp/WEB-INF/web.xml +++ b/guacamole/src/main/webapp/WEB-INF/web.xml @@ -47,6 +47,16 @@ /* + + + RestrictedFilter + org.glyptodon.guacamole.net.basic.RestrictedFilter + + + RestrictedFilter + /websocket-tunnel + + Login servlet.