Merge pull request #248 from glyptodon/concurrent-policy

GUAC-830: Implement concurrency policy attributes
This commit is contained in:
James Muehlner
2015-08-21 21:58:38 -07:00
21 changed files with 674 additions and 159 deletions

View File

@@ -42,6 +42,20 @@ public class ConnectionModel extends GroupedObjectModel {
*/
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() {

View File

@@ -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<ConnectionModel>
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.<Field>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<Form> ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList(
CONCURRENCY_LIMITS
));
/**
* Service for managing connections.
*/
@@ -127,12 +169,35 @@ public class ModeledConnection extends ModeledGroupedDirectoryObject<ConnectionM
@Override
public Map<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
Map<String, String> attributes = new HashMap<String, String>();
// 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<String, String> 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);
}
}
}

View File

@@ -43,6 +43,20 @@ public class ConnectionGroupModel extends GroupedObjectModel {
*/
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() {

View File

@@ -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<ConnectionGroupModel>
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.<Field>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<Form> ATTRIBUTES = Collections.unmodifiableCollection(Arrays.asList(
CONCURRENCY_LIMITS
));
/**
* Service for managing connections.
*/
@@ -112,12 +155,35 @@ public class ModeledConnectionGroup extends ModeledGroupedDirectoryObject<Connec
@Override
public Map<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
Map<String, String> attributes = new HashMap<String, String>();
// 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<String, String> 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);
}
}
}

View File

@@ -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

View File

@@ -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<UserModel> 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<String, String> 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<String, String> attributes = new HashMap<String, String>();
// 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<UserModel> 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<UserModel> 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<String, String> getAttributes() {
Map<String, String> attributes = new HashMap<String, String>();
// 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<String, String> attributes) {
@@ -446,7 +391,7 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
}
// Translate timezone attribute
getModel().setTimeZone(parseTimeZone(attributes.get(TIMEZONE_ATTRIBUTE_NAME)));
getModel().setTimeZone(TimeZoneField.parse(attributes.get(TIMEZONE_ATTRIBUTE_NAME)));
}

View File

@@ -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<Form> getConnectionAttributes() {
return Collections.<Form>emptyList();
return ModeledConnection.ATTRIBUTES;
}
@Override
public Collection<Form> getConnectionGroupAttributes() {
return Collections.<Form>emptyList();
return ModeledConnectionGroup.ATTRIBUTES;
}
}

View File

@@ -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:",

View File

@@ -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`),

View File

@@ -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);

View File

@@ -32,6 +32,8 @@
<result column="connection_name" property="name" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="protocol" property="protocol" jdbcType="VARCHAR"/>
<result column="max_connections" property="maxConnections" jdbcType="INTEGER"/>
<result column="max_connections_per_user" property="maxConnectionsPerUser" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all connection identifiers -->
@@ -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
<foreach collection="identifiers" item="identifier"
@@ -94,7 +98,9 @@
guacamole_connection.connection_id,
connection_name,
parent_id,
protocol
protocol,
max_connections,
max_connections_per_user
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE guacamole_connection.connection_id IN
@@ -114,7 +120,9 @@
connection_id,
connection_name,
parent_id,
protocol
protocol,
max_connections,
max_connections_per_user
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=VARCHAR}</if>
@@ -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}
)
</insert>
@@ -151,7 +163,9 @@
UPDATE guacamole_connection
SET connection_name = #{object.name,jdbcType=VARCHAR},
parent_id = #{object.parentIdentifier,jdbcType=VARCHAR},
protocol = #{object.protocol,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}
</update>

View File

@@ -33,6 +33,8 @@
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="type" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.ConnectionGroup$Type"/>
<result column="max_connections" property="maxConnections" jdbcType="INTEGER"/>
<result column="max_connections_per_user" property="maxConnectionsPerUser" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all connection group identifiers -->
@@ -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
<foreach collection="identifiers" item="identifier"
@@ -95,7 +99,9 @@
guacamole_connection_group.connection_group_id,
connection_group_name,
parent_id,
type
type,
max_connections,
max_connections_per_user
FROM guacamole_connection_group
JOIN guacamole_connection_group_permission ON guacamole_connection_group_permission.connection_group_id = guacamole_connection_group.connection_group_id
WHERE guacamole_connection_group.connection_group_id IN
@@ -115,7 +121,9 @@
connection_group_id,
connection_group_name,
parent_id,
type
type,
max_connections,
max_connections_per_user
FROM guacamole_connection_group
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=VARCHAR}</if>
@@ -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}
)
</insert>
@@ -152,7 +164,9 @@
UPDATE guacamole_connection_group
SET connection_group_name = #{object.name,jdbcType=VARCHAR},
parent_id = #{object.parentIdentifier,jdbcType=VARCHAR},
type = #{object.type,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}
</update>

View File

@@ -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

View File

@@ -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;

View File

@@ -32,6 +32,8 @@
<result column="connection_name" property="name" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="protocol" property="protocol" jdbcType="VARCHAR"/>
<result column="max_connections" property="maxConnections" jdbcType="INTEGER"/>
<result column="max_connections_per_user" property="maxConnectionsPerUser" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all connection identifiers -->
@@ -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
<foreach collection="identifiers" item="identifier"
@@ -94,7 +98,9 @@
guacamole_connection.connection_id,
connection_name,
parent_id,
protocol
protocol,
max_connections,
max_connections_per_user
FROM guacamole_connection
JOIN guacamole_connection_permission ON guacamole_connection_permission.connection_id = guacamole_connection.connection_id
WHERE guacamole_connection.connection_id IN
@@ -114,7 +120,9 @@
connection_id,
connection_name,
parent_id,
protocol
protocol,
max_connections,
max_connections_per_user
FROM guacamole_connection
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
@@ -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}
)
</insert>
@@ -151,7 +163,9 @@
UPDATE guacamole_connection
SET connection_name = #{object.name,jdbcType=VARCHAR},
parent_id = #{object.parentIdentifier,jdbcType=INTEGER}::integer,
protocol = #{object.protocol,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}::integer
</update>

View File

@@ -33,6 +33,8 @@
<result column="parent_id" property="parentIdentifier" jdbcType="INTEGER"/>
<result column="type" property="type" jdbcType="VARCHAR"
javaType="org.glyptodon.guacamole.net.auth.ConnectionGroup$Type"/>
<result column="max_connections" property="maxConnections" jdbcType="INTEGER"/>
<result column="max_connections_per_user" property="maxConnectionsPerUser" jdbcType="INTEGER"/>
</resultMap>
<!-- Select all connection group identifiers -->
@@ -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
<foreach collection="identifiers" item="identifier"
@@ -95,7 +99,9 @@
guacamole_connection_group.connection_group_id,
connection_group_name,
parent_id,
type
type,
max_connections,
max_connections_per_user
FROM guacamole_connection_group
JOIN guacamole_connection_group_permission ON guacamole_connection_group_permission.connection_group_id = guacamole_connection_group.connection_group_id
WHERE guacamole_connection_group.connection_group_id IN
@@ -115,7 +121,9 @@
connection_group_id,
connection_group_name,
parent_id,
type
type,
max_connections,
max_connections_per_user
FROM guacamole_connection_group
WHERE
<if test="parentIdentifier != null">parent_id = #{parentIdentifier,jdbcType=INTEGER}::integer</if>
@@ -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}
)
</insert>
@@ -152,7 +164,9 @@
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
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
</update>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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() : '');
});
}]);