Implement multiple authorized connections per user.
[guacamole.git] / src / main / java / net / sourceforge / guacamole / net / basic / BasicFileAuthenticationProvider.java
index f81503f..06be103 100644 (file)
@@ -19,35 +19,61 @@ package net.sourceforge.guacamole.net.basic;
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+import java.io.BufferedReader;
+import net.sourceforge.guacamole.net.auth.AuthenticationProvider;
 import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
+import java.io.Reader;
 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 net.sourceforge.guacamole.net.Configuration;
-import net.sourceforge.guacamole.net.GuacamoleProperties;
+import net.sourceforge.guacamole.net.auth.Credentials;
+import net.sourceforge.guacamole.properties.FileGuacamoleProperty;
+import net.sourceforge.guacamole.properties.GuacamoleProperties;
+import net.sourceforge.guacamole.protocol.GuacamoleConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
 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 {
+/**
+ * Authenticates users against a static list of username/password pairs.
+ * Each username/password may be associated with multiple configurations.
+ * This list is stored in an XML file which is reread if modified.
+ * 
+ * This is modified version of BasicFileAuthenticationProvider written by Michael Jumper.
+ * 
+ * @author Michal Kotas
+ */
+public class BasicFileAuthenticationProvider implements AuthenticationProvider {
 
+    private Logger logger = LoggerFactory.getLogger(BasicFileAuthenticationProvider.class);
+    
     private long mappingTime;
     private Map<String, AuthInfo> mapping;
 
-    private File getUserMappingFile() throws GuacamoleException {
+    /**
+     * The filename of the XML file to read the user mapping from.
+     */
+    public static final FileGuacamoleProperty BASIC_USER_MAPPING = new FileGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "basic-user-mapping"; }
 
-        // Get user mapping filename
-        String filename = GuacamoleProperties.getProperty("basic-user-mapping");
-        if (filename == null)
-            return null;
+    };
 
-        return new File(filename);
+    private File getUserMappingFile() throws GuacamoleException {
+
+        // Get user mapping file
+        return GuacamoleProperties.getProperty(BASIC_USER_MAPPING);
 
     }
 
@@ -58,15 +84,23 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
         if (mapFile == null)
             throw new GuacamoleException("Missing \"basic-user-mapping\" parameter required for basic login.");
 
+        logger.info("Reading user mapping file: {}", mapFile);
+        
         // Parse document
         try {
 
+            // Set up parser
             BasicUserMappingContentHandler contentHandler = new BasicUserMappingContentHandler();
 
             XMLReader parser = XMLReaderFactory.createXMLReader();
             parser.setContentHandler(contentHandler);
-            parser.parse(mapFile.getAbsolutePath());
 
+            // Read and parse file
+            Reader reader = new BufferedReader(new FileReader(mapFile));
+            parser.parse(new InputSource(reader));
+            reader.close();
+
+            // Init mapping and record mod time of file
             mappingTime = mapFile.lastModified();
             mapping = contentHandler.getUserMapping();
 
@@ -81,7 +115,7 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
     }
 
     @Override
-    public Configuration getAuthorizedConfiguration(String username, String password) throws GuacamoleException {
+    public Map<String, GuacamoleConfiguration> getAuthorizedConfigurations(Credentials credentials) throws GuacamoleException {
 
         // Check mapping file mod time
         File userMappingFile = getUserMappingFile();
@@ -89,16 +123,31 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
 
             // If modified recently, gain exclusive access and recheck
             synchronized (this) {
-                if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified())
+                if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) {
+                    logger.info("User mapping file {} has been modified.", userMappingFile);
                     init(); // If still not up to date, re-init
+                }
             }
 
         }
 
-        AuthInfo info = mapping.get(username);
-        if (info != null && info.validate(username, password))
-            return info.getConfiguration();
+        // If no mapping available, report as such
+        if (mapping == null)
+            throw new GuacamoleException("User mapping could not be read.");
+        
+        // Validate and return info for given user and pass
+        AuthInfo info = mapping.get(credentials.getUsername());
+        if (info != null && info.validate(credentials.getUsername(), credentials.getPassword())) {
+            
+            //Map<String, GuacamoleConfiguration> configs = new HashMap<String, GuacamoleConfiguration>();
+            //configs.put("DEFAULT", info.getConfiguration());
+            //return configs;
+            
+            Map<String, GuacamoleConfiguration> configs = info.getConfigurations();          
+            return configs;
+        }
 
+        // Unauthorized
         return null;
 
     }
@@ -114,14 +163,14 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
         private String auth_password;
         private Encoding auth_encoding;
 
-        private Configuration config;
+        private Map<String, GuacamoleConfiguration> configs;
 
         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;
 
-            config = new Configuration();
+            configs = new HashMap<String, GuacamoleConfiguration>();
         }
 
         private static final char HEX_CHARS[] = {
@@ -177,13 +226,19 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
 
         }
 
-        public Configuration getConfiguration() {
-            return config;
+        public GuacamoleConfiguration getConfiguration(String name) {
+            //return configs;
+            return configs.get(name);
+        }
+        public Map<String, GuacamoleConfiguration> getConfigurations() {
+            return configs;
+        }
+        public void addConfiguration(String name) {
+            configs.put(name, new GuacamoleConfiguration());
         }
 
     }
 
-
     private static class BasicUserMappingContentHandler extends DefaultHandler {
 
         private Map<String, AuthInfo> authMapping = new HashMap<String, AuthInfo>();
@@ -195,6 +250,7 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
         private enum State {
             ROOT,
             USER_MAPPING,
+            REMOTE_SERVER,
             AUTH_INFO,
             PROTOCOL,
             PARAMETER,
@@ -204,56 +260,66 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
         private State state = State.ROOT;
         private AuthInfo current = null;
         private String currentParameter = null;
+        private String currentRemoteServer = null;
 
         @Override
         public void endElement(String uri, String localName, String qName) throws SAXException {
 
             switch (state)  {
 
-                case USER_MAPPING:
+            case USER_MAPPING:
 
-                    if (localName.equals("user-mapping")) {
-                        state = State.END;
-                        return;
-                    }
+                if (localName.equals("user-mapping")) {
+                    state = State.END;
+                    return;
+                }
 
-                    break;
+                break;
 
-                case AUTH_INFO:
+            case AUTH_INFO:
 
-                    if (localName.equals("authorize")) {
+                if (localName.equals("authorize")) {
 
-                        // Finalize mapping for this user
-                        authMapping.put(
-                            current.auth_username,
-                            current
-                        );
+                    // Finalize mapping for this user
+                    authMapping.put(
+                        current.auth_username,
+                        current
+                    );
 
-                        state = State.USER_MAPPING;
-                        return;
-                    }
+                    state = State.USER_MAPPING;
+                    return;
+                }
 
-                    break;
+                break;
+                
+            case REMOTE_SERVER:
 
-                case PROTOCOL:
+                if (localName.equals("remote-server")) {
+                    state = State.AUTH_INFO;
+                    return;
+                }
 
-                    if (localName.equals("protocol")) {
-                        state = State.AUTH_INFO;
-                        return;
-                    }
+                break;                
 
-                    break;
+            case PROTOCOL:
 
-                case PARAMETER:
+                if (localName.equals("protocol")) {
+                    state = State.REMOTE_SERVER;
+                    return;
+                }
 
-                    if (localName.equals("param")) {
-                        state = State.AUTH_INFO;
-                        return;
-                    }
+                break;
 
-                    break;
+            case PARAMETER:
 
-            }
+                if (localName.equals("param")) {
+                    state = State.REMOTE_SERVER;
+                    return;
+                }
+
+                break;
+
+        }
 
             throw new SAXException("Tag not yet complete: " + localName);
 
@@ -274,7 +340,6 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
 
                     break;
 
-                // Only <authorize> tags allowed in main document
                 case USER_MAPPING:
 
                     if (localName.equals("authorize")) {
@@ -306,6 +371,23 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
 
                 case AUTH_INFO:
 
+                    if (localName.equals("remote-server")) {
+
+                        currentRemoteServer = attributes.getValue("servername");
+                        if (currentRemoteServer == null)
+                            throw new SAXException("Attribute \"servername\" required for param tag.");
+                        
+                        current.addConfiguration(currentRemoteServer);
+                        
+                        // Next state
+                        state = State.REMOTE_SERVER;
+                        return;
+                    }
+
+                    break;
+                    
+                case REMOTE_SERVER:
+
                     if (localName.equals("protocol")) {
                         // Next state
                         state = State.PROTOCOL;
@@ -323,7 +405,7 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
                         return;
                     }
 
-                    break;
+                    break;                   
 
             }
 
@@ -335,14 +417,17 @@ public class BasicFileAuthenticationProvider implements BasicLogin.Authenticatio
         public void characters(char[] ch, int start, int length) throws SAXException {
 
             String str = new String(ch, start, length);
+   
             switch (state) {
 
                 case PROTOCOL:
-                    current.getConfiguration().setProtocol(str);
+                    current.getConfiguration(currentRemoteServer)
+                        .setProtocol(str);
                     return;
 
                 case PARAMETER:
-                    current.getConfiguration().setParameter(currentParameter, str);
+                    current.getConfiguration(currentRemoteServer)
+                            .setParameter(currentParameter, str);
                     return;
                 
             }