diff --git a/guacamole/web-client/doc/example/guacamole-users.xml b/guacamole/web-client/doc/example/guacamole-users.xml deleted file mode 100644 index 50bb77a12..000000000 --- a/guacamole/web-client/doc/example/guacamole-users.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/guacamole/web-client/doc/example/guacamole.xml b/guacamole/web-client/doc/example/guacamole.xml index 7499ca2ff..aeb6208b6 100644 --- a/guacamole/web-client/doc/example/guacamole.xml +++ b/guacamole/web-client/doc/example/guacamole.xml @@ -18,12 +18,16 @@ along with this program. If not, see . --> + - - + + + + + + - diff --git a/guacamole/web-client/doc/example/user-mapping.xml b/guacamole/web-client/doc/example/user-mapping.xml new file mode 100644 index 000000000..f006887f9 --- /dev/null +++ b/guacamole/web-client/doc/example/user-mapping.xml @@ -0,0 +1,11 @@ + + + + + vnc + localhost + 5900 + VNCPASS + + + diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/BasicGuacamoleSessionProvider.java b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java similarity index 59% rename from guacamole/web-client/src/net/sourceforge/guacamole/net/BasicGuacamoleSessionProvider.java rename to guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java index 85dcb3fbd..75997bdb4 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/BasicGuacamoleSessionProvider.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java @@ -1,8 +1,10 @@ -package net.sourceforge.guacamole.net; +package net.sourceforge.guacamole.basic; import javax.servlet.http.HttpSession; import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; +import net.sourceforge.guacamole.net.GuacamoleSessionProvider; /* * Guacamole - Clientless Remote Desktop @@ -26,11 +28,21 @@ public class BasicGuacamoleSessionProvider implements GuacamoleSessionProvider { public GuacamoleSession createSession(HttpSession session) throws GuacamoleException { + // Retrieve authorized config data from session + BasicLogin.AuthorizedConfiguration config = (BasicLogin.AuthorizedConfiguration) + session.getAttribute("BASIC-LOGIN-AUTH"); + + // If no data, not authorized + if (config == null) + throw new GuacamoleException("Unauthorized"); + + // Configure session from authorized config info GuacamoleSession guacSession = new GuacamoleSession(session); + guacSession.setConnection(config.getProtocol(), config.getHostname(), config.getPort()); + if (config.getPassword() != null) + guacSession.setPassword(config.getPassword()); - guacSession.setConnection("vnc", "localhost", 5901); - guacSession.setPassword("potato"); - + // Return authorized session return guacSession; } diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java new file mode 100644 index 000000000..f73b8974b --- /dev/null +++ b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java @@ -0,0 +1,125 @@ + +package net.sourceforge.guacamole.basic; + +import java.io.IOException; +import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import net.sourceforge.guacamole.basic.BasicUserMappingContentHandler.AuthInfo; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +public class BasicLogin extends HttpServlet { + + private Map mapping; + + // Added to session when session validated + public class AuthorizedConfiguration { + + private String protocol; + private String hostname; + private int port; + private String password; + + public AuthorizedConfiguration(String protocol, String hostname, int port, String password) { + this.protocol = protocol; + this.hostname = hostname; + this.port = port; + this.password = password; + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + } + + @Override + public void init() throws ServletException { + + // Get user mapping filename + ServletContext context = getServletContext(); + String filename = context.getInitParameter("basic-user-mapping"); + if (filename == null) + throw new ServletException("Missing \"basic-user-mapping\" parameter required for basic login."); + + // Parse document + try { + + BasicUserMappingContentHandler contentHandler = new BasicUserMappingContentHandler(); + + XMLReader parser = XMLReaderFactory.createXMLReader(); + parser.setContentHandler(contentHandler); + parser.parse(filename); + + mapping = contentHandler.getUserMapping(); + + } + catch (IOException e) { + throw new ServletException("Error reading basic user mapping file.", e); + } + catch (SAXException e) { + throw new ServletException("Error parsing basic user mapping XML.", e); + } + + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + // Retrieve username and password from parms + String username = req.getParameter("username"); + String password = req.getParameter("password"); + + // Retrieve corresponding authorization info + AuthInfo info = mapping.get(username); + if (info != null) { + + // Validate username and password + if (info.getAuthorizedUsername().equals(username) + && info.getAuthorizedPassword().equals(password)) { + + // Store authorized configuration + HttpSession session = req.getSession(true); + session.setAttribute( + "BASIC-LOGIN-AUTH", + new AuthorizedConfiguration( + info.getProtocol(), + info.getHostname(), + info.getPort(), + info.getPassword() + ) + ); + + // Success + return; + + } + + } + + // Report "forbidden" on any failure + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Login invalid"); + + } + + +} diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicUserMappingContentHandler.java b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicUserMappingContentHandler.java new file mode 100644 index 000000000..6f71ebf30 --- /dev/null +++ b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicUserMappingContentHandler.java @@ -0,0 +1,150 @@ + +package net.sourceforge.guacamole.basic; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +public class BasicUserMappingContentHandler extends DefaultHandler { + + private Map authMapping = new HashMap(); + + public Map getUserMapping() { + return Collections.unmodifiableMap(authMapping); + } + + public class AuthInfo { + + private String auth_username; + private String auth_password; + + private String protocol; + private String hostname; + private int port; + private String password; + + public AuthInfo(String auth_username, String auth_password) { + this.auth_username = auth_username; + this.auth_password = auth_password; + } + + public String getAuthorizedUsername() { + return auth_username; + } + + public String getAuthorizedPassword() { + return auth_password; + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + } + + private AuthInfo current; + + private enum AUTH_INFO_STATE { + PROTOCOL, + HOSTNAME, + PORT, + PASSWORD + }; + + private AUTH_INFO_STATE infoState; + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (localName.equals("authorize")) { + + // Finalize mapping for this user + authMapping.put( + current.getAuthorizedUsername(), + current + ); + + } + + infoState = null; + + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (localName.equals("authorize")) { + + current = new AuthInfo( + attributes.getValue("username"), + attributes.getValue("password") + ); + + infoState = null; + + } + + else if (localName.equals("protocol")) + infoState = AUTH_INFO_STATE.PROTOCOL; + + else if (localName.equals("hostname")) + infoState = AUTH_INFO_STATE.HOSTNAME; + + else if (localName.equals("port")) + infoState = AUTH_INFO_STATE.PORT; + + else if (localName.equals("password")) + infoState = AUTH_INFO_STATE.PASSWORD; + + else + infoState = null; + + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + + String str = new String(ch, start, length); + + if (infoState == null) + return; + + switch (infoState) { + + case PROTOCOL: + current.protocol = str; + break; + + case HOSTNAME: + current.hostname = str; + break; + + case PORT: + current.port = Integer.parseInt(str); + break; + + case PASSWORD: + current.password = str; + break; + + } + + } + + +} diff --git a/guacamole/web-client/web/META-INF/context.xml b/guacamole/web-client/web/META-INF/context.xml deleted file mode 100644 index ea825baf9..000000000 --- a/guacamole/web-client/web/META-INF/context.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/guacamole/web-client/web/WEB-INF/web.xml b/guacamole/web-client/web/WEB-INF/web.xml index 0be425c7f..216304c13 100644 --- a/guacamole/web-client/web/WEB-INF/web.xml +++ b/guacamole/web-client/web/WEB-INF/web.xml @@ -17,9 +17,8 @@ along with this program. If not, see . --> - + - index.html @@ -29,9 +28,8 @@ 30 - - + Connect servlet. Connect @@ -41,7 +39,6 @@ Connect /connect - Outbound servlet. Outbound @@ -51,7 +48,6 @@ Outbound /outbound - Input servlet. Inbound @@ -61,5 +57,15 @@ Inbound /inbound - + + + + BasicLogin + net.sourceforge.guacamole.basic.BasicLogin + + + BasicLogin + /login + + diff --git a/guacamole/web-client/web/guacamole.css b/guacamole/web-client/web/guacamole.css index bd0e7c80f..4a0cbf533 100644 --- a/guacamole/web-client/web/guacamole.css +++ b/guacamole/web-client/web/guacamole.css @@ -34,6 +34,13 @@ div#login-ui { display: table; } +p#login-error { + text-align: center; + background: #FDD; + color: red; + margin: 0.2em; +} + div#login-logo { position: relative; bottom: 0; @@ -71,19 +78,18 @@ div#login-dialog h1 { margin-top: 0; margin-bottom: 0em; text-align: center; - border-bottom: 1px solid silver; - padding-bottom: 0.5em; } div#login-dialog #buttons { - border-top: 1px solid silver; padding-top: 0.5em; text-align: center; } div#login-dialog #login-fields { - margin-top: 0.5em; - margin-bottom: 0.5em; + border-top: 1px solid silver; + border-bottom: 1px solid silver; + padding-top: 0.5em; + padding-bottom: 0.5em; } div.errorDialogOuter { diff --git a/guacamole/web-client/web/index.html b/guacamole/web-client/web/index.html index f6a53aabb..77d939b51 100644 --- a/guacamole/web-client/web/index.html +++ b/guacamole/web-client/web/index.html @@ -40,6 +40,9 @@

Guacamole Login

+ +

+
@@ -124,10 +127,33 @@ loginForm.onsubmit = function() { - // FIXME: Do ACTUAL login here + var username = document.getElementById("username"); + var password = document.getElementById("password"); - loginUI.style.display = "none"; - startGuacamole(); + var data = + "username=" + encodeURIComponent(username.value) + + "&password=" + encodeURIComponent(password.value) + + var xmlhttprequest = new XMLHttpRequest(); + xmlhttprequest.open("POST", "login", false); + xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xmlhttprequest.setRequestHeader("Content-length", data.length); + xmlhttprequest.send(data); + + if (xmlhttprequest.status == 200) { + loginUI.style.display = "none"; + startGuacamole(); + } + else { + + var loginError = document.getElementById("login-error"); + + // Display error, reset and refocus password field + loginError.textContent = "Invalid login. Please try again."; + password.value = ""; + password.focus(); + + } return false;