mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
Initial auth provider, using properties instead of XML
This commit is contained in:
26
guacamole/web-client/doc/example/guacamole.properties
Normal file
26
guacamole/web-client/doc/example/guacamole.properties
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
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 <http://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Context antiJARLocking="true" path="/guacamole" docBase="/var/lib/guacamole/guacamole.war">
|
|
||||||
|
|
||||||
<!-- Change the lines below to match your Guacamole proxy -->
|
|
||||||
<Parameter name="guacd-hostname" value="localhost"/>
|
|
||||||
<Parameter name="guacd-port" value="4822"/>
|
|
||||||
|
|
||||||
<!-- Session provider class (provides and configured guacamole session based on authentication information) -->
|
|
||||||
<Parameter name="session-provider" value="net.sourceforge.guacamole.basic.BasicGuacamoleSessionProvider"/>
|
|
||||||
<Parameter name="basic-user-mapping" value="/path/to/user-mapping.xml"/>
|
|
||||||
|
|
||||||
</Context>
|
|
||||||
|
|
@@ -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<String, AuthInfo> 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");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -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<String, AuthInfo> authMapping = new HashMap<String, AuthInfo>();
|
|
||||||
|
|
||||||
public Map<String, AuthInfo> 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -24,8 +24,6 @@ import net.sourceforge.guacamole.GuacamoleException;
|
|||||||
|
|
||||||
public abstract class Configuration {
|
public abstract class Configuration {
|
||||||
|
|
||||||
private ServletContext context;
|
|
||||||
|
|
||||||
protected String humanReadableList(Object... values) {
|
protected String humanReadableList(Object... values) {
|
||||||
|
|
||||||
String list = "";
|
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 {
|
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
|
// Use default if not specified
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -70,7 +73,7 @@ public abstract class Configuration {
|
|||||||
|
|
||||||
protected boolean readBooleanParameter(String name, Boolean defaultValue) throws GuacamoleException {
|
protected boolean readBooleanParameter(String name, Boolean defaultValue) throws GuacamoleException {
|
||||||
|
|
||||||
String value = context.getInitParameter(name);
|
String value = GuacamoleProperties.getProperty(name);
|
||||||
|
|
||||||
// Use default if not specified
|
// Use default if not specified
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -93,7 +96,7 @@ public abstract class Configuration {
|
|||||||
|
|
||||||
protected int readIntParameter(String name, Integer defaultValue, Integer... allowedValues) throws GuacamoleException {
|
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
|
// Use default if not specified
|
||||||
if (parmString== null) {
|
if (parmString== null) {
|
||||||
@@ -123,8 +126,4 @@ public abstract class Configuration {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Configuration(ServletContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,8 @@ package net.sourceforge.guacamole.net;
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import net.sourceforge.guacamole.GuacamoleException;
|
import net.sourceforge.guacamole.GuacamoleException;
|
||||||
|
|
||||||
@@ -30,16 +30,14 @@ public class GuacamoleConfiguration extends Configuration {
|
|||||||
private int guacd_port;
|
private int guacd_port;
|
||||||
private GuacamoleSessionProvider sessionProvider;
|
private GuacamoleSessionProvider sessionProvider;
|
||||||
|
|
||||||
public GuacamoleConfiguration(ServletContext context) throws GuacamoleException {
|
public GuacamoleConfiguration() throws GuacamoleException {
|
||||||
|
|
||||||
super(context);
|
guacd_hostname = readParameter("guacd-hostname");
|
||||||
|
|
||||||
guacd_hostname = context.getInitParameter("guacd-hostname");
|
|
||||||
guacd_port = readIntParameter("guacd-port", null);
|
guacd_port = readIntParameter("guacd-port", null);
|
||||||
|
|
||||||
// Get session provider instance
|
// Get session provider instance
|
||||||
try {
|
try {
|
||||||
String sessionProviderClassName = context.getInitParameter("session-provider");
|
String sessionProviderClassName = readParameter("session-provider");
|
||||||
Object obj = Class.forName(sessionProviderClassName).getConstructor().newInstance();
|
Object obj = Class.forName(sessionProviderClassName).getConstructor().newInstance();
|
||||||
if (!(obj instanceof GuacamoleSessionProvider))
|
if (!(obj instanceof GuacamoleSessionProvider))
|
||||||
throw new GuacamoleException("Specified session provider class is not a GuacamoleSessionProvider");
|
throw new GuacamoleException("Specified session provider class is not a GuacamoleSessionProvider");
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -33,9 +33,9 @@ public abstract class GuacamoleServlet extends HttpServlet {
|
|||||||
private GuacamoleConfiguration config;
|
private GuacamoleConfiguration config;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ServletConfig config) throws ServletException {
|
public void init() throws ServletException {
|
||||||
try {
|
try {
|
||||||
this.config = new GuacamoleConfiguration(config.getServletContext());
|
this.config = new GuacamoleConfiguration();
|
||||||
}
|
}
|
||||||
catch (GuacamoleException e) {
|
catch (GuacamoleException e) {
|
||||||
throw new ServletException(e);
|
throw new ServletException(e);
|
||||||
|
@@ -104,8 +104,7 @@ public class GuacamoleSession {
|
|||||||
synchronized (session) {
|
synchronized (session) {
|
||||||
|
|
||||||
// Read configuration parameters
|
// Read configuration parameters
|
||||||
ServletContext context = session.getServletContext();
|
config = new GuacamoleConfiguration();
|
||||||
config = new GuacamoleConfiguration(context);
|
|
||||||
|
|
||||||
client = (SessionClient) session.getAttribute("CLIENT");
|
client = (SessionClient) session.getAttribute("CLIENT");
|
||||||
instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK");
|
instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK");
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
package net.sourceforge.guacamole.net;
|
package net.sourceforge.guacamole.net.authentication;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import net.sourceforge.guacamole.GuacamoleException;
|
import net.sourceforge.guacamole.GuacamoleException;
|
||||||
|
import net.sourceforge.guacamole.net.GuacamoleSession;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guacamole - Clientless Remote Desktop
|
* Guacamole - Clientless Remote Desktop
|
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
package net.sourceforge.guacamole.net;
|
package net.sourceforge.guacamole.net.authentication;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import net.sourceforge.guacamole.GuacamoleException;
|
import net.sourceforge.guacamole.GuacamoleException;
|
||||||
|
import net.sourceforge.guacamole.net.GuacamoleSession;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guacamole - Clientless Remote Desktop
|
* Guacamole - Clientless Remote Desktop
|
@@ -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<String, AuthInfo> 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<String, AuthInfo> authMapping = new HashMap<String, AuthInfo>();
|
||||||
|
|
||||||
|
public Map<String, AuthInfo> 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
|
|
||||||
package net.sourceforge.guacamole.basic;
|
package net.sourceforge.guacamole.net.authentication.basic;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import net.sourceforge.guacamole.GuacamoleException;
|
import net.sourceforge.guacamole.GuacamoleException;
|
||||||
import net.sourceforge.guacamole.net.GuacamoleSession;
|
import net.sourceforge.guacamole.net.GuacamoleSession;
|
||||||
import net.sourceforge.guacamole.net.GuacamoleSessionProvider;
|
import net.sourceforge.guacamole.net.authentication.GuacamoleSessionProvider;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guacamole - Clientless Remote Desktop
|
* Guacamole - Clientless Remote Desktop
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
2
guacamole/web-client/web/META-INF/context.xml
Normal file
2
guacamole/web-client/web/META-INF/context.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Context antiJARLocking="true" path=""/>
|
@@ -17,18 +17,15 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
|
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
|
||||||
|
|
||||||
<!-- Basic config -->
|
<!-- Basic config -->
|
||||||
<welcome-file-list>
|
<welcome-file-list>
|
||||||
<welcome-file>index.html</welcome-file>
|
<welcome-file>index.html</welcome-file>
|
||||||
</welcome-file-list>
|
</welcome-file-list>
|
||||||
|
|
||||||
<session-config>
|
<session-config>
|
||||||
<session-timeout>
|
<session-timeout>
|
||||||
30
|
30
|
||||||
</session-timeout>
|
</session-timeout>
|
||||||
</session-config>
|
</session-config>
|
||||||
|
|
||||||
<!-- Guacamole Tunnel Servlets -->
|
<!-- Guacamole Tunnel Servlets -->
|
||||||
<servlet>
|
<servlet>
|
||||||
<description>Connect servlet.</description>
|
<description>Connect servlet.</description>
|
||||||
@@ -57,15 +54,13 @@
|
|||||||
<servlet-name>Inbound</servlet-name>
|
<servlet-name>Inbound</servlet-name>
|
||||||
<url-pattern>/inbound</url-pattern>
|
<url-pattern>/inbound</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
<!-- Basic Login Servlets -->
|
<!-- Basic Login Servlets -->
|
||||||
<servlet>
|
<servlet>
|
||||||
<servlet-name>BasicLogin</servlet-name>
|
<servlet-name>BasicLogin</servlet-name>
|
||||||
<servlet-class>net.sourceforge.guacamole.basic.BasicLogin</servlet-class>
|
<servlet-class>net.sourceforge.guacamole.net.authentication.basic.BasicLogin</servlet-class>
|
||||||
</servlet>
|
</servlet>
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<servlet-name>BasicLogin</servlet-name>
|
<servlet-name>BasicLogin</servlet-name>
|
||||||
<url-pattern>/login</url-pattern>
|
<url-pattern>/login</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
</web-app>
|
</web-app>
|
||||||
|
Reference in New Issue
Block a user