diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java index 5b3552fe9..f272f43c1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionModel.java @@ -41,7 +41,21 @@ public class ConnectionModel extends GroupedObjectModel { * The name of the protocol to use when connecting to this connection. */ private String protocol; - + + /** + * The maximum number of connections that can be established to this + * connection concurrently, zero if no restriction applies, or null if the + * default restrictions should be applied. + */ + private Integer maxConnections; + + /** + * The maximum number of connections that can be established to this + * connection concurrently by any one user, zero if no restriction applies, + * or null if the default restrictions should be applied. + */ + private Integer maxConnectionsPerUser; + /** * Creates a new, empty connection. */ @@ -89,6 +103,58 @@ public class ConnectionModel extends GroupedObjectModel { this.protocol = protocol; } + /** + * Returns the maximum number of connections that can be established to + * this connection concurrently. + * + * @return + * The maximum number of connections that can be established to this + * connection concurrently, zero if no restriction applies, or null if + * the default restrictions should be applied. + */ + public Integer getMaxConnections() { + return maxConnections; + } + + /** + * Sets the maximum number of connections that can be established to this + * connection concurrently. + * + * @param maxConnections + * The maximum number of connections that can be established to this + * connection concurrently, zero if no restriction applies, or null if + * the default restrictions should be applied. + */ + public void setMaxConnections(Integer maxConnections) { + this.maxConnections = maxConnections; + } + + /** + * Returns the maximum number of connections that can be established to + * this connection concurrently by any one user. + * + * @return + * The maximum number of connections that can be established to this + * connection concurrently by any one user, zero if no restriction + * applies, or null if the default restrictions should be applied. + */ + public Integer getMaxConnectionsPerUser() { + return maxConnectionsPerUser; + } + + /** + * Sets the maximum number of connections that can be established to this + * connection concurrently by any one user. + * + * @param maxConnectionsPerUser + * The maximum number of connections that can be established to this + * connection concurrently by any one user, zero if no restriction + * applies, or null if the default restrictions should be applied. + */ + public void setMaxConnectionsPerUser(Integer maxConnectionsPerUser) { + this.maxConnectionsPerUser = maxConnectionsPerUser; + } + @Override public String getIdentifier() { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ModeledConnection.java index dad6c69dd..3a1bb2ff5 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ModeledConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connection/ModeledConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,17 +24,25 @@ package org.glyptodon.guacamole.auth.jdbc.connection; import com.google.inject.Inject; import com.google.inject.Provider; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.auth.jdbc.base.ModeledGroupedDirectoryObject; +import org.glyptodon.guacamole.form.Field; +import org.glyptodon.guacamole.form.Form; +import org.glyptodon.guacamole.form.NumericField; import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An implementation of the Connection object which is backed by a database @@ -46,6 +54,40 @@ import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; public class ModeledConnection extends ModeledGroupedDirectoryObject implements Connection { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ModeledConnection.class); + + /** + * The name of the attribute which controls the maximum number of + * concurrent connections. + */ + public static final String MAX_CONNECTIONS_NAME = "max-connections"; + + /** + * The name of the attribute which controls the maximum number of + * concurrent connections per user. + */ + public static final String MAX_CONNECTIONS_PER_USER_NAME = "max-connections-per-user"; + + /** + * All attributes related to restricting user accounts, within a logical + * form. + */ + public static final Form CONCURRENCY_LIMITS = new Form("concurrency", Arrays.asList( + new NumericField(MAX_CONNECTIONS_NAME), + new NumericField(MAX_CONNECTIONS_PER_USER_NAME) + )); + + /** + * All possible attributes of connection objects organized as individual, + * logical forms. + */ + public static final Collection
ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList( + CONCURRENCY_LIMITS + )); + /** * Service for managing connections. */ @@ -127,12 +169,35 @@ public class ModeledConnection extends ModeledGroupedDirectoryObject getAttributes() { - return Collections.emptyMap(); + + Map attributes = new HashMap(); + + // Set connection limit attribute + attributes.put(MAX_CONNECTIONS_NAME, NumericField.format(getModel().getMaxConnections())); + + // Set per-user connection limit attribute + attributes.put(MAX_CONNECTIONS_PER_USER_NAME, NumericField.format(getModel().getMaxConnectionsPerUser())); + + return attributes; } @Override public void setAttributes(Map attributes) { - // Drop all attributes - none currently supported + + // Translate connection limit attribute + try { getModel().setMaxConnections(NumericField.parse(attributes.get(MAX_CONNECTIONS_NAME))); } + catch (NumberFormatException e) { + logger.warn("Not setting maximum connections: {}", e.getMessage()); + logger.debug("Unable to parse numeric attribute.", e); + } + + // Translate per-user connection limit attribute + try { getModel().setMaxConnectionsPerUser(NumericField.parse(attributes.get(MAX_CONNECTIONS_PER_USER_NAME))); } + catch (NumberFormatException e) { + logger.warn("Not setting maximum connections per user: {}", e.getMessage()); + logger.debug("Unable to parse numeric attribute.", e); + } + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java index def543ffc..161f133ed 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupModel.java @@ -42,7 +42,21 @@ public class ConnectionGroupModel extends GroupedObjectModel { * The type of this connection group, such as organizational or balancing. */ private ConnectionGroup.Type type; - + + /** + * The maximum number of connections that can be established to this + * connection group concurrently, zero if no restriction applies, or + * null if the default restrictions should be applied. + */ + private Integer maxConnections; + + /** + * The maximum number of connections that can be established to this + * connection group concurrently by any one user, zero if no restriction + * applies, or null if the default restrictions should be applied. + */ + private Integer maxConnectionsPerUser; + /** * Creates a new, empty connection group. */ @@ -91,6 +105,60 @@ public class ConnectionGroupModel extends GroupedObjectModel { this.type = type; } + /** + * Returns the maximum number of connections that can be established to + * this connection group concurrently. + * + * @return + * The maximum number of connections that can be established to this + * connection group concurrently, zero if no restriction applies, or + * null if the default restrictions should be applied. + */ + public Integer getMaxConnections() { + return maxConnections; + } + + /** + * Sets the maximum number of connections that can be established to this + * connection group concurrently. + * + * @param maxConnections + * The maximum number of connections that can be established to this + * connection group concurrently, zero if no restriction applies, or + * null if the default restrictions should be applied. + */ + public void setMaxConnections(Integer maxConnections) { + this.maxConnections = maxConnections; + } + + /** + * Returns the maximum number of connections that can be established to + * this connection group concurrently by any one user. + * + * @return + * The maximum number of connections that can be established to this + * connection group concurrently by any one user, zero if no + * restriction applies, or null if the default restrictions should be + * applied. + */ + public Integer getMaxConnectionsPerUser() { + return maxConnectionsPerUser; + } + + /** + * Sets the maximum number of connections that can be established to this + * connection group concurrently by any one user. + * + * @param maxConnectionsPerUser + * The maximum number of connections that can be established to this + * connection group concurrently by any one user, zero if no + * restriction applies, or null if the default restrictions should be + * applied. + */ + public void setMaxConnectionsPerUser(Integer maxConnectionsPerUser) { + this.maxConnectionsPerUser = maxConnectionsPerUser; + } + @Override public String getIdentifier() { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java index 5d46147f5..8e1403fb2 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,26 +23,69 @@ package org.glyptodon.guacamole.auth.jdbc.connectiongroup; import com.google.inject.Inject; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Set; import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionService; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.auth.jdbc.base.ModeledGroupedDirectoryObject; +import org.glyptodon.guacamole.form.Field; +import org.glyptodon.guacamole.form.Form; +import org.glyptodon.guacamole.form.NumericField; import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An implementation of the ConnectionGroup object which is backed by a * database model. * * @author James Muehlner + * @author Michael Jumper */ public class ModeledConnectionGroup extends ModeledGroupedDirectoryObject implements ConnectionGroup { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(ModeledConnectionGroup.class); + + /** + * The name of the attribute which controls the maximum number of + * concurrent connections. + */ + public static final String MAX_CONNECTIONS_NAME = "max-connections"; + + /** + * The name of the attribute which controls the maximum number of + * concurrent connections per user. + */ + public static final String MAX_CONNECTIONS_PER_USER_NAME = "max-connections-per-user"; + + /** + * All attributes related to restricting user accounts, within a logical + * form. + */ + public static final Form CONCURRENCY_LIMITS = new Form("concurrency", Arrays.asList( + new NumericField(MAX_CONNECTIONS_NAME), + new NumericField(MAX_CONNECTIONS_PER_USER_NAME) + )); + + /** + * All possible attributes of connection group objects organized as + * individual, logical forms. + */ + public static final Collection ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList( + CONCURRENCY_LIMITS + )); + /** * Service for managing connections. */ @@ -112,12 +155,35 @@ public class ModeledConnectionGroup extends ModeledGroupedDirectoryObject getAttributes() { - return Collections.emptyMap(); + + Map attributes = new HashMap(); + + // Set connection limit attribute + attributes.put(MAX_CONNECTIONS_NAME, NumericField.format(getModel().getMaxConnections())); + + // Set per-user connection limit attribute + attributes.put(MAX_CONNECTIONS_PER_USER_NAME, NumericField.format(getModel().getMaxConnectionsPerUser())); + + return attributes; } @Override public void setAttributes(Map attributes) { - // Drop all attributes - none currently supported + + // Translate connection limit attribute + try { getModel().setMaxConnections(NumericField.parse(attributes.get(MAX_CONNECTIONS_NAME))); } + catch (NumberFormatException e) { + logger.warn("Not setting maximum connections: {}", e.getMessage()); + logger.debug("Unable to parse numeric attribute.", e); + } + + // Translate per-user connection limit attribute + try { getModel().setMaxConnectionsPerUser(NumericField.parse(attributes.get(MAX_CONNECTIONS_PER_USER_NAME))); } + catch (NumberFormatException e) { + logger.warn("Not setting maximum connections per user: {}", e.getMessage()); + logger.debug("Unable to parse numeric attribute.", e); + } + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java index a3f5b6d0e..3b5e8b54d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java @@ -160,7 +160,7 @@ public class ConfigurableGuacamoleTunnelService int count = multiset.count(value); // Bail out if the maximum has already been reached - if (count >= max || max == 0) + if (count >= max && max != 0) return false; // Attempt to add one more value @@ -200,14 +200,23 @@ public class ConfigurableGuacamoleTunnelService // Return the first unreserved connection for (ModeledConnection connection : sortedConnections) { + // Determine per-user limits on this connection + Integer connectionMaxConnectionsPerUser = connection.getModel().getMaxConnectionsPerUser(); + if (connectionMaxConnectionsPerUser == null) + connectionMaxConnectionsPerUser = connectionDefaultMaxConnectionsPerUser; + + // Determine overall limits on this connection + Integer connectionMaxConnections = connection.getModel().getMaxConnections(); + if (connectionMaxConnections == null) + connectionMaxConnections = connectionDefaultMaxConnections; + // Attempt to aquire connection according to per-user limits Seat seat = new Seat(username, connection.getIdentifier()); - if (tryAdd(activeSeats, seat, - connectionDefaultMaxConnectionsPerUser)) { + if (tryAdd(activeSeats, seat, connectionMaxConnectionsPerUser)) { // Attempt to aquire connection according to overall limits if (tryAdd(activeConnections, connection.getIdentifier(), - connectionDefaultMaxConnections)) + connectionMaxConnections)) return connection; // Acquire failed - retry with next connection @@ -222,7 +231,7 @@ public class ConfigurableGuacamoleTunnelService // Too many connections by this user if (userSpecificFailure) - throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user."); + throw new GuacamoleClientTooManyException("Cannot connect. Connection already in use by this user."); // Too many connections, but not necessarily due purely to this user else @@ -243,14 +252,24 @@ public class ConfigurableGuacamoleTunnelService // Get username String username = user.getUser().getIdentifier(); + // Determine per-user limits on this connection group + Integer connectionGroupMaxConnectionsPerUser = connectionGroup.getModel().getMaxConnectionsPerUser(); + if (connectionGroupMaxConnectionsPerUser == null) + connectionGroupMaxConnectionsPerUser = connectionGroupDefaultMaxConnectionsPerUser; + + // Determine overall limits on this connection group + Integer connectionGroupMaxConnections = connectionGroup.getModel().getMaxConnections(); + if (connectionGroupMaxConnections == null) + connectionGroupMaxConnections = connectionGroupDefaultMaxConnections; + // Attempt to aquire connection group according to per-user limits Seat seat = new Seat(username, connectionGroup.getIdentifier()); if (tryAdd(activeGroupSeats, seat, - connectionGroupDefaultMaxConnectionsPerUser)) { + connectionGroupMaxConnectionsPerUser)) { // Attempt to aquire connection group according to overall limits if (tryAdd(activeGroups, connectionGroup.getIdentifier(), - connectionGroupDefaultMaxConnections)) + connectionGroupMaxConnections)) return; // Acquire failed diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java index 99bca4850..586686b67 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java @@ -25,9 +25,7 @@ package org.glyptodon.guacamole.auth.jdbc.user; import com.google.inject.Inject; import java.sql.Date; import java.sql.Time; -import java.text.DateFormat; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; @@ -268,40 +266,39 @@ public class ModeledUser extends ModeledDirectoryObject implements Us return userPermissionService.getPermissionSet(getCurrentUser(), this); } - /** - * Converts the given date into a string which follows the format used by - * date attributes. - * - * @param date - * The date value to format, which may be null. - * - * @return - * The formatted date, or null if the provided time was null. - */ - private String formatDate(Date date) { - DateFormat dateFormat = new SimpleDateFormat(DateField.FORMAT); - return date == null ? null : dateFormat.format(date); - } + @Override + public Map getAttributes() { - /** - * Converts the given time into a string which follows the format used by - * time attributes. - * - * @param time - * The time value to format, which may be null. - * - * @return - * The formatted time, or null if the provided time was null. - */ - private String formatTime(Time time) { - DateFormat timeFormat = new SimpleDateFormat(TimeField.FORMAT); - return time == null ? null : timeFormat.format(time); + Map attributes = new HashMap(); + + // Set disabled attribute + attributes.put(DISABLED_ATTRIBUTE_NAME, getModel().isDisabled() ? "true" : null); + + // Set password expired attribute + attributes.put(EXPIRED_ATTRIBUTE_NAME, getModel().isExpired() ? "true" : null); + + // Set access window start time + attributes.put(ACCESS_WINDOW_START_ATTRIBUTE_NAME, TimeField.format(getModel().getAccessWindowStart())); + + // Set access window end time + attributes.put(ACCESS_WINDOW_END_ATTRIBUTE_NAME, TimeField.format(getModel().getAccessWindowEnd())); + + // Set account validity start date + attributes.put(VALID_FROM_ATTRIBUTE_NAME, DateField.format(getModel().getValidFrom())); + + // Set account validity end date + attributes.put(VALID_UNTIL_ATTRIBUTE_NAME, DateField.format(getModel().getValidUntil())); + + // Set timezone attribute + attributes.put(TIMEZONE_ATTRIBUTE_NAME, getModel().getTimeZone()); + + return attributes; } /** * Parses the given string into a corresponding date. The string must * follow the standard format used by date attributes, as defined by - * DATE_FORMAT and as would be produced by formatDate(). + * DateField.FORMAT and as would be produced by DateField.format(). * * @param dateString * The date string to parse, which may be null. @@ -318,19 +315,19 @@ public class ModeledUser extends ModeledDirectoryObject implements Us throws ParseException { // Return null if no date provided - if (dateString == null || dateString.isEmpty()) + java.util.Date parsedDate = DateField.parse(dateString); + if (parsedDate == null) return null; - // Parse date according to format - DateFormat dateFormat = new SimpleDateFormat(DateField.FORMAT); - return new Date(dateFormat.parse(dateString).getTime()); + // Convert to SQL Date + return new Date(parsedDate.getTime()); } /** * Parses the given string into a corresponding time. The string must * follow the standard format used by time attributes, as defined by - * TIME_FORMAT and as would be produced by formatTime(). + * TimeField.FORMAT and as would be produced by TimeField.format(). * * @param timeString * The time string to parse, which may be null. @@ -347,67 +344,15 @@ public class ModeledUser extends ModeledDirectoryObject implements Us throws ParseException { // Return null if no time provided - if (timeString == null || timeString.isEmpty()) + java.util.Date parsedDate = TimeField.parse(timeString); + if (parsedDate == null) return null; - // Parse time according to format - DateFormat timeFormat = new SimpleDateFormat(TimeField.FORMAT); - return new Time(timeFormat.parse(timeString).getTime()); + // Convert to SQL Time + return new Time(parsedDate.getTime()); } - /** - * Parses the given string into a time zone ID string. As these strings are - * equivalent, the only transformation currently performed by this function - * is to ensure that a blank time zone string is parsed into null. - * - * @param timeZone - * The time zone string to parse, which may be null. - * - * @return - * The ID of the time zone corresponding to the given string, or null - * if the given time zone string was null or blank. - */ - private String parseTimeZone(String timeZone) { - - // Return null if no time zone provided - if (timeZone == null || timeZone.isEmpty()) - return null; - - // Otherwise, assume time zone is valid - return timeZone; - - } - - @Override - public Map getAttributes() { - - Map attributes = new HashMap(); - - // Set disabled attribute - attributes.put(DISABLED_ATTRIBUTE_NAME, getModel().isDisabled() ? "true" : null); - - // Set password expired attribute - attributes.put(EXPIRED_ATTRIBUTE_NAME, getModel().isExpired() ? "true" : null); - - // Set access window start time - attributes.put(ACCESS_WINDOW_START_ATTRIBUTE_NAME, formatTime(getModel().getAccessWindowStart())); - - // Set access window end time - attributes.put(ACCESS_WINDOW_END_ATTRIBUTE_NAME, formatTime(getModel().getAccessWindowEnd())); - - // Set account validity start date - attributes.put(VALID_FROM_ATTRIBUTE_NAME, formatDate(getModel().getValidFrom())); - - // Set account validity end date - attributes.put(VALID_UNTIL_ATTRIBUTE_NAME, formatDate(getModel().getValidUntil())); - - // Set timezone attribute - attributes.put(TIMEZONE_ATTRIBUTE_NAME, getModel().getTimeZone()); - - return attributes; - } - @Override public void setAttributes(Map attributes) { @@ -446,7 +391,7 @@ public class ModeledUser extends ModeledDirectoryObject implements Us } // Translate timezone attribute - getModel().setTimeZone(parseTimeZone(attributes.get(TIMEZONE_ATTRIBUTE_NAME))); + getModel().setTimeZone(TimeZoneField.parse(attributes.get(TIMEZONE_ATTRIBUTE_NAME))); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java index 41c61e340..12676927a 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java @@ -29,10 +29,11 @@ import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionDirectory; import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Collection; -import java.util.Collections; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.auth.jdbc.base.RestrictedObject; import org.glyptodon.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory; +import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; +import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; import org.glyptodon.guacamole.form.Form; import org.glyptodon.guacamole.net.auth.ActiveConnection; import org.glyptodon.guacamole.net.auth.Connection; @@ -140,12 +141,12 @@ public class UserContext extends RestrictedObject @Override public Collection getConnectionAttributes() { - return Collections.emptyList(); + return ModeledConnection.ATTRIBUTES; } @Override public Collection getConnectionGroupAttributes() { - return Collections.emptyList(); + return ModeledConnectionGroup.ATTRIBUTES; } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json index b11c15741..1b631c993 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/resources/translations/en.json @@ -15,6 +15,24 @@ }, + "CONNECTION_ATTRIBUTES" : { + + "FIELD_HEADER_MAX_CONNECTIONS" : "Maximum number of connections:", + "FIELD_HEADER_MAX_CONNECTIONS_PER_USER" : "Maximum number of connections per user:", + + "SECTION_HEADER_CONCURRENCY" : "Concurrency Limits" + + }, + + "CONNECTION_GROUP_ATTRIBUTES" : { + + "FIELD_HEADER_MAX_CONNECTIONS" : "Maximum number of connections:", + "FIELD_HEADER_MAX_CONNECTIONS_PER_USER" : "Maximum number of connections per user:", + + "SECTION_HEADER_CONCURRENCY" : "Concurrency Limits (Balancing Groups)" + + }, + "USER_ATTRIBUTES" : { "FIELD_HEADER_DISABLED" : "Login disabled:", diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql index 9daf3a63e..8ebda4c4b 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/001-create-schema.sql @@ -32,6 +32,10 @@ CREATE TABLE `guacamole_connection_group` ( `type` enum('ORGANIZATIONAL', 'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL', + -- Concurrency limits + `max_connections` int(11), + `max_connections_per_user` int(11), + PRIMARY KEY (`connection_group_id`), UNIQUE KEY `connection_group_name_parent` (`connection_group_name`, `parent_id`), @@ -54,6 +58,10 @@ CREATE TABLE `guacamole_connection` ( `parent_id` int(11), `protocol` varchar(32) NOT NULL, + -- Concurrency limits + `max_connections` int(11), + `max_connections_per_user` int(11), + PRIMARY KEY (`connection_id`), UNIQUE KEY `connection_name_parent` (`connection_name`, `parent_id`), diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.8.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.8.sql index 2d417db51..1f393f562 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.8.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/schema/upgrade/upgrade-pre-0.9.8.sql @@ -39,3 +39,17 @@ ALTER TABLE guacamole_user ADD COLUMN valid_until DATE; -- ALTER TABLE guacamole_user ADD COLUMN timezone VARCHAR(64); + +-- +-- Add connection concurrency limits +-- + +ALTER TABLE guacamole_connection ADD COLUMN max_connections INT(11); +ALTER TABLE guacamole_connection ADD COLUMN max_connections_per_user INT(11); + +-- +-- Add connection group concurrency limits +-- + +ALTER TABLE guacamole_connection_group ADD COLUMN max_connections INT(11); +ALTER TABLE guacamole_connection_group ADD COLUMN max_connections_per_user INT(11); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml index 2211da069..8c85bf1a6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -28,10 +28,12 @@ - - - - + + + + + + @@ -77,7 +79,9 @@ connection_id, connection_name, parent_id, - protocol + protocol, + max_connections, + max_connections_per_user FROM guacamole_connection WHERE connection_id IN parent_id = #{parentIdentifier,jdbcType=VARCHAR} @@ -136,12 +144,16 @@ INSERT INTO guacamole_connection ( connection_name, parent_id, - protocol + protocol, + max_connections, + max_connections_per_user ) VALUES ( #{object.name,jdbcType=VARCHAR}, #{object.parentIdentifier,jdbcType=VARCHAR}, - #{object.protocol,jdbcType=VARCHAR} + #{object.protocol,jdbcType=VARCHAR}, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER} ) @@ -149,9 +161,11 @@ UPDATE guacamole_connection - SET connection_name = #{object.name,jdbcType=VARCHAR}, - parent_id = #{object.parentIdentifier,jdbcType=VARCHAR}, - protocol = #{object.protocol,jdbcType=VARCHAR} + SET connection_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=VARCHAR}, + protocol = #{object.protocol,jdbcType=VARCHAR}, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER} WHERE connection_id = #{object.objectID,jdbcType=INTEGER} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml index 4eb20da1c..75acaab42 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml @@ -28,11 +28,13 @@ - - - - + + + + + @@ -78,7 +80,9 @@ connection_group_id, connection_group_name, parent_id, - type + type, + max_connections, + max_connections_per_user FROM guacamole_connection_group WHERE connection_group_id IN parent_id = #{parentIdentifier,jdbcType=VARCHAR} @@ -137,12 +145,16 @@ INSERT INTO guacamole_connection_group ( connection_group_name, parent_id, - type + type, + max_connections, + max_connections_per_user ) VALUES ( #{object.name,jdbcType=VARCHAR}, #{object.parentIdentifier,jdbcType=VARCHAR}, - #{object.type,jdbcType=VARCHAR} + #{object.type,jdbcType=VARCHAR}, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER} ) @@ -150,9 +162,11 @@ UPDATE guacamole_connection_group - SET connection_group_name = #{object.name,jdbcType=VARCHAR}, - parent_id = #{object.parentIdentifier,jdbcType=VARCHAR}, - type = #{object.type,jdbcType=VARCHAR} + SET connection_group_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=VARCHAR}, + type = #{object.type,jdbcType=VARCHAR}, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER} WHERE connection_group_id = #{object.objectID,jdbcType=INTEGER} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql index dd5e69887..8f90d4cbf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql @@ -63,6 +63,10 @@ CREATE TABLE guacamole_connection_group ( type guacamole_connection_group_type NOT NULL DEFAULT 'ORGANIZATIONAL', + -- Concurrency limits + max_connections integer, + max_connections_per_user integer, + PRIMARY KEY (connection_group_id), CONSTRAINT connection_group_name_parent @@ -90,6 +94,10 @@ CREATE TABLE guacamole_connection ( parent_id integer, protocol varchar(32) NOT NULL, + -- Concurrency limits + max_connections integer, + max_connections_per_user integer, + PRIMARY KEY (connection_id), CONSTRAINT connection_name_parent diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.8.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.8.sql index be2d36898..da4cff4f7 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.8.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.8.sql @@ -39,3 +39,17 @@ ALTER TABLE guacamole_user ADD COLUMN valid_until date; -- ALTER TABLE guacamole_user ADD COLUMN timezone varchar(64); + +-- +-- Add connection concurrency limits +-- + +ALTER TABLE guacamole_connection ADD COLUMN max_connections integer; +ALTER TABLE guacamole_connection ADD COLUMN max_connections_per_user integer; + +-- +-- Add connection group concurrency limits +-- + +ALTER TABLE guacamole_connection_group ADD COLUMN max_connections integer; +ALTER TABLE guacamole_connection_group ADD COLUMN max_connections_per_user integer; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml index 11d18a9b4..7bc96e425 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connection/ConnectionMapper.xml @@ -28,10 +28,12 @@ - - - - + + + + + + @@ -77,7 +79,9 @@ connection_id, connection_name, parent_id, - protocol + protocol, + max_connections, + max_connections_per_user FROM guacamole_connection WHERE connection_id IN parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer @@ -136,12 +144,16 @@ INSERT INTO guacamole_connection ( connection_name, parent_id, - protocol + protocol, + max_connections, + max_connections_per_user ) VALUES ( #{object.name,jdbcType=VARCHAR}, #{object.parentIdentifier,jdbcType=INTEGER}::integer, - #{object.protocol,jdbcType=VARCHAR} + #{object.protocol,jdbcType=VARCHAR}, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER} ) @@ -149,9 +161,11 @@ UPDATE guacamole_connection - SET connection_name = #{object.name,jdbcType=VARCHAR}, - parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer, - protocol = #{object.protocol,jdbcType=VARCHAR} + SET connection_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer, + protocol = #{object.protocol,jdbcType=VARCHAR}, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER} WHERE connection_id = #{object.objectID,jdbcType=INTEGER}::integer diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml index 3acf3731f..75005a663 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/connectiongroup/ConnectionGroupMapper.xml @@ -28,11 +28,13 @@ - - - - + + + + + @@ -78,7 +80,9 @@ connection_group_id, connection_group_name, parent_id, - type + type, + max_connections, + max_connections_per_user FROM guacamole_connection_group WHERE connection_group_id IN parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer @@ -137,12 +145,16 @@ INSERT INTO guacamole_connection_group ( connection_group_name, parent_id, - type + type, + max_connections, + max_connections_per_user ) VALUES ( #{object.name,jdbcType=VARCHAR}, #{object.parentIdentifier,jdbcType=INTEGER}::integer, - #{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type + #{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type, + #{object.maxConnections,jdbcType=INTEGER}, + #{object.maxConnectionsPerUser,jdbcType=INTEGER} ) @@ -150,9 +162,11 @@ UPDATE guacamole_connection_group - SET connection_group_name = #{object.name,jdbcType=VARCHAR}, - parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer, - type = #{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type + SET connection_group_name = #{object.name,jdbcType=VARCHAR}, + parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer, + type = #{object.type,jdbcType=VARCHAR}::guacamole_connection_group_type, + max_connections = #{object.maxConnections,jdbcType=INTEGER}, + max_connections_per_user = #{object.maxConnectionsPerUser,jdbcType=INTEGER} WHERE connection_group_id = #{object.objectID,jdbcType=INTEGER}::integer diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/DateField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/DateField.java index d27020bdb..20ff2575c 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/DateField.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/DateField.java @@ -22,6 +22,11 @@ package org.glyptodon.guacamole.form; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * Represents a date field. The field may contain only date values which * conform to a standard pattern, defined by DateField.FORMAT. @@ -45,4 +50,48 @@ public class DateField extends Field { super(name, Field.Type.DATE); } + /** + * Converts the given date into a string which follows the format used by + * date fields. + * + * @param date + * The date value to format, which may be null. + * + * @return + * The formatted date, or null if the provided time was null. + */ + public static String format(Date date) { + DateFormat dateFormat = new SimpleDateFormat(DateField.FORMAT); + return date == null ? null : dateFormat.format(date); + } + + /** + * Parses the given string into a corresponding date. The string must + * follow the standard format used by date fields, as defined by FORMAT + * and as would be produced by format(). + * + * @param dateString + * The date string to parse, which may be null. + * + * @return + * The date corresponding to the given date string, or null if the + * provided date string was null or blank. + * + * @throws ParseException + * If the given date string does not conform to the standard format + * used by date fields. + */ + public static Date parse(String dateString) + throws ParseException { + + // Return null if no date provided + if (dateString == null || dateString.isEmpty()) + return null; + + // Parse date according to format + DateFormat dateFormat = new SimpleDateFormat(DateField.FORMAT); + return dateFormat.parse(dateString); + + } + } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java index 5e06cb33e..12f78ed13 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java @@ -39,4 +39,50 @@ public class NumericField extends Field { super(name, Field.Type.NUMERIC); } + /** + * Formats the given integer in the format required by a numeric field. + * + * @param i + * The integer to format, which may be null. + * + * @return + * A string representation of the given integer, or null if the given + * integer was null. + */ + public static String format(Integer i) { + + // Return null if no value provided + if (i == null) + return null; + + // Convert to string + return i.toString(); + + } + + /** + * Parses the given string as an integer, where the given string is in the + * format required by a numeric field. + * + * @param str + * The string to parse as an integer, which may be null. + * + * @return + * The integer representation of the given string, or null if the given + * string was null. + * + * @throws NumberFormatException + * If the given string is not in a parseable format. + */ + public static Integer parse(String str) throws NumberFormatException { + + // Return null if no value provided + if (str == null || str.isEmpty()) + return null; + + // Parse as integer + return new Integer(str); + + } + } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeField.java index 15891deb2..3d7ee7d48 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeField.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeField.java @@ -22,6 +22,11 @@ package org.glyptodon.guacamole.form; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * Represents a time field. The field may contain only time values which * conform to a standard pattern, defined by TimeField.FORMAT. @@ -45,4 +50,48 @@ public class TimeField extends Field { super(name, Field.Type.TIME); } + /** + * Parses the given string into a corresponding time. The string must + * follow the standard format used by time fields, as defined by + * FORMAT and as would be produced by format(). + * + * @param timeString + * The time string to parse, which may be null. + * + * @return + * The time corresponding to the given time string, or null if the + * provided time string was null or blank. + * + * @throws ParseException + * If the given time string does not conform to the standard format + * used by time fields. + */ + public static Date parse(String timeString) + throws ParseException { + + // Return null if no time provided + if (timeString == null || timeString.isEmpty()) + return null; + + // Parse time according to format + DateFormat timeFormat = new SimpleDateFormat(TimeField.FORMAT); + return timeFormat.parse(timeString); + + } + + /** + * Converts the given time into a string which follows the format used by + * time fields. + * + * @param time + * The time value to format, which may be null. + * + * @return + * The formatted time, or null if the provided time was null. + */ + public static String format(Date time) { + DateFormat timeFormat = new SimpleDateFormat(TimeField.FORMAT); + return time == null ? null : timeFormat.format(time); + } + } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeZoneField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeZoneField.java index 5d07e918e..09a5e642e 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeZoneField.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TimeZoneField.java @@ -40,4 +40,27 @@ public class TimeZoneField extends Field { super(name, Field.Type.TIMEZONE); } + /** + * Parses the given string into a time zone ID string. As these strings are + * equivalent, the only transformation currently performed by this function + * is to ensure that a blank time zone string is parsed into null. + * + * @param timeZone + * The time zone string to parse, which may be null. + * + * @return + * The ID of the time zone corresponding to the given string, or null + * if the given time zone string was null or blank. + */ + public static String parse(String timeZone) { + + // Return null if no time zone provided + if (timeZone == null || timeZone.isEmpty()) + return null; + + // Otherwise, assume time zone is valid + return timeZone; + + } + } diff --git a/guacamole/src/main/webapp/app/form/controllers/numberFieldController.js b/guacamole/src/main/webapp/app/form/controllers/numberFieldController.js index fc213099e..640384f45 100644 --- a/guacamole/src/main/webapp/app/form/controllers/numberFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/numberFieldController.js @@ -34,7 +34,7 @@ angular.module('form').controller('numberFieldController', ['$scope', // Update string value in model when typed value is changed $scope.$watch('typedValue', function typedValueChanged(typedValue) { - $scope.model = (typedValue ? typedValue.toString() : ''); + $scope.model = ((typedValue || typedValue === 0) ? typedValue.toString() : ''); }); }]);