From f99f9cff15f0c1d90939f21db07d7d87240fec73 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 5 Dec 2010 14:33:43 -0800 Subject: [PATCH] Initial auth provider, using properties instead of XML --- .../doc/example/guacamole.properties | 26 ++ .../web-client/doc/example/guacamole.xml | 33 -- .../guacamole/basic/BasicLogin.java | 149 --------- .../basic/BasicUserMappingContentHandler.java | 217 ------------- .../guacamole/net/Configuration.java | 17 +- .../guacamole/net/GuacamoleConfiguration.java | 10 +- .../guacamole/net/GuacamoleProperties.java | 31 ++ .../guacamole/net/GuacamoleServlet.java | 4 +- .../guacamole/net/GuacamoleSession.java | 3 +- .../GuacamoleSessionProvider.java | 3 +- .../NullGuacamoleSessionProvider.java | 3 +- .../BasicFileAuthenticationProvider.java | 300 ++++++++++++++++++ .../basic/BasicGuacamoleSessionProvider.java | 4 +- .../net/authentication/basic/BasicLogin.java | 89 ++++++ guacamole/web-client/web/META-INF/context.xml | 2 + guacamole/web-client/web/WEB-INF/web.xml | 7 +- 16 files changed, 470 insertions(+), 428 deletions(-) create mode 100644 guacamole/web-client/doc/example/guacamole.properties delete mode 100644 guacamole/web-client/doc/example/guacamole.xml delete mode 100644 guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java delete mode 100644 guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicUserMappingContentHandler.java create mode 100644 guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleProperties.java rename guacamole/web-client/src/net/sourceforge/guacamole/net/{ => authentication}/GuacamoleSessionProvider.java (90%) rename guacamole/web-client/src/net/sourceforge/guacamole/net/{ => authentication}/NullGuacamoleSessionProvider.java (91%) create mode 100644 guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java rename guacamole/web-client/src/net/sourceforge/guacamole/{ => net/authentication}/basic/BasicGuacamoleSessionProvider.java (92%) create mode 100644 guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java create mode 100644 guacamole/web-client/web/META-INF/context.xml diff --git a/guacamole/web-client/doc/example/guacamole.properties b/guacamole/web-client/doc/example/guacamole.properties new file mode 100644 index 000000000..fb8b93900 --- /dev/null +++ b/guacamole/web-client/doc/example/guacamole.properties @@ -0,0 +1,26 @@ + +# Guacamole - Clientless Remote Desktop +# Copyright (C) 2010 Michael Jumper +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +# Hostname and port of guacamole proxy +guacd-hostname: localhost +guacd-port: 4822 + +# Session provider class (provides and configured guacamole session based on authentication information) +session-provider: net.sourceforge.guacamole.basic.BasicGuacamoleSessionProvider +basic-user-mapping: /path/to/user-mapping.xml + diff --git a/guacamole/web-client/doc/example/guacamole.xml b/guacamole/web-client/doc/example/guacamole.xml deleted file mode 100644 index aeb6208b6..000000000 --- a/guacamole/web-client/doc/example/guacamole.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java b/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java deleted file mode 100644 index 2733abde9..000000000 --- a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicLogin.java +++ /dev/null @@ -1,149 +0,0 @@ - -package net.sourceforge.guacamole.basic; - -import java.io.File; -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 long mappingTime; - 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; - } - - } - - private File getUserMappingFile() { - - // Get user mapping filename - ServletContext context = getServletContext(); - String filename = context.getInitParameter("basic-user-mapping"); - if (filename == null) - return null; - - return new File(filename); - - } - - @Override - public synchronized void init() throws ServletException { - - // Get user mapping file - File mapFile = getUserMappingFile(); - if (mapFile == 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(mapFile.getAbsolutePath()); - - mappingTime = mapFile.lastModified(); - 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 { - - // Check mapping file mod time - File userMappingFile = getUserMappingFile(); - if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) { - - // If modified recently, gain exclusive access and recheck - synchronized (this) { - if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) - init(); // If still not up to date, re-init - } - - } - - // 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.validate(username, 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 deleted file mode 100644 index ea282c448..000000000 --- a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicUserMappingContentHandler.java +++ /dev/null @@ -1,217 +0,0 @@ - -package net.sourceforge.guacamole.basic; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 static class AuthInfo { - - public static enum Encoding { - PLAIN_TEXT, - MD5 - } - - private String auth_username; - private String auth_password; - private Encoding auth_encoding; - - private String protocol; - private String hostname; - private int port; - private String password; - - public AuthInfo(String auth_username, String auth_password, Encoding auth_encoding) { - this.auth_username = auth_username; - this.auth_password = auth_password; - this.auth_encoding = auth_encoding; - } - - private static final char HEX_CHARS[] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' - }; - - public static String getHexString(byte[] bytes) { - - if (bytes == null) - return null; - - StringBuilder hex = new StringBuilder(2 * bytes.length); - for (byte b : bytes) { - hex.append(HEX_CHARS[(b & 0xF0) >> 4]) - .append(HEX_CHARS[(b & 0x0F) ]); - } - - return hex.toString(); - - } - - - public boolean validate(String username, String password) { - - // If username matches - if (username != null && password != null && username.equals(auth_username)) { - - switch (auth_encoding) { - - case PLAIN_TEXT: - - // Compare plaintext - return password.equals(auth_password); - - case MD5: - - // Compare hashed password - try { - MessageDigest digest = MessageDigest.getInstance("MD5"); - String hashedPassword = getHexString(digest.digest(password.getBytes())); - return hashedPassword.equals(auth_password.toUpperCase()); - } - catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e); - } - - } - - } - - return false; - - } - - 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.auth_username, - current - ); - - } - - infoState = null; - - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - - if (localName.equals("authorize")) { - - AuthInfo.Encoding encoding; - String encodingString = attributes.getValue("encoding"); - if (encodingString == null) - encoding = AuthInfo.Encoding.PLAIN_TEXT; - else if (encodingString.equals("plain")) - encoding = AuthInfo.Encoding.PLAIN_TEXT; - else if (encodingString.equals("md5")) - encoding = AuthInfo.Encoding.MD5; - else - throw new SAXException("Invalid encoding type"); - - - current = new AuthInfo( - attributes.getValue("username"), - attributes.getValue("password"), - encoding - ); - - 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/src/net/sourceforge/guacamole/net/Configuration.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/Configuration.java index b8bc8d057..6e78eb296 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/Configuration.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/Configuration.java @@ -24,8 +24,6 @@ import net.sourceforge.guacamole.GuacamoleException; public abstract class Configuration { - private ServletContext context; - protected String humanReadableList(Object... values) { String list = ""; @@ -44,9 +42,14 @@ public abstract class Configuration { } + protected String readParameter(String name) throws GuacamoleException { + String value = GuacamoleProperties.getProperty(name); + return value; + } + protected String readParameter(String name, String defaultValue, String... allowedValues) throws GuacamoleException { - String value = context.getInitParameter(name); + String value = GuacamoleProperties.getProperty(name); // Use default if not specified if (value == null) { @@ -70,7 +73,7 @@ public abstract class Configuration { protected boolean readBooleanParameter(String name, Boolean defaultValue) throws GuacamoleException { - String value = context.getInitParameter(name); + String value = GuacamoleProperties.getProperty(name); // Use default if not specified if (value == null) { @@ -93,7 +96,7 @@ public abstract class Configuration { protected int readIntParameter(String name, Integer defaultValue, Integer... allowedValues) throws GuacamoleException { - String parmString = context.getInitParameter(name); + String parmString = GuacamoleProperties.getProperty(name); // Use default if not specified if (parmString== null) { @@ -123,8 +126,4 @@ public abstract class Configuration { } - public Configuration(ServletContext context) { - this.context = context; - } - } diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java index ac56cb825..a6bfeeb52 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java @@ -19,8 +19,8 @@ package net.sourceforge.guacamole.net; * along with this program. If not, see . */ +import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider; import java.lang.reflect.InvocationTargetException; -import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import net.sourceforge.guacamole.GuacamoleException; @@ -30,16 +30,14 @@ public class GuacamoleConfiguration extends Configuration { private int guacd_port; private GuacamoleSessionProvider sessionProvider; - public GuacamoleConfiguration(ServletContext context) throws GuacamoleException { + public GuacamoleConfiguration() throws GuacamoleException { - super(context); - - guacd_hostname = context.getInitParameter("guacd-hostname"); + guacd_hostname = readParameter("guacd-hostname"); guacd_port = readIntParameter("guacd-port", null); // Get session provider instance try { - String sessionProviderClassName = context.getInitParameter("session-provider"); + String sessionProviderClassName = readParameter("session-provider"); Object obj = Class.forName(sessionProviderClassName).getConstructor().newInstance(); if (!(obj instanceof GuacamoleSessionProvider)) throw new GuacamoleException("Specified session provider class is not a GuacamoleSessionProvider"); diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleProperties.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleProperties.java new file mode 100644 index 000000000..25eef84ca --- /dev/null +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleProperties.java @@ -0,0 +1,31 @@ + +package net.sourceforge.guacamole.net; + +import java.io.IOException; +import java.util.Properties; +import javax.servlet.ServletException; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.authentication.basic.BasicLogin; + +public class GuacamoleProperties { + + private static final Properties properties = new Properties(); + private static GuacamoleException exception; + + static { + + try { + properties.load(BasicLogin.class.getResourceAsStream("/guacamole.properties")); + } + catch (IOException e) { + exception = new GuacamoleException("Error reading guacamole.properties", e); + } + + } + + public static String getProperty(String name) throws GuacamoleException { + if (exception != null) throw exception; + return properties.getProperty(name); + } + +} diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java index 0ccbff9e3..a1e713132 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java @@ -33,9 +33,9 @@ public abstract class GuacamoleServlet extends HttpServlet { private GuacamoleConfiguration config; @Override - public void init(ServletConfig config) throws ServletException { + public void init() throws ServletException { try { - this.config = new GuacamoleConfiguration(config.getServletContext()); + this.config = new GuacamoleConfiguration(); } catch (GuacamoleException e) { throw new ServletException(e); diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSession.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSession.java index 650f2fce5..2eb01326b 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSession.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSession.java @@ -104,8 +104,7 @@ public class GuacamoleSession { synchronized (session) { // Read configuration parameters - ServletContext context = session.getServletContext(); - config = new GuacamoleConfiguration(context); + config = new GuacamoleConfiguration(); client = (SessionClient) session.getAttribute("CLIENT"); instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK"); diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSessionProvider.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java similarity index 90% rename from guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSessionProvider.java rename to guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java index 3bcd25876..bbe82a2de 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/GuacamoleSessionProvider.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/GuacamoleSessionProvider.java @@ -1,8 +1,9 @@ -package net.sourceforge.guacamole.net; +package net.sourceforge.guacamole.net.authentication; import javax.servlet.http.HttpSession; import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; /* * Guacamole - Clientless Remote Desktop diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/NullGuacamoleSessionProvider.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java similarity index 91% rename from guacamole/web-client/src/net/sourceforge/guacamole/net/NullGuacamoleSessionProvider.java rename to guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java index bb0e75532..a46d0fe57 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/net/NullGuacamoleSessionProvider.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/NullGuacamoleSessionProvider.java @@ -1,8 +1,9 @@ -package net.sourceforge.guacamole.net; +package net.sourceforge.guacamole.net.authentication; import javax.servlet.http.HttpSession; import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; /* * Guacamole - Clientless Remote Desktop diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java new file mode 100644 index 000000000..34a631279 --- /dev/null +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicFileAuthenticationProvider.java @@ -0,0 +1,300 @@ + +package net.sourceforge.guacamole.net.authentication.basic; + +import java.io.File; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import net.sourceforge.guacamole.GuacamoleException; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +public class BasicFileAuthenticationProvider implements BasicLogin.AuthenticationProvider { + + private long mappingTime; + private Map mapping; + + private File getUserMappingFile() { + + // Get user mapping filename + //String filename = context.getInitParameter("basic-user-mapping"); + String filename = ""; // FIXME + if (filename == null) + return null; + + return new File(filename); + + } + + public synchronized void init() throws GuacamoleException { + + // Get user mapping file + File mapFile = getUserMappingFile(); + if (mapFile == null) + throw new GuacamoleException("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(mapFile.getAbsolutePath()); + + mappingTime = mapFile.lastModified(); + mapping = contentHandler.getUserMapping(); + + } + catch (IOException e) { + throw new GuacamoleException("Error reading basic user mapping file.", e); + } + catch (SAXException e) { + throw new GuacamoleException("Error parsing basic user mapping XML.", e); + } + + } + + @Override + public BasicLogin.AuthorizedConfiguration getAuthorizedConfiguration(String username, String password) throws GuacamoleException { + + // Check mapping file mod time + File userMappingFile = getUserMappingFile(); + if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) { + + // If modified recently, gain exclusive access and recheck + synchronized (this) { + if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) + init(); // If still not up to date, re-init + } + + } + + AuthInfo info = mapping.get(username); + if (info != null && info.validate(username, password)) + return new BasicLogin.AuthorizedConfiguration( + info.getProtocol(), + info.getHostname(), + info.getPort(), + info.getPassword() + ); + + return null; + + } + + public static class AuthInfo { + + public static enum Encoding { + PLAIN_TEXT, + MD5 + } + + private String auth_username; + private String auth_password; + private Encoding auth_encoding; + + private String protocol; + private String hostname; + private int port; + private String password; + + public AuthInfo(String auth_username, String auth_password, Encoding auth_encoding) { + this.auth_username = auth_username; + this.auth_password = auth_password; + this.auth_encoding = auth_encoding; + } + + private static final char HEX_CHARS[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + public static String getHexString(byte[] bytes) { + + if (bytes == null) + return null; + + StringBuilder hex = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + hex.append(HEX_CHARS[(b & 0xF0) >> 4]) + .append(HEX_CHARS[(b & 0x0F) ]); + } + + return hex.toString(); + + } + + + public boolean validate(String username, String password) { + + // If username matches + if (username != null && password != null && username.equals(auth_username)) { + + switch (auth_encoding) { + + case PLAIN_TEXT: + + // Compare plaintext + return password.equals(auth_password); + + case MD5: + + // Compare hashed password + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + String hashedPassword = getHexString(digest.digest(password.getBytes())); + return hashedPassword.equals(auth_password.toUpperCase()); + } + catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e); + } + + } + + } + + return false; + + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + } + + + private static class BasicUserMappingContentHandler extends DefaultHandler { + + private Map authMapping = new HashMap(); + + public Map getUserMapping() { + return Collections.unmodifiableMap(authMapping); + } + + 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.auth_username, + current + ); + + } + + infoState = null; + + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (localName.equals("authorize")) { + + AuthInfo.Encoding encoding; + String encodingString = attributes.getValue("encoding"); + if (encodingString == null) + encoding = AuthInfo.Encoding.PLAIN_TEXT; + else if (encodingString.equals("plain")) + encoding = AuthInfo.Encoding.PLAIN_TEXT; + else if (encodingString.equals("md5")) + encoding = AuthInfo.Encoding.MD5; + else + throw new SAXException("Invalid encoding type"); + + + current = new AuthInfo( + attributes.getValue("username"), + attributes.getValue("password"), + encoding + ); + + 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/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java similarity index 92% rename from guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java rename to guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java index 75997bdb4..959bf0e6e 100644 --- a/guacamole/web-client/src/net/sourceforge/guacamole/basic/BasicGuacamoleSessionProvider.java +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicGuacamoleSessionProvider.java @@ -1,10 +1,10 @@ -package net.sourceforge.guacamole.basic; +package net.sourceforge.guacamole.net.authentication.basic; import javax.servlet.http.HttpSession; import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.net.GuacamoleSession; -import net.sourceforge.guacamole.net.GuacamoleSessionProvider; +import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider; /* * Guacamole - Clientless Remote Desktop diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java new file mode 100644 index 000000000..76287bd01 --- /dev/null +++ b/guacamole/web-client/src/net/sourceforge/guacamole/net/authentication/basic/BasicLogin.java @@ -0,0 +1,89 @@ + +package net.sourceforge.guacamole.net.authentication.basic; + +import java.io.IOException; +import java.util.Properties; +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.GuacamoleException; + +public class BasicLogin extends HttpServlet { + + private AuthenticationProvider authProvider; + + public static interface AuthenticationProvider { + public AuthorizedConfiguration getAuthorizedConfiguration(String username, String password) throws GuacamoleException; + } + + // Added to session when session validated + public static 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 + 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"); + + // Validate username and password + try { + + AuthorizedConfiguration info = authProvider.getAuthorizedConfiguration(username, password); + if (info != null) { + + // Store authorized configuration + HttpSession session = req.getSession(true); + session.setAttribute( + "BASIC-LOGIN-AUTH", + info + ); + + // Success + return; + + } + + // Report "forbidden" on any failure + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Login invalid"); + } + catch (GuacamoleException e) { + throw new ServletException("Error validating credentials", e); + } + + } + + +} diff --git a/guacamole/web-client/web/META-INF/context.xml b/guacamole/web-client/web/META-INF/context.xml new file mode 100644 index 000000000..5bee3dc30 --- /dev/null +++ b/guacamole/web-client/web/META-INF/context.xml @@ -0,0 +1,2 @@ + + diff --git a/guacamole/web-client/web/WEB-INF/web.xml b/guacamole/web-client/web/WEB-INF/web.xml index 216304c13..c7cbc9fb6 100644 --- a/guacamole/web-client/web/WEB-INF/web.xml +++ b/guacamole/web-client/web/WEB-INF/web.xml @@ -17,18 +17,15 @@ along with this program. If not, see . --> - index.html - 30 - Connect servlet. @@ -57,15 +54,13 @@ Inbound /inbound - BasicLogin - net.sourceforge.guacamole.basic.BasicLogin + net.sourceforge.guacamole.net.authentication.basic.BasicLogin BasicLogin /login -