Merge pull request #236 from glyptodon/date-time-restrictions

GUAC-1213: Add date/time restrictions for user accounts.
This commit is contained in:
James Muehlner
2015-07-31 14:26:43 -07:00
10 changed files with 809 additions and 28 deletions

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,11 +23,18 @@
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;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import org.glyptodon.guacamole.auth.jdbc.base.ModeledDirectoryObject;
import org.glyptodon.guacamole.auth.jdbc.security.PasswordEncryptionService;
import org.glyptodon.guacamole.auth.jdbc.security.SaltService;
@@ -40,10 +47,13 @@ import org.glyptodon.guacamole.auth.jdbc.permission.UserPermissionService;
import org.glyptodon.guacamole.form.BooleanField;
import org.glyptodon.guacamole.form.Field;
import org.glyptodon.guacamole.form.Form;
import org.glyptodon.guacamole.form.TextField;
import org.glyptodon.guacamole.net.auth.User;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An implementation of the User object which is backed by a database model.
@@ -53,6 +63,21 @@ import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
*/
public class ModeledUser extends ModeledDirectoryObject<UserModel> implements User {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(ModeledUser.class);
/**
* The format to use for all date attributes associated with users.
*/
private static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* The format to use for all time attributes associated with users.
*/
private static final String TIME_FORMAT = "HH:mm:ss";
/**
* The name of the attribute which controls whether a user account is
* disabled.
@@ -65,13 +90,48 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
*/
public static final String EXPIRED_ATTRIBUTE_NAME = "expired";
/**
* The name of the attribute which controls the time of day after which a
* user may login.
*/
public static final String ACCESS_WINDOW_START_ATTRIBUTE_NAME = "access-window-start";
/**
* The name of the attribute which controls the time of day after which a
* user may NOT login.
*/
public static final String ACCESS_WINDOW_END_ATTRIBUTE_NAME = "access-window-end";
/**
* The name of the attribute which controls the date after which a user's
* account is valid.
*/
public static final String VALID_FROM_ATTRIBUTE_NAME = "valid-from";
/**
* The name of the attribute which controls the date after which a user's
* account is no longer valid.
*/
public static final String VALID_UNTIL_ATTRIBUTE_NAME = "valid-until";
/**
* The name of the attribute which defines the time zone used for all
* time and date attributes related to this user.
*/
public static final String TIMEZONE_ATTRIBUTE_NAME = "timezone";
/**
* All attributes related to restricting user accounts, within a logical
* form.
*/
public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", Arrays.<Field>asList(
new BooleanField(DISABLED_ATTRIBUTE_NAME, "true"),
new BooleanField(EXPIRED_ATTRIBUTE_NAME, "true")
new BooleanField(EXPIRED_ATTRIBUTE_NAME, "true"),
new TextField(ACCESS_WINDOW_START_ATTRIBUTE_NAME),
new TextField(ACCESS_WINDOW_END_ATTRIBUTE_NAME),
new TextField(VALID_FROM_ATTRIBUTE_NAME),
new TextField(VALID_UNTIL_ATTRIBUTE_NAME),
new TextField(TIMEZONE_ATTRIBUTE_NAME)
));
/**
@@ -216,6 +276,117 @@ 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(DATE_FORMAT);
return date == null ? null : dateFormat.format(date);
}
/**
* 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(TIME_FORMAT);
return time == null ? null : timeFormat.format(time);
}
/**
* 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().
*
* @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 attributes.
*/
private Date parseDate(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(DATE_FORMAT);
return new Date(dateFormat.parse(dateString).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().
*
* @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 attributes.
*/
private Time parseTime(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(TIME_FORMAT);
return new Time(timeFormat.parse(timeString).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() {
@@ -227,6 +398,21 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
// 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;
}
@@ -239,6 +425,237 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us
// Translate password expired attribute
getModel().setExpired("true".equals(attributes.get(EXPIRED_ATTRIBUTE_NAME)));
// Translate access window start time
try { getModel().setAccessWindowStart(parseTime(attributes.get(ACCESS_WINDOW_START_ATTRIBUTE_NAME))); }
catch (ParseException e) {
logger.warn("Not setting start time of user access window: {}", e.getMessage());
logger.debug("Unable to parse time attribute.", e);
}
// Translate access window end time
try { getModel().setAccessWindowEnd(parseTime(attributes.get(ACCESS_WINDOW_END_ATTRIBUTE_NAME))); }
catch (ParseException e) {
logger.warn("Not setting end time of user access window: {}", e.getMessage());
logger.debug("Unable to parse time attribute.", e);
}
// Translate account validity start date
try { getModel().setValidFrom(parseDate(attributes.get(VALID_FROM_ATTRIBUTE_NAME))); }
catch (ParseException e) {
logger.warn("Not setting user validity start date: {}", e.getMessage());
logger.debug("Unable to parse date attribute.", e);
}
// Translate account validity end date
try { getModel().setValidUntil(parseDate(attributes.get(VALID_UNTIL_ATTRIBUTE_NAME))); }
catch (ParseException e) {
logger.warn("Not setting user validity end date: {}", e.getMessage());
logger.debug("Unable to parse date attribute.", e);
}
// Translate timezone attribute
getModel().setTimeZone(parseTimeZone(attributes.get(TIMEZONE_ATTRIBUTE_NAME)));
}
/**
* Returns the time zone associated with this user. This time zone must be
* used when interpreting all date/time restrictions related to this user.
*
* @return
* The time zone associated with this user.
*/
private TimeZone getTimeZone() {
// If no time zone is set, use the default
String timeZone = getModel().getTimeZone();
if (timeZone == null)
return TimeZone.getDefault();
// Otherwise parse and return time zone
return TimeZone.getTimeZone(timeZone);
}
/**
* Converts a SQL Time to a Calendar, independently of time zone, using the
* given Calendar as a base. The time components will be copied to the
* given Calendar verbatim, leaving the date and time zone components of
* the given Calendar otherwise intact.
*
* @param base
* The Calendar object to use as a base for the conversion.
*
* @param time
* The SQL Time object containing the time components to be applied to
* the given Calendar.
*
* @return
* The given Calendar, now modified to represent the given time.
*/
private Calendar asCalendar(Calendar base, Time time) {
// Get calendar from given SQL time
Calendar timeCalendar = Calendar.getInstance();
timeCalendar.setTime(time);
// Apply given time to base calendar
base.set(Calendar.HOUR_OF_DAY, timeCalendar.get(Calendar.HOUR_OF_DAY));
base.set(Calendar.MINUTE, timeCalendar.get(Calendar.MINUTE));
base.set(Calendar.SECOND, timeCalendar.get(Calendar.SECOND));
base.set(Calendar.MILLISECOND, timeCalendar.get(Calendar.MILLISECOND));
return base;
}
/**
* Returns the time during the current day when this user account can start
* being used.
*
* @return
* The time during the current day when this user account can start
* being used.
*/
private Calendar getAccessWindowStart() {
// Get window start time
Time start = getModel().getAccessWindowStart();
if (start == null)
return null;
// Return within defined time zone, current day
return asCalendar(Calendar.getInstance(getTimeZone()), start);
}
/**
* Returns the time during the current day when this user account can no
* longer be used.
*
* @return
* The time during the current day when this user account can no longer
* be used.
*/
private Calendar getAccessWindowEnd() {
// Get window end time
Time end = getModel().getAccessWindowEnd();
if (end == null)
return null;
// Return within defined time zone, current day
return asCalendar(Calendar.getInstance(getTimeZone()), end);
}
/**
* Returns the date after which this account becomes valid. The time
* components of the resulting Calendar object will be set to midnight of
* the date in question.
*
* @return
* The date after which this account becomes valid.
*/
private Calendar getValidFrom() {
// Get valid from date
Date validFrom = getModel().getValidFrom();
if (validFrom == null)
return null;
// Convert to midnight within defined time zone
Calendar validFromCalendar = Calendar.getInstance(getTimeZone());
validFromCalendar.setTime(validFrom);
validFromCalendar.set(Calendar.HOUR_OF_DAY, 0);
validFromCalendar.set(Calendar.MINUTE, 0);
validFromCalendar.set(Calendar.SECOND, 0);
validFromCalendar.set(Calendar.MILLISECOND, 0);
return validFromCalendar;
}
/**
* Returns the date after which this account becomes invalid. The time
* components of the resulting Calendar object will be set to the last
* millisecond of the day in question (23:59:59.999).
*
* @return
* The date after which this account becomes invalid.
*/
private Calendar getValidUntil() {
// Get valid until date
Date validUntil = getModel().getValidUntil();
if (validUntil == null)
return null;
// Convert to end-of-day within defined time zone
Calendar validUntilCalendar = Calendar.getInstance(getTimeZone());
validUntilCalendar.setTime(validUntil);
validUntilCalendar.set(Calendar.HOUR_OF_DAY, 23);
validUntilCalendar.set(Calendar.MINUTE, 59);
validUntilCalendar.set(Calendar.SECOND, 59);
validUntilCalendar.set(Calendar.MILLISECOND, 999);
return validUntilCalendar;
}
/**
* Given a time when a particular state changes from inactive to active,
* and a time when a particular state changes from active to inactive,
* determines whether that state is currently active.
*
* @param activeStart
* The time at which the state changes from inactive to active.
*
* @param inactiveStart
* The time at which the state changes from active to inactive.
*
* @return
* true if the state is currently active, false otherwise.
*/
private boolean isActive(Calendar activeStart, Calendar inactiveStart) {
// If end occurs before start, convert to equivalent case where start
// start is before end
if (inactiveStart != null && activeStart != null && inactiveStart.before(activeStart))
return !isActive(inactiveStart, activeStart);
// Get current time
Calendar current = Calendar.getInstance();
// State is active iff the current time is between the start and end
return !(activeStart != null && current.before(activeStart))
&& !(inactiveStart != null && current.after(inactiveStart));
}
/**
* Returns whether this user account is currently valid as of today.
* Account validity depends on optional date-driven restrictions which
* define when an account becomes valid, and when an account ceases being
* valid.
*
* @return
* true if the account is valid as of today, false otherwise.
*/
public boolean isAccountValid() {
return isActive(getValidFrom(), getValidUntil());
}
/**
* Returns whether the current time is within this user's allowed access
* window. If the login times for this user are not limited, this will
* return true.
*
* @return
* true if the current time is within this user's allowed access
* window, or if this user has no restrictions on login time, false
* otherwise.
*/
public boolean isAccountAccessible() {
return isActive(getAccessWindowStart(), getAccessWindowEnd());
}
}

View File

@@ -22,6 +22,8 @@
package org.glyptodon.guacamole.auth.jdbc.user;
import java.sql.Date;
import java.sql.Time;
import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel;
/**
@@ -55,6 +57,40 @@ public class UserModel extends ObjectModel {
*/
private boolean expired;
/**
* The time each day after which this user account may be used, stored in
* local time according to the value of timeZone.
*/
private Time accessWindowStart;
/**
* The time each day after which this user account may NOT be used, stored
* in local time according to the value of timeZone.
*/
private Time accessWindowEnd;
/**
* The day after which this account becomes valid and usable. Account
* validity begins at midnight of this day. Time information within the
* Date object is ignored.
*/
private Date validFrom;
/**
* The day after which this account can no longer be used. Account validity
* ends at midnight of the day following this day. Time information within
* the Date object is ignored.
*/
private Date validUntil;
/**
* The ID of the time zone used for all time comparisons for this user.
* Both accessWindowStart and accessWindowEnd values will use this time
* zone, as will checks for whether account validity dates have passed. If
* unset, the server's local time zone is used.
*/
private String timeZone;
/**
* Creates a new, empty user.
*/
@@ -158,4 +194,136 @@ public class UserModel extends ObjectModel {
this.expired = expired;
}
/**
* Returns the time each day after which this user account may be used. The
* time returned will be local time according to the time zone set with
* setTimeZone().
*
* @return
* The time each day after which this user account may be used, or null
* if this restriction does not apply.
*/
public Time getAccessWindowStart() {
return accessWindowStart;
}
/**
* Sets the time each day after which this user account may be used. The
* time given must be in local time according to the time zone set with
* setTimeZone().
*
* @param accessWindowStart
* The time each day after which this user account may be used, or null
* if this restriction does not apply.
*/
public void setAccessWindowStart(Time accessWindowStart) {
this.accessWindowStart = accessWindowStart;
}
/**
* Returns the time each day after which this user account may NOT be used.
* The time returned will be local time according to the time zone set with
* setTimeZone().
*
* @return
* The time each day after which this user account may NOT be used, or
* null if this restriction does not apply.
*/
public Time getAccessWindowEnd() {
return accessWindowEnd;
}
/**
* Sets the time each day after which this user account may NOT be used.
* The time given must be in local time according to the time zone set with
* setTimeZone().
*
* @param accessWindowEnd
* The time each day after which this user account may NOT be used, or
* null if this restriction does not apply.
*/
public void setAccessWindowEnd(Time accessWindowEnd) {
this.accessWindowEnd = accessWindowEnd;
}
/**
* Returns the day after which this account becomes valid and usable.
* Account validity begins at midnight of this day. Any time information
* within the returned Date object must be ignored.
*
* @return
* The day after which this account becomes valid and usable, or null
* if this restriction does not apply.
*/
public Date getValidFrom() {
return validFrom;
}
/**
* Sets the day after which this account becomes valid and usable. Account
* validity begins at midnight of this day. Any time information within
* the provided Date object will be ignored.
*
* @param validFrom
* The day after which this account becomes valid and usable, or null
* if this restriction does not apply.
*/
public void setValidFrom(Date validFrom) {
this.validFrom = validFrom;
}
/**
* Returns the day after which this account can no longer be used. Account
* validity ends at midnight of the day following this day. Any time
* information within the returned Date object must be ignored.
*
* @return
* The day after which this account can no longer be used, or null if
* this restriction does not apply.
*/
public Date getValidUntil() {
return validUntil;
}
/**
* Sets the day after which this account can no longer be used. Account
* validity ends at midnight of the day following this day. Any time
* information within the provided Date object will be ignored.
*
* @param validUntil
* The day after which this account can no longer be used, or null if
* this restriction does not apply.
*/
public void setValidUntil(Date validUntil) {
this.validUntil = validUntil;
}
/**
* Returns the Java ID of the time zone to be used for all time comparisons
* for this user. This ID should correspond to a value returned by
* TimeZone.getAvailableIDs(). If unset or invalid, the server's local time
* zone must be used.
*
* @return
* The ID of the time zone to be used for all time comparisons, which
* should correspond to a value returned by TimeZone.getAvailableIDs().
*/
public String getTimeZone() {
return timeZone;
}
/**
* Sets the Java ID of the time zone to be used for all time comparisons
* for this user. This ID should correspond to a value returned by
* TimeZone.getAvailableIDs(). If unset or invalid, the server's local time
* zone will be used.
*
* @param timeZone
* The ID of the time zone to be used for all time comparisons, which
* should correspond to a value returned by TimeZone.getAvailableIDs().
*/
public void setTimeZone(String timeZone) {
this.timeZone = timeZone;
}
}

View File

@@ -300,6 +300,14 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
ModeledUser user = getObjectInstance(null, userModel);
user.setCurrentUser(new AuthenticatedUser(user, credentials));
// Verify user account is still valid as of today
if (!user.isAccountValid())
throw new GuacamoleClientException("LOGIN.ERROR_NOT_VALID");
// Verify user account is allowed to be used at the current time
if (!user.isAccountAccessible())
throw new GuacamoleClientException("LOGIN.ERROR_NOT_ACCESSIBLE");
// Update password if password is expired
if (userModel.isExpired()) {

View File

@@ -5,6 +5,8 @@
"ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK",
"ERROR_PASSWORD_SAME" : "The new password must be different from the expired password.",
"ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH",
"ERROR_NOT_VALID" : "This user account is not currently valid.",
"ERROR_NOT_ACCESSIBLE" : "Access to this account is not currently allowed. Please try again later.",
"INFO_PASSWORD_EXPIRED" : "Your password has expired and must be reset. Please enter a new password to continue.",
@@ -15,8 +17,13 @@
"USER_ATTRIBUTES" : {
"FIELD_HEADER_DISABLED" : "Login disabled:",
"FIELD_HEADER_EXPIRED" : "Password expired:",
"FIELD_HEADER_DISABLED" : "Login disabled:",
"FIELD_HEADER_EXPIRED" : "Password expired:",
"FIELD_HEADER_ACCESS_WINDOW_END" : "Do not allow access after:",
"FIELD_HEADER_ACCESS_WINDOW_START" : "Allow access after:",
"FIELD_HEADER_TIMEZONE" : "User time zone:",
"FIELD_HEADER_VALID_FROM" : "Enable account after:",
"FIELD_HEADER_VALID_UNTIL" : "Disable account after:",
"SECTION_HEADER_RESTRICTIONS" : "Account Restrictions"

View File

@@ -73,12 +73,27 @@ CREATE TABLE `guacamole_connection` (
CREATE TABLE `guacamole_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
-- Username and optionally-salted password
`username` varchar(128) NOT NULL,
`password_hash` binary(32) NOT NULL,
`password_salt` binary(32),
-- Account disabled/expired status
`disabled` boolean NOT NULL DEFAULT 0,
`expired` boolean NOT NULL DEFAULT 0,
-- Time-based access restriction
`access_window_start` TIME,
`access_window_end` TIME,
-- Date-based access restriction
`valid_from` DATE,
`valid_until` DATE,
-- Timezone used for all date/time comparisons and interpretation
`timezone` VARCHAR(64),
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)

View File

@@ -0,0 +1,41 @@
--
-- 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
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
--
-- Add per-user time-based access restrictions.
--
ALTER TABLE guacamole_user ADD COLUMN access_window_start TIME;
ALTER TABLE guacamole_user ADD COLUMN access_window_end TIME;
--
-- Add per-user date-based account validity restrictions.
--
ALTER TABLE guacamole_user ADD COLUMN valid_from DATE;
ALTER TABLE guacamole_user ADD COLUMN valid_until DATE;
--
-- Add per-user timezone for sake of time comparisons/interpretation.
--
ALTER TABLE guacamole_user ADD COLUMN timezone VARCHAR(64);

View File

@@ -28,12 +28,16 @@
<!-- Result mapper for user objects -->
<resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" >
<id column="user_id" property="objectID" jdbcType="INTEGER"/>
<result column="username" property="identifier" jdbcType="VARCHAR"/>
<result column="password_hash" property="passwordHash" jdbcType="BINARY"/>
<result column="password_salt" property="passwordSalt" jdbcType="BINARY"/>
<result column="disabled" property="disabled" jdbcType="BOOLEAN"/>
<result column="expired" property="expired" jdbcType="BOOLEAN"/>
<id column="user_id" property="objectID" jdbcType="INTEGER"/>
<result column="username" property="identifier" jdbcType="VARCHAR"/>
<result column="password_hash" property="passwordHash" jdbcType="BINARY"/>
<result column="password_salt" property="passwordSalt" jdbcType="BINARY"/>
<result column="disabled" property="disabled" jdbcType="BOOLEAN"/>
<result column="access_window_start" property="accessWindowStart" jdbcType="TIME"/>
<result column="access_window_end" property="accessWindowEnd" jdbcType="TIME"/>
<result column="valid_from" property="validFrom" jdbcType="DATE"/>
<result column="valid_until" property="validUntil" jdbcType="DATE"/>
<result column="timezone" property="timeZone" jdbcType="VARCHAR"/>
</resultMap>
<!-- Select all usernames -->
@@ -61,7 +65,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
WHERE username IN
<foreach collection="identifiers" item="identifier"
@@ -80,7 +89,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id
WHERE username IN
@@ -102,7 +116,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
WHERE
username = #{username,jdbcType=VARCHAR}
@@ -124,14 +143,24 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
)
VALUES (
#{object.identifier,jdbcType=VARCHAR},
#{object.passwordHash,jdbcType=BINARY},
#{object.passwordSalt,jdbcType=BINARY},
#{object.disabled,jdbcType=BOOLEAN},
#{object.expired,jdbcType=BOOLEAN}
#{object.expired,jdbcType=BOOLEAN},
#{object.accessWindowStart,jdbcType=TIME},
#{object.accessWindowEnd,jdbcType=TIME},
#{object.validFrom,jdbcType=DATE},
#{object.validUntil,jdbcType=DATE},
#{object.timeZone,jdbcType=VARCHAR}
)
</insert>
@@ -142,7 +171,12 @@
SET password_hash = #{object.passwordHash,jdbcType=BINARY},
password_salt = #{object.passwordSalt,jdbcType=BINARY},
disabled = #{object.disabled,jdbcType=BOOLEAN},
expired = #{object.expired,jdbcType=BOOLEAN}
expired = #{object.expired,jdbcType=BOOLEAN},
access_window_start = #{object.accessWindowStart,jdbcType=TIME},
access_window_end = #{object.accessWindowEnd,jdbcType=TIME},
valid_from = #{object.validFrom,jdbcType=DATE},
valid_until = #{object.validUntil,jdbcType=DATE},
timezone = #{object.timeZone,jdbcType=VARCHAR}
WHERE user_id = #{object.objectID,jdbcType=VARCHAR}
</update>

View File

@@ -114,12 +114,27 @@ CREATE INDEX ON guacamole_connection(parent_id);
CREATE TABLE guacamole_user (
user_id serial NOT NULL,
-- Username and optionally-salted password
username varchar(128) NOT NULL,
password_hash bytea NOT NULL,
password_salt bytea,
-- Account disabled/expired status
disabled boolean NOT NULL DEFAULT FALSE,
expired boolean NOT NULL DEFAULT FALSE,
-- Time-based access restriction
access_window_start time,
access_window_end time,
-- Date-based access restriction
valid_from date,
valid_until date,
-- Timezone used for all date/time comparisons and interpretation
timezone varchar(64),
PRIMARY KEY (user_id),
CONSTRAINT username

View File

@@ -0,0 +1,41 @@
--
-- 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
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
--
--
-- Add per-user time-based access restrictions.
--
ALTER TABLE guacamole_user ADD COLUMN access_window_start time;
ALTER TABLE guacamole_user ADD COLUMN access_window_end time;
--
-- Add per-user date-based account validity restrictions.
--
ALTER TABLE guacamole_user ADD COLUMN valid_from date;
ALTER TABLE guacamole_user ADD COLUMN valid_until date;
--
-- Add per-user timezone for sake of time comparisons/interpretation.
--
ALTER TABLE guacamole_user ADD COLUMN timezone varchar(64);

View File

@@ -28,12 +28,17 @@
<!-- Result mapper for user objects -->
<resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" >
<id column="user_id" property="objectID" jdbcType="INTEGER"/>
<result column="username" property="identifier" jdbcType="VARCHAR"/>
<result column="password_hash" property="passwordHash" jdbcType="BINARY"/>
<result column="password_salt" property="passwordSalt" jdbcType="BINARY"/>
<result column="disabled" property="disabled" jdbcType="BOOLEAN"/>
<result column="expired" property="expired" jdbcType="BOOLEAN"/>
<id column="user_id" property="objectID" jdbcType="INTEGER"/>
<result column="username" property="identifier" jdbcType="VARCHAR"/>
<result column="password_hash" property="passwordHash" jdbcType="BINARY"/>
<result column="password_salt" property="passwordSalt" jdbcType="BINARY"/>
<result column="disabled" property="disabled" jdbcType="BOOLEAN"/>
<result column="expired" property="expired" jdbcType="BOOLEAN"/>
<result column="access_window_start" property="accessWindowStart" jdbcType="TIME"/>
<result column="access_window_end" property="accessWindowEnd" jdbcType="TIME"/>
<result column="valid_from" property="validFrom" jdbcType="DATE"/>
<result column="valid_until" property="validUntil" jdbcType="DATE"/>
<result column="timezone" property="timeZone" jdbcType="VARCHAR"/>
</resultMap>
<!-- Select all usernames -->
@@ -61,7 +66,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
WHERE username IN
<foreach collection="identifiers" item="identifier"
@@ -80,7 +90,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id
WHERE username IN
@@ -102,7 +117,12 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
FROM guacamole_user
WHERE
username = #{username,jdbcType=VARCHAR}
@@ -124,14 +144,24 @@
password_hash,
password_salt,
disabled,
expired
expired,
access_window_start,
access_window_end,
valid_from,
valid_until,
timezone
)
VALUES (
#{object.identifier,jdbcType=VARCHAR},
#{object.passwordHash,jdbcType=BINARY},
#{object.passwordSalt,jdbcType=BINARY},
#{object.disabled,jdbcType=BOOLEAN},
#{object.expired,jdbcType=BOOLEAN}
#{object.expired,jdbcType=BOOLEAN},
#{object.accessWindowStart,jdbcType=TIME},
#{object.accessWindowEnd,jdbcType=TIME},
#{object.validFrom,jdbcType=DATE},
#{object.validUntil,jdbcType=DATE},
#{object.timeZone,jdbcType=VARCHAR}
)
</insert>
@@ -142,7 +172,12 @@
SET password_hash = #{object.passwordHash,jdbcType=BINARY},
password_salt = #{object.passwordSalt,jdbcType=BINARY},
disabled = #{object.disabled,jdbcType=BOOLEAN},
expired = #{object.expired,jdbcType=BOOLEAN}
expired = #{object.expired,jdbcType=BOOLEAN},
access_window_start = #{object.accessWindowStart,jdbcType=TIME},
access_window_end = #{object.accessWindowEnd,jdbcType=TIME},
valid_from = #{object.validFrom,jdbcType=DATE},
valid_until = #{object.validUntil,jdbcType=DATE},
timezone = #{object.timeZone,jdbcType=VARCHAR}
WHERE user_id = #{object.objectID,jdbcType=VARCHAR}
</update>