mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-30 00:23:21 +00:00 
			
		
		
		
	Merge pull request #236 from glyptodon/date-time-restrictions
GUAC-1213: Add date/time restrictions for user accounts.
This commit is contained in:
		| @@ -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 |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  * of this software and associated documentation files (the "Software"), to deal |  * of this software and associated documentation files (the "Software"), to deal | ||||||
| @@ -23,11 +23,18 @@ | |||||||
| package org.glyptodon.guacamole.auth.jdbc.user; | package org.glyptodon.guacamole.auth.jdbc.user; | ||||||
|  |  | ||||||
| import com.google.inject.Inject; | 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.Arrays; | ||||||
|  | import java.util.Calendar; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.TimeZone; | ||||||
| import org.glyptodon.guacamole.auth.jdbc.base.ModeledDirectoryObject; | import org.glyptodon.guacamole.auth.jdbc.base.ModeledDirectoryObject; | ||||||
| import org.glyptodon.guacamole.auth.jdbc.security.PasswordEncryptionService; | import org.glyptodon.guacamole.auth.jdbc.security.PasswordEncryptionService; | ||||||
| import org.glyptodon.guacamole.auth.jdbc.security.SaltService; | 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.BooleanField; | ||||||
| import org.glyptodon.guacamole.form.Field; | import org.glyptodon.guacamole.form.Field; | ||||||
| import org.glyptodon.guacamole.form.Form; | 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.User; | ||||||
| import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; | import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; | ||||||
| import org.glyptodon.guacamole.net.auth.permission.SystemPermission; | import org.glyptodon.guacamole.net.auth.permission.SystemPermission; | ||||||
| import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet; | 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. |  * 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 { | 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 |      * The name of the attribute which controls whether a user account is | ||||||
|      * disabled. |      * disabled. | ||||||
| @@ -65,13 +90,48 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us | |||||||
|      */ |      */ | ||||||
|     public static final String EXPIRED_ATTRIBUTE_NAME = "expired"; |     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 |      * All attributes related to restricting user accounts, within a logical | ||||||
|      * form. |      * form. | ||||||
|      */ |      */ | ||||||
|     public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", Arrays.<Field>asList( |     public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", Arrays.<Field>asList( | ||||||
|         new BooleanField(DISABLED_ATTRIBUTE_NAME, "true"), |         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); |         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 |     @Override | ||||||
|     public Map<String, String> getAttributes() { |     public Map<String, String> getAttributes() { | ||||||
|  |  | ||||||
| @@ -227,6 +398,21 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us | |||||||
|         // Set password expired attribute |         // Set password expired attribute | ||||||
|         attributes.put(EXPIRED_ATTRIBUTE_NAME, getModel().isExpired() ? "true" : null); |         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; |         return attributes; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -239,6 +425,237 @@ public class ModeledUser extends ModeledDirectoryObject<UserModel> implements Us | |||||||
|         // Translate password expired attribute |         // Translate password expired attribute | ||||||
|         getModel().setExpired("true".equals(attributes.get(EXPIRED_ATTRIBUTE_NAME))); |         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()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ | |||||||
|  |  | ||||||
| package org.glyptodon.guacamole.auth.jdbc.user; | package org.glyptodon.guacamole.auth.jdbc.user; | ||||||
|  |  | ||||||
|  | import java.sql.Date; | ||||||
|  | import java.sql.Time; | ||||||
| import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel; | import org.glyptodon.guacamole.auth.jdbc.base.ObjectModel; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -55,6 +57,40 @@ public class UserModel extends ObjectModel { | |||||||
|      */ |      */ | ||||||
|     private boolean expired; |     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. |      * Creates a new, empty user. | ||||||
|      */ |      */ | ||||||
| @@ -158,4 +194,136 @@ public class UserModel extends ObjectModel { | |||||||
|         this.expired = expired; |         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; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -300,6 +300,14 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User | |||||||
|         ModeledUser user = getObjectInstance(null, userModel); |         ModeledUser user = getObjectInstance(null, userModel); | ||||||
|         user.setCurrentUser(new AuthenticatedUser(user, credentials)); |         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 |         // Update password if password is expired | ||||||
|         if (userModel.isExpired()) { |         if (userModel.isExpired()) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ | |||||||
|         "ERROR_PASSWORD_BLANK"    : "@:APP.ERROR_PASSWORD_BLANK", |         "ERROR_PASSWORD_BLANK"    : "@:APP.ERROR_PASSWORD_BLANK", | ||||||
|         "ERROR_PASSWORD_SAME"     : "The new password must be different from the expired password.", |         "ERROR_PASSWORD_SAME"     : "The new password must be different from the expired password.", | ||||||
|         "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", |         "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.", |         "INFO_PASSWORD_EXPIRED" : "Your password has expired and must be reset. Please enter a new password to continue.", | ||||||
|  |  | ||||||
| @@ -15,8 +17,13 @@ | |||||||
|  |  | ||||||
|     "USER_ATTRIBUTES" : { |     "USER_ATTRIBUTES" : { | ||||||
|  |  | ||||||
|         "FIELD_HEADER_DISABLED" : "Login disabled:", |         "FIELD_HEADER_DISABLED"            : "Login disabled:", | ||||||
|         "FIELD_HEADER_EXPIRED"  : "Password expired:", |         "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" |         "SECTION_HEADER_RESTRICTIONS" : "Account Restrictions" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -73,12 +73,27 @@ CREATE TABLE `guacamole_connection` ( | |||||||
| CREATE TABLE `guacamole_user` ( | CREATE TABLE `guacamole_user` ( | ||||||
|  |  | ||||||
|   `user_id`       int(11)      NOT NULL AUTO_INCREMENT, |   `user_id`       int(11)      NOT NULL AUTO_INCREMENT, | ||||||
|  |  | ||||||
|  |   -- Username and optionally-salted password | ||||||
|   `username`      varchar(128) NOT NULL, |   `username`      varchar(128) NOT NULL, | ||||||
|   `password_hash` binary(32)   NOT NULL, |   `password_hash` binary(32)   NOT NULL, | ||||||
|   `password_salt` binary(32), |   `password_salt` binary(32), | ||||||
|  |  | ||||||
|  |   -- Account disabled/expired status | ||||||
|   `disabled`      boolean      NOT NULL DEFAULT 0, |   `disabled`      boolean      NOT NULL DEFAULT 0, | ||||||
|   `expired`       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`), |   PRIMARY KEY (`user_id`), | ||||||
|   UNIQUE KEY `username` (`username`) |   UNIQUE KEY `username` (`username`) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -28,12 +28,16 @@ | |||||||
|  |  | ||||||
|     <!-- Result mapper for user objects --> |     <!-- Result mapper for user objects --> | ||||||
|     <resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" > |     <resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" > | ||||||
|         <id     column="user_id"       property="objectID"     jdbcType="INTEGER"/> |         <id     column="user_id"             property="objectID"          jdbcType="INTEGER"/> | ||||||
|         <result column="username"      property="identifier"   jdbcType="VARCHAR"/> |         <result column="username"            property="identifier"        jdbcType="VARCHAR"/> | ||||||
|         <result column="password_hash" property="passwordHash" jdbcType="BINARY"/> |         <result column="password_hash"       property="passwordHash"      jdbcType="BINARY"/> | ||||||
|         <result column="password_salt" property="passwordSalt" jdbcType="BINARY"/> |         <result column="password_salt"       property="passwordSalt"      jdbcType="BINARY"/> | ||||||
|         <result column="disabled"      property="disabled"     jdbcType="BOOLEAN"/> |         <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> |     </resultMap> | ||||||
|  |  | ||||||
|     <!-- Select all usernames --> |     <!-- Select all usernames --> | ||||||
| @@ -61,7 +65,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         WHERE username IN |         WHERE username IN | ||||||
|             <foreach collection="identifiers" item="identifier" |             <foreach collection="identifiers" item="identifier" | ||||||
| @@ -80,7 +89,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id |         JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id | ||||||
|         WHERE username IN |         WHERE username IN | ||||||
| @@ -102,7 +116,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         WHERE |         WHERE | ||||||
|             username = #{username,jdbcType=VARCHAR} |             username = #{username,jdbcType=VARCHAR} | ||||||
| @@ -124,14 +143,24 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         ) |         ) | ||||||
|         VALUES ( |         VALUES ( | ||||||
|             #{object.identifier,jdbcType=VARCHAR}, |             #{object.identifier,jdbcType=VARCHAR}, | ||||||
|             #{object.passwordHash,jdbcType=BINARY}, |             #{object.passwordHash,jdbcType=BINARY}, | ||||||
|             #{object.passwordSalt,jdbcType=BINARY}, |             #{object.passwordSalt,jdbcType=BINARY}, | ||||||
|             #{object.disabled,jdbcType=BOOLEAN}, |             #{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> |     </insert> | ||||||
| @@ -142,7 +171,12 @@ | |||||||
|         SET password_hash = #{object.passwordHash,jdbcType=BINARY}, |         SET password_hash = #{object.passwordHash,jdbcType=BINARY}, | ||||||
|             password_salt = #{object.passwordSalt,jdbcType=BINARY}, |             password_salt = #{object.passwordSalt,jdbcType=BINARY}, | ||||||
|             disabled = #{object.disabled,jdbcType=BOOLEAN}, |             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} |         WHERE user_id = #{object.objectID,jdbcType=VARCHAR} | ||||||
|     </update> |     </update> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -114,12 +114,27 @@ CREATE INDEX ON guacamole_connection(parent_id); | |||||||
| CREATE TABLE guacamole_user ( | CREATE TABLE guacamole_user ( | ||||||
|  |  | ||||||
|   user_id       serial       NOT NULL, |   user_id       serial       NOT NULL, | ||||||
|  |  | ||||||
|  |   -- Username and optionally-salted password | ||||||
|   username      varchar(128) NOT NULL, |   username      varchar(128) NOT NULL, | ||||||
|   password_hash bytea        NOT NULL, |   password_hash bytea        NOT NULL, | ||||||
|   password_salt bytea, |   password_salt bytea, | ||||||
|  |  | ||||||
|  |   -- Account disabled/expired status | ||||||
|   disabled      boolean      NOT NULL DEFAULT FALSE, |   disabled      boolean      NOT NULL DEFAULT FALSE, | ||||||
|   expired       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), |   PRIMARY KEY (user_id), | ||||||
|  |  | ||||||
|   CONSTRAINT username |   CONSTRAINT username | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -28,12 +28,17 @@ | |||||||
|  |  | ||||||
|     <!-- Result mapper for user objects --> |     <!-- Result mapper for user objects --> | ||||||
|     <resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" > |     <resultMap id="UserResultMap" type="org.glyptodon.guacamole.auth.jdbc.user.UserModel" > | ||||||
|         <id     column="user_id"       property="objectID"     jdbcType="INTEGER"/> |         <id     column="user_id"             property="objectID"          jdbcType="INTEGER"/> | ||||||
|         <result column="username"      property="identifier"   jdbcType="VARCHAR"/> |         <result column="username"            property="identifier"        jdbcType="VARCHAR"/> | ||||||
|         <result column="password_hash" property="passwordHash" jdbcType="BINARY"/> |         <result column="password_hash"       property="passwordHash"      jdbcType="BINARY"/> | ||||||
|         <result column="password_salt" property="passwordSalt" jdbcType="BINARY"/> |         <result column="password_salt"       property="passwordSalt"      jdbcType="BINARY"/> | ||||||
|         <result column="disabled"      property="disabled"     jdbcType="BOOLEAN"/> |         <result column="disabled"            property="disabled"          jdbcType="BOOLEAN"/> | ||||||
|         <result column="expired"       property="expired"      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> |     </resultMap> | ||||||
|  |  | ||||||
|     <!-- Select all usernames --> |     <!-- Select all usernames --> | ||||||
| @@ -61,7 +66,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         WHERE username IN |         WHERE username IN | ||||||
|             <foreach collection="identifiers" item="identifier" |             <foreach collection="identifiers" item="identifier" | ||||||
| @@ -80,7 +90,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id |         JOIN guacamole_user_permission ON affected_user_id = guacamole_user.user_id | ||||||
|         WHERE username IN |         WHERE username IN | ||||||
| @@ -102,7 +117,12 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         FROM guacamole_user |         FROM guacamole_user | ||||||
|         WHERE |         WHERE | ||||||
|             username = #{username,jdbcType=VARCHAR} |             username = #{username,jdbcType=VARCHAR} | ||||||
| @@ -124,14 +144,24 @@ | |||||||
|             password_hash, |             password_hash, | ||||||
|             password_salt, |             password_salt, | ||||||
|             disabled, |             disabled, | ||||||
|             expired |             expired, | ||||||
|  |             access_window_start, | ||||||
|  |             access_window_end, | ||||||
|  |             valid_from, | ||||||
|  |             valid_until, | ||||||
|  |             timezone | ||||||
|         ) |         ) | ||||||
|         VALUES ( |         VALUES ( | ||||||
|             #{object.identifier,jdbcType=VARCHAR}, |             #{object.identifier,jdbcType=VARCHAR}, | ||||||
|             #{object.passwordHash,jdbcType=BINARY}, |             #{object.passwordHash,jdbcType=BINARY}, | ||||||
|             #{object.passwordSalt,jdbcType=BINARY}, |             #{object.passwordSalt,jdbcType=BINARY}, | ||||||
|             #{object.disabled,jdbcType=BOOLEAN}, |             #{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> |     </insert> | ||||||
| @@ -142,7 +172,12 @@ | |||||||
|         SET password_hash = #{object.passwordHash,jdbcType=BINARY}, |         SET password_hash = #{object.passwordHash,jdbcType=BINARY}, | ||||||
|             password_salt = #{object.passwordSalt,jdbcType=BINARY}, |             password_salt = #{object.passwordSalt,jdbcType=BINARY}, | ||||||
|             disabled = #{object.disabled,jdbcType=BOOLEAN}, |             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} |         WHERE user_id = #{object.objectID,jdbcType=VARCHAR} | ||||||
|     </update> |     </update> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user