diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java index 0f234c494..0e0e840ff 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/ModeledUser.java @@ -37,6 +37,7 @@ import org.glyptodon.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermis import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionGroupPermissionService; import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionPermissionService; 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.net.auth.User; @@ -58,12 +59,19 @@ public class ModeledUser extends ModeledDirectoryObject implements Us */ public static final String DISABLED_ATTRIBUTE_NAME = "disabled"; + /** + * The name of the attribute which controls whether a user's password is + * expired and must be reset upon login. + */ + public static final String EXPIRED_ATTRIBUTE_NAME = "expired"; + /** * All attributes related to restricting user accounts, within a logical * form. */ - public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", "Account Restrictions", Arrays.asList( - new Field(DISABLED_ATTRIBUTE_NAME, "Disabled", "true") + public static final Form ACCOUNT_RESTRICTIONS = new Form("restrictions", Arrays.asList( + new BooleanField(DISABLED_ATTRIBUTE_NAME, "true"), + new BooleanField(EXPIRED_ATTRIBUTE_NAME, "true") )); /** @@ -214,7 +222,10 @@ public class ModeledUser extends ModeledDirectoryObject implements Us Map attributes = new HashMap(); // Set disabled attribute - attributes.put("disabled", getModel().isDisabled() ? "true" : null); + attributes.put(DISABLED_ATTRIBUTE_NAME, getModel().isDisabled() ? "true" : null); + + // Set password expired attribute + attributes.put(EXPIRED_ATTRIBUTE_NAME, getModel().isExpired() ? "true" : null); return attributes; } @@ -223,7 +234,10 @@ public class ModeledUser extends ModeledDirectoryObject implements Us public void setAttributes(Map attributes) { // Translate disabled attribute - getModel().setDisabled("true".equals(attributes.get("disabled"))); + getModel().setDisabled("true".equals(attributes.get(DISABLED_ATTRIBUTE_NAME))); + + // Translate password expired attribute + getModel().setExpired("true".equals(attributes.get(EXPIRED_ATTRIBUTE_NAME))); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java index 589edd149..b980b10ef 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java @@ -26,6 +26,8 @@ import com.google.inject.Inject; import com.google.inject.Provider; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Credentials; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; /** * Service which creates new UserContext instances for valid users based on @@ -49,17 +51,20 @@ public class UserContextService { /** * Authenticates the user having the given credentials, returning a new - * UserContext instance if the credentials are valid. + * UserContext instance only if the credentials are valid. If the + * credentials are invalid or expired, an appropriate GuacamoleException + * will be thrown. * * @param credentials * The credentials to use to produce the UserContext. * * @return * A new UserContext instance for the user identified by the given - * credentials, or null if the credentials are not valid. + * credentials. * * @throws GuacamoleException - * If an error occurs during authentication. + * If an error occurs during authentication, or if the given + * credentials are invalid or expired. */ public org.glyptodon.guacamole.net.auth.UserContext getUserContext(Credentials credentials) @@ -67,7 +72,7 @@ public class UserContextService { // Authenticate user ModeledUser user = userService.retrieveUser(credentials); - if (user != null && !user.getModel().isDisabled()) { + if (user != null) { // Upon successful authentication, return new user context UserContext context = userContextProvider.get(); @@ -77,7 +82,7 @@ public class UserContextService { } // Otherwise, unauthorized - return null; + throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java index 1f84ab566..5057b2f5c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserModel.java @@ -48,6 +48,13 @@ public class UserModel extends ObjectModel { */ private boolean disabled; + /** + * Whether the user's password is expired. If a user's password is expired, + * it must be changed immediately upon login, and the account cannot be + * used until this occurs. + */ + private boolean expired; + /** * Creates a new, empty user. */ @@ -127,4 +134,28 @@ public class UserModel extends ObjectModel { this.disabled = disabled; } + /** + * Returns whether the user's password has expired. If a user's password is + * expired, it must be immediately changed upon login. A user account with + * an expired password cannot be used until the password has been changed. + * + * @return + * true if the user's password has expired, false otherwise. + */ + public boolean isExpired() { + return expired; + } + + /** + * Sets whether the user's password is expired. If a user's password is + * expired, it must be immediately changed upon login. A user account with + * an expired password cannot be used until the password has been changed. + * + * @param expired + * true to expire the user's password, false otherwise. + */ + public void setExpired(boolean expired) { + this.expired = expired; + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java index 6cb7ad36b..969741bf4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java @@ -27,6 +27,7 @@ import com.google.inject.Provider; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import javax.servlet.http.HttpServletRequest; import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; import org.glyptodon.guacamole.auth.jdbc.base.ModeledDirectoryObjectService; @@ -37,11 +38,17 @@ import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionMapper; import org.glyptodon.guacamole.auth.jdbc.permission.ObjectPermissionModel; import org.glyptodon.guacamole.auth.jdbc.permission.UserPermissionMapper; import org.glyptodon.guacamole.auth.jdbc.security.PasswordEncryptionService; +import org.glyptodon.guacamole.form.Field; +import org.glyptodon.guacamole.form.PasswordField; import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; 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; /** * Service which provides convenience methods for creating, retrieving, and @@ -51,6 +58,11 @@ import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet; */ public class UserService extends ModeledDirectoryObjectService { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(UserService.class); + /** * All user permissions which are implicitly granted to the new user upon * creation. @@ -59,7 +71,43 @@ public class UserService extends ModeledDirectoryObjectService + @@ -59,7 +60,8 @@ username, password_hash, password_salt, - disabled + disabled, + expired FROM guacamole_user WHERE username IN @@ -135,7 +141,8 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, - disabled = #{object.disabled,jdbcType=BOOLEAN} + disabled = #{object.disabled,jdbcType=BOOLEAN}, + expired = #{object.expired,jdbcType=BOOLEAN} WHERE user_id = #{object.objectID,jdbcType=VARCHAR} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql index d228b5844..7f14986be 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/001-create-schema.sql @@ -118,6 +118,7 @@ CREATE TABLE guacamole_user ( password_hash bytea NOT NULL, password_salt bytea, disabled boolean NOT NULL DEFAULT FALSE, + expired boolean NOT NULL DEFAULT FALSE, PRIMARY KEY (user_id), diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql index ba8649629..0853e90c8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/schema/upgrade/upgrade-pre-0.9.7.sql @@ -26,3 +26,9 @@ ALTER TABLE guacamole_user ADD COLUMN disabled boolean NOT NULL DEFAULT FALSE; +-- +-- Add per-user password expiration flag +-- + +ALTER TABLE guacamole_user ADD COLUMN expired boolean NOT NULL DEFAULT FALSE; + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json index a3ae33e4e..18f97e207 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/guac-manifest.json @@ -10,7 +10,9 @@ ], "translations" : [ - "translations/en_US.json" + "translations/en.json", + "translations/fr.json", + "translations/ru.json" ] } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml index 6b2438229..d78b30abb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/resources/org/glyptodon/guacamole/auth/jdbc/user/UserMapper.xml @@ -33,6 +33,7 @@ + @@ -59,7 +60,8 @@ username, password_hash, password_salt, - disabled + disabled, + expired FROM guacamole_user WHERE username IN @@ -135,7 +141,8 @@ UPDATE guacamole_user SET password_hash = #{object.passwordHash,jdbcType=BINARY}, password_salt = #{object.passwordSalt,jdbcType=BINARY}, - disabled = #{object.disabled,jdbcType=BOOLEAN} + disabled = #{object.disabled,jdbcType=BOOLEAN}, + expired = #{object.expired,jdbcType=BOOLEAN} WHERE user_id = #{object.objectID,jdbcType=VARCHAR} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/BooleanField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/BooleanField.java new file mode 100644 index 000000000..d033a7dc4 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/BooleanField.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +import java.util.Collections; + +/** + * Represents a field with strictly one possible value. It is assumed that the + * field may be blank, but that its sole non-blank value is the value provided. + * The provided value represents "true" while all other values, including + * having no associated value, represent "false". + * + * @author Michael Jumper + */ +public class BooleanField extends Field { + + /** + * Creates a new BooleanField with the given name and truth value. The + * truth value is the value that, when assigned to this field, means that + * this field is "true". + * + * @param name + * The unique name to associate with this field. + * + * @param truthValue + * The value to consider "true" for this field. All other values will + * be considered "false". + */ + public BooleanField(String name, String truthValue) { + super(name, Field.Type.BOOLEAN, Collections.singletonList(truthValue)); + } + +} diff --git a/guacamole/src/main/webapp/app/rest/types/FieldOption.js b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/EnumField.java similarity index 53% rename from guacamole/src/main/webapp/app/rest/types/FieldOption.js rename to guacamole-ext/src/main/java/org/glyptodon/guacamole/form/EnumField.java index 7c4f9a933..272b52f22 100644 --- a/guacamole/src/main/webapp/app/rest/types/FieldOption.js +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/EnumField.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 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 @@ -20,42 +20,29 @@ * THE SOFTWARE. */ +package org.glyptodon.guacamole.form; + +import java.util.Collection; + /** - * Service which defines the FieldOption class. + * Represents an arbitrary field with a finite, enumerated set of possible + * values. + * + * @author Michael Jumper */ -angular.module('rest').factory('FieldOption', [function defineFieldOption() { - +public class EnumField extends Field { + /** - * The object returned by REST API calls when representing a single possible - * legal value of a field. - * - * @constructor - * @param {FieldOption|Object} [template={}] - * The object whose properties should be copied within the new - * FieldOption. + * Creates a new EnumField with the given name and possible values. + * + * @param name + * The unique name to associate with this field. + * + * @param options + * All possible legal options for this field. */ - var FieldOption = function FieldOption(template) { + public EnumField(String name, Collection options) { + super(name, Field.Type.ENUM, options); + } - // Use empty object by default - template = template || {}; - - /** - * A human-readable name for this parameter value. - * - * @type String - */ - this.title = template.title; - - /** - * The actual value to set the parameter to, if this option is - * selected. - * - * @type String - */ - this.value = template.value; - - }; - - return FieldOption; - -}]); \ No newline at end of file +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Field.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Field.java index 91e4c6ffd..c463c47b3 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Field.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,11 @@ import org.codehaus.jackson.map.annotate.JsonSerialize; /** * Represents an arbitrary field, such as an HTTP parameter, the parameter of a - * remote desktop protocol, or an input field within a form. + * remote desktop protocol, or an input field within a form. Fields are generic + * and typed dynamically through a type string, with the semantics of the field + * defined by the type string. The behavior of each field type is defined + * either through the web application itself (see FormService.js) or through + * extensions. * * @author Michael Jumper */ @@ -35,7 +39,11 @@ import org.codehaus.jackson.map.annotate.JsonSerialize; public class Field { /** - * All possible types of field. + * All types of fields which are available by default. Additional field + * types may be defined by extensions by using a unique field type name and + * registering that name with the form service within JavaScript. + * + * See FormService.js. */ public static class Type { @@ -83,67 +91,51 @@ public class Field { */ private String name; - /** - * A human-readable name to be presented to the user. - */ - private String title; - /** * The type of this field. */ private String type; /** - * The value of this field, when checked. This is only applicable to - * BOOLEAN fields. + * A collection of all legal values of this field. */ - private String value; + private Collection options; /** - * A collection of all associated field options. - */ - private Collection options; - - /** - * Creates a new Parameter with no associated name, title, or type. + * Creates a new Parameter with no associated name or type. */ public Field() { } /** - * Creates a new Parameter with the given name, title, and type. + * Creates a new Field with the given name and type. * * @param name * The unique name to associate with this field. * - * @param title - * The human-readable title to associate with this field. - * * @param type * The type of this field. */ - public Field(String name, String title, String type) { - this.name = name; - this.title = title; - this.type = type; + public Field(String name, String type) { + this.name = name; + this.type = type; } /** - * Creates a new ENUM Parameter with the given name, title, and options. + * Creates a new Field with the given name, type, and possible values. * * @param name * The unique name to associate with this field. * - * @param title - * The human-readable title to associate with this field. + * @param type + * The type of this field. * * @param options * A collection of all possible valid options for this field. */ - public Field(String name, String title, Collection options) { + public Field(String name, String type, Collection options) { this.name = name; - this.title = title; - this.type = Type.ENUM; + this.type = type; this.options = options; } @@ -167,49 +159,6 @@ public class Field { this.name = name; } - /** - * Returns the human-readable title associated with this field. - * - * @return - * The human-readable title associated with this field. - */ - public String getTitle() { - return title; - } - - /** - * Sets the title associated with this field. The title must be a human- - * readable string which describes accurately this field. - * - * @param title - * A human-readable string describing this field. - */ - public void setTitle(String title) { - this.title = title; - } - - /** - * Returns the value that should be assigned to this field if enabled. This - * is only applicable to BOOLEAN fields. - * - * @return - * The value that should be assigned to this field if enabled. - */ - public String getValue() { - return value; - } - - /** - * Sets the value that should be assigned to this field if enabled. This is - * only applicable to BOOLEAN fields. - * - * @param value - * The value that should be assigned to this field if enabled. - */ - public void setValue(String value) { - this.value = value; - } - /** * Returns the type of this field. * @@ -232,25 +181,23 @@ public class Field { /** * Returns a mutable collection of field options. Changes to this - * collection directly affect the available options. This is only - * applicable to ENUM fields. + * collection directly affect the available options. * * @return * A mutable collection of field options, or null if the field has no * options. */ - public Collection getOptions() { + public Collection getOptions() { return options; } /** - * Sets the options available as possible values of this field. This is - * only applicable to ENUM fields. + * Sets the options available as possible values of this field. * * @param options * The options to associate with this field. */ - public void setOptions(Collection options) { + public void setOptions(Collection options) { this.options = options; } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Form.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Form.java index f85f755f3..3052d6283 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Form.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Form.java @@ -40,41 +40,32 @@ public class Form { */ private String name; - /** - * The a human-readable title describing this form. - */ - private String title; - /** * All fields associated with this form. */ private Collection fields; /** - * Creates a new Form object with no associated fields. The name and title - * of the form are left unset as null. If no form name is provided, this - * form must not be used in the same context as another unnamed form. + * Creates a new Form object with no associated fields. The name is left + * unset as null. If no form name is provided, this form must not be used + * in the same context as another unnamed form. */ public Form() { fields = new ArrayList(); } /** - * Creates a new Form object having the given name and title, and - * containing the given fields. + * Creates a new Form object having the given name and containing the given + * fields. * * @param name * A name which uniquely identifies this form. * - * @param title - * A human-readable title describing this form. - * * @param fields * The fields to provided within the new Form. */ - public Form(String name, String title, Collection fields) { + public Form(String name, Collection fields) { this.name = name; - this.title = title; this.fields = fields; } @@ -120,26 +111,4 @@ public class Form { this.name = name; } - /** - * Returns the human-readable title associated with this form. A form's - * title describes the form, but need not be unique. - * - * @return - * A human-readable title describing this form. - */ - public String getTitle() { - return title; - } - - /** - * Sets the human-readable title associated with this form. A form's title - * describes the form, but need not be unique. - * - * @param title - * A human-readable title describing this form. - */ - public void setTitle(String title) { - this.title = title; - } - } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/MultilineField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/MultilineField.java new file mode 100644 index 000000000..158eec786 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/MultilineField.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +/** + * Represents a field which can contain multiple lines of text. + * + * @author Michael Jumper + */ +public class MultilineField extends Field { + + /** + * Creates a new MultilineField with the given name. + * + * @param name + * The unique name to associate with this field. + */ + public MultilineField(String name) { + super(name, Field.Type.MULTILINE); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java new file mode 100644 index 000000000..5e06cb33e --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/NumericField.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +/** + * Represents a field which may contain only integer values. + * + * @author Michael Jumper + */ +public class NumericField extends Field { + + /** + * Creates a new NumericField with the given name. + * + * @param name + * The unique name to associate with this field. + */ + public NumericField(String name) { + super(name, Field.Type.NUMERIC); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/PasswordField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/PasswordField.java new file mode 100644 index 000000000..c48a6596b --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/PasswordField.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +/** + * Represents a field which contains sensitive text information related to + * authenticating a user. + * + * @author Michael Jumper + */ +public class PasswordField extends Field { + + /** + * Creates a new PasswordField with the given name. + * + * @param name + * The unique name to associate with this field. + */ + public PasswordField(String name) { + super(name, Field.Type.PASSWORD); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TextField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TextField.java new file mode 100644 index 000000000..75cae7bb6 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/TextField.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +/** + * Represents a basic text field. The field may generally contain any data, but + * may not contain multiple lines. + * + * @author Michael Jumper + */ +public class TextField extends Field { + + /** + * Creates a new TextField with the given name. + * + * @param name + * The unique name to associate with this field. + */ + public TextField(String name) { + super(name, Field.Type.TEXT); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/UsernameField.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/UsernameField.java new file mode 100644 index 000000000..9b2701df4 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/UsernameField.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.glyptodon.guacamole.form; + +/** + * Represents a text field which will contain the uniquely-identifying name of + * a user. + * + * @author Michael Jumper + */ +public class UsernameField extends Field { + + /** + * Creates a new UsernameField with the given name. + * + * @param name + * The unique name to associate with this field. + */ + public UsernameField(String name) { + super(name, Field.Type.USERNAME); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java index b68e06d5d..a0c471686 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java @@ -26,6 +26,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import org.glyptodon.guacamole.form.Field; +import org.glyptodon.guacamole.form.PasswordField; +import org.glyptodon.guacamole.form.UsernameField; /** * Information which describes a set of valid credentials. @@ -67,13 +69,25 @@ public class CredentialsInfo { */ public static final CredentialsInfo EMPTY = new CredentialsInfo(Collections.emptyList()); + /** + * A field describing the username HTTP parameter expected by Guacamole + * during login, if usernames are being used. + */ + public static final Field USERNAME = new UsernameField("username"); + + /** + * A field describing the password HTTP parameter expected by Guacamole + * during login, if passwords are being used. + */ + public static final Field PASSWORD = new PasswordField("password"); + /** * CredentialsInfo object which describes standard username/password * credentials. */ public static final CredentialsInfo USERNAME_PASSWORD = new CredentialsInfo(Arrays.asList( - new Field("username", "username", Field.Type.USERNAME), - new Field("password", "password", Field.Type.PASSWORD) + USERNAME, + PASSWORD )); } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolInfo.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolInfo.java index 69b7966f1..36d88b4a8 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolInfo.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolInfo.java @@ -35,11 +35,6 @@ import org.glyptodon.guacamole.form.Form; */ public class ProtocolInfo { - /** - * The human-readable title associated with this protocol. - */ - private String title; - /** * The unique name associated with this protocol. */ @@ -51,65 +46,37 @@ public class ProtocolInfo { private Collection
forms; /** - * Creates a new ProtocolInfo with no associated name, title, or - * forms. + * Creates a new ProtocolInfo with no associated name or forms. */ public ProtocolInfo() { this.forms = new ArrayList(); } /** - * Creates a new ProtocolInfo having the given name and title, but without - * any forms. + * Creates a new ProtocolInfo having the given name, but without any forms. * * @param name * The unique name associated with the protocol. - * - * @param title - * The human-readable title to associate with the protocol. */ - public ProtocolInfo(String name, String title) { + public ProtocolInfo(String name) { this.name = name; - this.title = title; this.forms = new ArrayList(); } /** - * Creates a new ProtocolInfo having the given name, title, and forms. + * Creates a new ProtocolInfo having the given name and forms. * * @param name * The unique name associated with the protocol. * - * @param title - * The human-readable title to associate with the protocol. - * * @param forms * The forms to associate with the protocol. */ - public ProtocolInfo(String name, String title, Collection forms) { + public ProtocolInfo(String name, Collection forms) { this.name = name; - this.title = title; this.forms = forms; } - /** - * Returns the human-readable title associated with this protocol. - * - * @return The human-readable title associated with this protocol. - */ - public String getTitle() { - return title; - } - - /** - * Sets the human-readable title associated with this protocol. - * - * @param title The human-readable title to associate with this protocol. - */ - public void setTitle(String title) { - this.title = title; - } - /** * Returns the unique name of this protocol. The protocol name is the * value required by the corresponding protocol plugin for guacd. diff --git a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/rdp.json b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/rdp.json index 0480c7528..739ab2af2 100644 --- a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/rdp.json +++ b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/rdp.json @@ -1,251 +1,156 @@ { - "title" : "RDP", "name" : "rdp", "forms" : [ { - "title" : "Network", "name" : "network", "fields" : [ { "name" : "hostname", - "title" : "Hostname", "type" : "TEXT" }, { "name" : "port", - "title" : "Port", "type" : "NUMERIC" } ] }, { - "title" : "Authentication", "name" : "authentication", "fields" : [ { "name" : "username", - "title" : "Username", "type" : "USERNAME" }, { "name" : "password", - "title" : "Password", "type" : "PASSWORD" }, { "name" : "domain", - "title" : "Domain", "type" : "TEXT" }, { "name" : "security", - "title" : "Security mode", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "rdp", - "title" : "RDP encryption" - }, - { - "value" : "tls", - "title" : "TLS encryption" - }, - { - "value" : "nla", - "title" : "NLA (Network Level Authentication)" - }, - { - "value" : "any", - "title" : "Any" - } - ] + "options" : [ "", "rdp", "tls", "nla", "any" ] }, { - "name" : "disable-auth", - "title" : "Disable authentication", - "type" : "BOOLEAN", - "value" : "true" + "name" : "disable-auth", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { - "name" : "ignore-cert", - "title" : "Ignore server certificate", - "type" : "BOOLEAN", - "value" : "true" + "name" : "ignore-cert", + "type" : "BOOLEAN", + "options" : [ "true" ] } ] }, { - "title" : "Basic Parameters", "name" : "basic-parameters", "fields" : [ { "name" : "initial-program", - "title" : "Initial program", "type" : "TEXT" }, { "name" : "client-name", - "title" : "Client name", "type" : "TEXT" }, { "name" : "server-layout", - "title" : "Keyboard layout", "type" : "ENUM", "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "en-us-qwerty", - "title" : "US English (Qwerty)" - }, - { - "value" : "fr-fr-azerty", - "title" : "French (Azerty)" - }, - { - "value" : "de-de-qwertz", - "title" : "German (Qwertz)" - }, - { - "value" : "it-it-qwerty", - "title" : "Italian (Qwerty)" - }, - { - "value" : "sv-se-qwerty", - "title" : "Swedish (Qwerty)" - }, - { - "value" : "failsafe", - "title" : "Unicode" - } + "", + "en-us-qwerty", + "fr-fr-azerty", + "de-de-qwertz", + "it-it-qwerty", + "sv-se-qwerty", + "failsafe" ] }, { - "name" : "console", - "title" : "Administrator console", - "type" : "BOOLEAN", - "value" : "true" + "name" : "console", + "type" : "BOOLEAN", + "options" : [ "true" ] } ] }, { - "title" : "Display", "name" : "display", "fields" : [ { "name" : "width", - "title" : "Display width", "type" : "NUMERIC" }, { "name" : "height", - "title" : "Display height", "type" : "NUMERIC" }, { "name" : "dpi", - "title" : "Display resolution (DPI)", "type" : "NUMERIC" }, { "name" : "color-depth", - "title" : "Color depth", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "8", - "title" : "256 color" - }, - { - "value" : "16", - "title" : "Low color (16-bit)" - }, - { - "value" : "24", - "title" : "True color (24-bit)" - }, - { - "value" : "32", - "title" : "True color (32-bit)" - } - ] + "options" : [ "", "8", "16", "24", "32" ] } ] }, { - "title" : "Device Redirection", "name" : "device-redirection", "fields" : [ { - "name" : "console-audio", - "title" : "Support audio in console", - "type" : "BOOLEAN", - "value" : "true" + "name" : "console-audio", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { - "name" : "disable-audio", - "title" : "Disable audio", - "type" : "BOOLEAN", - "value" : "true" + "name" : "disable-audio", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { - "name" : "enable-printing", - "title" : "Enable printing", - "type" : "BOOLEAN", - "value" : "true" + "name" : "enable-printing", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { - "name" : "enable-drive", - "title" : "Enable drive", - "type" : "BOOLEAN", - "value" : "true" + "name" : "enable-drive", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { "name" : "drive-path", - "title" : "Drive path", "type" : "TEXT" }, { "name" : "static-channels", - "title" : "Static channel names", "type" : "TEXT" } ] }, { - "title" : "RemoteApp", "name" : "remoteapp", "fields" : [ { "name" : "remote-app", - "title" : "RemoteApp program", "type" : "TEXT" }, { "name" : "remote-app-dir", - "title" : "RemoteApp working directory", "type" : "TEXT" }, { "name" : "remote-app-args", - "title" : "RemoteApp parameters", "type" : "TEXT" } ] diff --git a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/ssh.json b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/ssh.json index fa692975b..0e6fdb38b 100644 --- a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/ssh.json +++ b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/ssh.json @@ -1,140 +1,65 @@ { - "title" : "SSH", "name" : "ssh", "forms" : [ { - "title" : "Network", "name" : "network", "fields" : [ { "name" : "hostname", - "title" : "Hostname", "type" : "TEXT" }, { "name" : "port", - "title" : "Port", "type" : "NUMERIC" } ] }, { - "title" : "Authentication", "name" : "authentication", "fields" : [ { "name" : "username", - "title" : "Username", "type" : "USERNAME" }, { "name" : "password", - "title" : "Password", "type" : "PASSWORD" }, { "name" : "private-key", - "title" : "Private key", "type" : "MULTILINE" }, { "name" : "passphrase", - "title" : "Passphrase", "type" : "PASSWORD" } ] }, { - "title" : "Display", "name" : "display", "fields" : [ { "name" : "font-name", - "title" : "Font name", "type" : "TEXT" }, { "name" : "font-size", - "title" : "Font size", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "8", - "title" : "8" - }, - { - "value" : "9", - "title" : "9" - }, - { - "value" : "10", - "title" : "10" - }, - { - "value" : "11", - "title" : "11" - }, - { - "value" : "12", - "title" : "12" - }, - { - "value" : "14", - "title" : "14" - }, - { - "value" : "18", - "title" : "18" - }, - { - "value" : "24", - "title" : "24" - }, - { - "value" : "30", - "title" : "30" - }, - { - "value" : "36", - "title" : "36" - }, - { - "value" : "48", - "title" : "48" - }, - { - "value" : "60", - "title" : "60" - }, - { - "value" : "72", - "title" : "72" - }, - { - "value" : "96", - "title" : "96" - } - ] + "options" : [ "", "8", "9", "10", "11", "12", "14", "18", "24", "30", "36", "48", "60", "72", "96" ] } ] }, { - "title" : "SFTP", "name" : "sftp", "fields" : [ { - "name" : "enable-sftp", - "title" : "Enable SFTP", - "type" : "BOOLEAN", - "value" : "true" + "name" : "enable-sftp", + "type" : "BOOLEAN", + "options" : [ "true" ] } ] } diff --git a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/telnet.json b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/telnet.json index 12f30407f..d09457114 100644 --- a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/telnet.json +++ b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/telnet.json @@ -1,122 +1,50 @@ { - "title" : "Telnet", "name" : "telnet", "forms" : [ { - "title" : "Network", "name" : "network", "fields" : [ { "name" : "hostname", - "title" : "Hostname", "type" : "TEXT" }, { "name" : "port", - "title" : "Port", "type" : "NUMERIC" } ] }, { - "title" : "Authentication", "name" : "authentication", "fields" : [ { "name" : "username", - "title" : "Username", "type" : "USERNAME" }, { "name" : "password", - "title" : "Password", "type" : "PASSWORD" }, { "name" : "password-regex", - "title" : "Password regular expression", "type" : "TEXT" } ] }, { - "title" : "Display", "name" : "display", "fields" : [ { "name" : "font-name", - "title" : "Font name", "type" : "TEXT" }, { "name" : "font-size", - "title" : "Font size", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "8", - "title" : "8" - }, - { - "value" : "9", - "title" : "9" - }, - { - "value" : "10", - "title" : "10" - }, - { - "value" : "11", - "title" : "11" - }, - { - "value" : "12", - "title" : "12" - }, - { - "value" : "14", - "title" : "14" - }, - { - "value" : "18", - "title" : "18" - }, - { - "value" : "24", - "title" : "24" - }, - { - "value" : "30", - "title" : "30" - }, - { - "value" : "36", - "title" : "36" - }, - { - "value" : "48", - "title" : "48" - }, - { - "value" : "60", - "title" : "60" - }, - { - "value" : "72", - "title" : "72" - }, - { - "value" : "96", - "title" : "96" - } - ] + "options" : [ "", "8", "9", "10", "11", "12", "14", "18", "24", "30", "36", "48", "60", "72", "96" ] } ] } diff --git a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/vnc.json b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/vnc.json index c7d51d034..a1dc13d8f 100644 --- a/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/vnc.json +++ b/guacamole-ext/src/main/resources/org/glyptodon/guacamole/protocols/vnc.json @@ -1,132 +1,81 @@ { - "title" : "VNC", "name" : "vnc", "forms" : [ { - "title" : "Network", "name" : "network", "fields" : [ { "name" : "hostname", - "title" : "Hostname", "type" : "TEXT" }, { "name" : "port", - "title" : "Port", "type" : "NUMERIC" } ] }, { - "title" : "Authentication", "name" : "authentication", "fields" : [ { "name" : "password", - "title" : "Password", "type" : "PASSWORD" } ] }, { - "title" : "Display", "name" : "display", "fields" : [ { - "name" : "read-only", - "title" : "Read-only", - "type" : "BOOLEAN", - "value" : "true" + "name" : "read-only", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { - "name" : "swap-red-blue", - "title" : "Swap red/blue components", - "type" : "BOOLEAN", - "value" : "true" + "name" : "swap-red-blue", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { "name" : "cursor", - "title" : "Cursor", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "local", - "title" : "Local" - }, - { - "value" : "remote", - "title" : "Remote" - } - ] + "options" : [ "", "local", "remote" ] }, { "name" : "color-depth", - "title" : "Color depth", "type" : "ENUM", - "options" : [ - { - "value" : "", - "title" : "" - }, - { - "value" : "8", - "title" : "256 color" - }, - { - "value" : "16", - "title" : "Low color (16-bit)" - }, - { - "value" : "24", - "title" : "True color (24-bit)" - }, - { - "value" : "32", - "title" : "True color (32-bit)" - } - ] + "options" : [ "", "8", "16", "24", "32" ] } ] }, { - "title" : "Repeater", "name" : "repeater", "fields" : [ { "name" : "dest-host", - "title" : "Repeater destination host", "type" : "TEXT" }, { "name" : "dest-port", - "title" : "Repeater destination port", "type" : "NUMERIC" } ] }, { - "title" : "Audio", "name" : "audio", "fields" : [ { - "name" : "enable-audio", - "title" : "Enable audio", - "type" : "BOOLEAN", - "value" : "true" + "name" : "enable-audio", + "type" : "BOOLEAN", + "options" : [ "true" ] }, { "name" : "audio-servername", - "title" : "Audio server name", "type" : "TEXT" } ] diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 6671052ed..cece87b17 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -36,7 +36,8 @@ * Failed logins may also result in guacInsufficientCredentials or * guacInvalidCredentials events, if the provided credentials were rejected for * being insufficient or invalid respectively. Both events will be provided - * the set of parameters originally given to authenticate() and the set of + * the set of parameters originally given to authenticate() and the error that + * rejected the credentials. The Error object provided will contain set of * expected credentials returned by the REST endpoint. This set of credentials * will be in the form of a Field array. */ @@ -154,13 +155,13 @@ angular.module('auth').factory('authenticationService', ['$injector', // Request credentials if provided credentials were invalid if (error.type === Error.Type.INVALID_CREDENTIALS) - $rootScope.$broadcast('guacInvalidCredentials', parameters, error.expected); + $rootScope.$broadcast('guacInvalidCredentials', parameters, error); // Request more credentials if provided credentials were not enough else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) - $rootScope.$broadcast('guacInsufficientCredentials', parameters, error.expected); + $rootScope.$broadcast('guacInsufficientCredentials', parameters, error); - authenticationProcess.reject(); + authenticationProcess.reject(error); }); return authenticationProcess.promise; diff --git a/guacamole/src/main/webapp/app/form/controllers/checkboxFieldController.js b/guacamole/src/main/webapp/app/form/controllers/checkboxFieldController.js index eb15d37a5..3f9da9534 100644 --- a/guacamole/src/main/webapp/app/form/controllers/checkboxFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/checkboxFieldController.js @@ -29,12 +29,12 @@ angular.module('form').controller('checkboxFieldController', ['$scope', // Update typed value when model is changed $scope.$watch('model', function modelChanged(model) { - $scope.typedValue = (model === $scope.field.value); + $scope.typedValue = (model === $scope.field.options[0]); }); // Update string value in model when typed value is changed $scope.$watch('typedValue', function typedValueChanged(typedValue) { - $scope.model = (typedValue ? $scope.field.value : ''); + $scope.model = (typedValue ? $scope.field.options[0] : ''); }); }]); diff --git a/guacamole/src/main/webapp/app/form/controllers/selectFieldController.js b/guacamole/src/main/webapp/app/form/controllers/selectFieldController.js index 593cc716b..f3af8d821 100644 --- a/guacamole/src/main/webapp/app/form/controllers/selectFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/selectFieldController.js @@ -30,6 +30,12 @@ angular.module('form').controller('selectFieldController', ['$scope', '$injector // Required services var translationStringService = $injector.get('translationStringService'); + // Interpret undefined/null as empty string + $scope.$watch('model', function setModel(model) { + if (!model && model !== '') + $scope.model = ''; + }); + /** * Produces the translation string for the given field option * value. The translation string will be of the form: diff --git a/guacamole/src/main/webapp/app/form/templates/passwordField.html b/guacamole/src/main/webapp/app/form/templates/passwordField.html index 644566f8e..506d8b6b2 100644 --- a/guacamole/src/main/webapp/app/form/templates/passwordField.html +++ b/guacamole/src/main/webapp/app/form/templates/passwordField.html @@ -1,4 +1,4 @@
- +
\ No newline at end of file diff --git a/guacamole/src/main/webapp/app/form/templates/selectField.html b/guacamole/src/main/webapp/app/form/templates/selectField.html index 9df1f0769..3bd2bb876 100644 --- a/guacamole/src/main/webapp/app/form/templates/selectField.html +++ b/guacamole/src/main/webapp/app/form/templates/selectField.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index cd0897b2f..3980568d1 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -36,11 +36,30 @@ angular.module('index').controller('indexController', ['$scope', '$injector', */ $scope.guacNotification = guacNotification; + /** + * The message to display to the user as instructions for the login + * process. + * + * @type String + */ + $scope.loginHelpText = null; + + /** + * The credentials that the authentication service is has already accepted, + * pending additional credentials, if any. If the user is logged in, or no + * credentials have been accepted, this will be null. If credentials have + * been accepted, this will be a map of name/value pairs corresponding to + * the parameters submitted in a previous authentication attempt. + * + * @type Object. + */ + $scope.acceptedCredentials = null; + /** * The credentials that the authentication service is currently expecting, * if any. If the user is logged in, this will be null. * - * @type Form[]|Form|Field[]|Field + * @type Field[] */ $scope.expectedCredentials = null; @@ -109,21 +128,27 @@ angular.module('index').controller('indexController', ['$scope', '$injector', }; // Display login screen if a whole new set of credentials is needed - $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, expected) { + $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, error) { $scope.page.title = 'APP.NAME'; $scope.page.bodyClassName = ''; - $scope.expectedCredentials = expected; + $scope.loginHelpText = null; + $scope.acceptedCredentials = {}; + $scope.expectedCredentials = error.expected; }); // Prompt for remaining credentials if provided credentials were not enough - $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, expected) { + $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, error) { $scope.page.title = 'APP.NAME'; $scope.page.bodyClassName = ''; - $scope.expectedCredentials = expected; + $scope.loginHelpText = error.message; + $scope.acceptedCredentials = parameters; + $scope.expectedCredentials = error.expected; }); // Clear login screen if login was successful $scope.$on('guacLogin', function loginSuccessful() { + $scope.loginHelpText = null; + $scope.acceptedCredentials = null; $scope.expectedCredentials = null; }); diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js index 1b8b407ca..0c836c05a 100644 --- a/guacamole/src/main/webapp/app/login/directives/login.js +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -35,13 +35,28 @@ angular.module('login').directive('guacLogin', [function guacLogin() { // Login directive scope directive.scope = { + /** + * An optional instructional message to display within the login + * dialog. + * + * @type String + */ + helpText : '=', + /** * The login form or set of fields. This will be displayed to the user * to capture their credentials. * - * @type Form[]|Form|Field[]|Field + * @type Field[] */ - form : '=' + form : '=', + + /** + * A map of all field name/value pairs that have already been provided. + * If not null, the user will be prompted to continue their login + * attempt using only the fields which remain. + */ + values : '=' }; @@ -49,21 +64,78 @@ angular.module('login').directive('guacLogin', [function guacLogin() { directive.controller = ['$scope', '$injector', function loginController($scope, $injector) { + // Required types + var Error = $injector.get('Error'); + var Field = $injector.get('Field'); + // Required services var $route = $injector.get('$route'); var authenticationService = $injector.get('authenticationService'); /** - * Whether an error occurred during login. - * - * @type Boolean + * A description of the error that occurred during login, if any. + * + * @type String */ $scope.loginError = false; /** - * All form values entered by the user. + * All form values entered by the user, as parameter name/value pairs. + * + * @type Object. */ - $scope.values = {}; + $scope.enteredValues = {}; + + /** + * All form fields which have not yet been filled by the user. + * + * @type Field[] + */ + $scope.remainingFields = []; + + /** + * Returns whether a previous login attempt is continuing. + * + * @return {Boolean} + * true if a previous login attempt is continuing, false otherwise. + */ + $scope.isContinuation = function isContinuation() { + + // The login is continuing if any parameter values are provided + for (var name in $scope.values) + return true; + + return false; + + }; + + // Ensure provided values are included within entered values, even if + // they have no corresponding input fields + $scope.$watch('values', function resetEnteredValues(values) { + angular.extend($scope.enteredValues, values || {}); + }); + + // Update field information when form is changed + $scope.$watch('form', function resetRemainingFields(fields) { + + // If no fields are provided, then no fields remain + if (!fields) { + $scope.remainingFields = []; + return; + } + + // Filter provided fields against provided values + $scope.remainingFields = fields.filter(function isRemaining(field) { + return !(field.name in $scope.values); + }); + + // Set default values for all unset fields + angular.forEach($scope.remainingFields, function setDefault(field) { + if (!$scope.enteredValues[field.name]) + $scope.enteredValues[field.name] = ''; + }); + + }); /** * Submits the currently-specified username and password to the @@ -71,20 +143,42 @@ angular.module('login').directive('guacLogin', [function guacLogin() { */ $scope.login = function login() { + // Start with cleared status + $scope.loginError = null; + // Attempt login once existing session is destroyed - authenticationService.authenticate($scope.values) + authenticationService.authenticate($scope.enteredValues) // Clear and reload upon success .then(function loginSuccessful() { - $scope.loginError = false; - $scope.values = {}; + $scope.enteredValues = {}; $route.reload(); }) // Reset upon failure - ['catch'](function loginFailed() { - $scope.loginError = true; - $scope.values.password = ''; + ['catch'](function loginFailed(error) { + + // Clear out passwords if the credentials were rejected for any reason + if (error.type !== Error.Type.INSUFFICIENT_CREDENTIALS) { + + // Flag generic error for invalid login + if (error.type === Error.Type.INVALID_CREDENTIALS) + $scope.loginError = 'LOGIN.ERROR_INVALID_LOGIN'; + + // Display error if anything else goes wrong + else + $scope.loginError = error.message; + + // Clear all visible password fields + angular.forEach($scope.remainingFields, function clearEnteredValueIfPassword(field) { + + // Remove entered value only if field is a password field + if (field.type === Field.Type.PASSWORD && field.name in $scope.enteredValues) + $scope.enteredValues[field.name] = ''; + + }); + } + }); }; diff --git a/guacamole/src/main/webapp/app/login/styles/dialog.css b/guacamole/src/main/webapp/app/login/styles/dialog.css index c2d3a9709..56886c2bc 100644 --- a/guacamole/src/main/webapp/app/login/styles/dialog.css +++ b/guacamole/src/main/webapp/app/login/styles/dialog.css @@ -86,3 +86,15 @@ max-width: 3em; margin: 0.5em auto; } + +.login-ui.continuation div.login-dialog { + border-right: none; + border-left: none; + box-shadow: none; + max-width: 6in; +} + +.login-ui.continuation .login-dialog .logo, +.login-ui.continuation .login-dialog .version { + display: none; +} diff --git a/guacamole/src/main/webapp/app/login/styles/input.css b/guacamole/src/main/webapp/app/login/styles/input.css index d56ae5b12..fd4d70527 100644 --- a/guacamole/src/main/webapp/app/login/styles/input.css +++ b/guacamole/src/main/webapp/app/login/styles/input.css @@ -38,4 +38,13 @@ .login-ui .login-dialog .buttons input[type="submit"] { width: 100%; margin: 0; -} \ No newline at end of file +} + +.login-ui.continuation .login-dialog .buttons input[type="submit"] { + width: auto; +} + +.login-ui.initial .login-dialog input.continue-login, +.login-ui.continuation .login-dialog input.login { + display: none; +} diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index ac6879a14..3de86f507 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -1,4 +1,4 @@ - diff --git a/guacamole/src/main/webapp/app/rest/types/Field.js b/guacamole/src/main/webapp/app/rest/types/Field.js index 66b3aea71..77dd8fff7 100644 --- a/guacamole/src/main/webapp/app/rest/types/Field.js +++ b/guacamole/src/main/webapp/app/rest/types/Field.js @@ -46,13 +46,6 @@ angular.module('rest').factory('Field', [function defineField() { */ this.name = template.name; - /** - * A human-readable name for this parameter. - * - * @type String - */ - this.title = template.title; - /** * The type string defining which values this parameter may contain, * as well as what properties are applicable. Valid types are listed @@ -64,18 +57,9 @@ angular.module('rest').factory('Field', [function defineField() { this.type = template.type || Field.Type.TEXT; /** - * The value to set the parameter to, in the case of a BOOLEAN - * parameter, to enable that parameter's effect. + * All possible legal values for this parameter. * - * @type String - */ - this.value = template.value; - - /** - * All possible legal values for this parameter. This property is only - * applicable to ENUM type parameters. - * - * @type FieldOption[] + * @type String[] */ this.options = template.options; @@ -123,7 +107,9 @@ angular.module('rest').factory('Field', [function defineField() { /** * The type string associated with parameters that may contain only a * single possible value, where that value enables the parameter's - * effect. + * effect. It is assumed that each BOOLEAN field will provide exactly + * one possible value (option), which will be the value if that field + * is true. * * @type String */ diff --git a/guacamole/src/main/webapp/app/rest/types/Form.js b/guacamole/src/main/webapp/app/rest/types/Form.js index a402ccc07..d73604426 100644 --- a/guacamole/src/main/webapp/app/rest/types/Form.js +++ b/guacamole/src/main/webapp/app/rest/types/Form.js @@ -40,21 +40,13 @@ angular.module('rest').factory('Form', [function defineForm() { template = template || {}; /** - * The name which uniquely identifies this parameter, or null if this - * field has no name. + * The name which uniquely identifies this form, or null if this form + * has no name. * * @type String */ this.name = template.name; - /** - * A human-readable name for this form, or null if this form has no - * name. - * - * @type String - */ - this.title = template.title; - /** * All fields contained within this form. * diff --git a/guacamole/src/main/webapp/app/rest/types/Protocol.js b/guacamole/src/main/webapp/app/rest/types/Protocol.js index 73e66506c..fff1049d6 100644 --- a/guacamole/src/main/webapp/app/rest/types/Protocol.js +++ b/guacamole/src/main/webapp/app/rest/types/Protocol.js @@ -46,13 +46,6 @@ angular.module('rest').factory('Protocol', [function defineProtocol() { */ this.name = template.name; - /** - * A human-readable name for this protocol. - * - * @type String - */ - this.title = template.title; - /** * An array of forms containing all known parameters for this protocol, * their types, and other information. diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 3303ec1a8..6261ec533 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -50,7 +50,10 @@ - + diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 08d050a10..8f634dca7 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -7,6 +7,7 @@ "ACTION_ACKNOWLEDGE" : "OK", "ACTION_CANCEL" : "Cancel", "ACTION_CLONE" : "Clone", + "ACTION_CONTINUE" : "Continue", "ACTION_DELETE" : "Delete", "ACTION_DELETE_SESSIONS" : "Kill Sessions", "ACTION_LOGIN" : "Login", @@ -23,6 +24,7 @@ "DIALOG_HEADER_ERROR" : "Error", + "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", "ERROR_PASSWORD_MISMATCH" : "The provided passwords do not match.", "FIELD_HEADER_PASSWORD" : "Password:", @@ -143,7 +145,11 @@ "LOGIN": { - "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE", + "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_INVALID_LOGIN" : "Invalid Login", @@ -161,7 +167,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Location:", "FIELD_HEADER_NAME" : "Name:", @@ -194,7 +200,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection Group", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Location:", "FIELD_HEADER_NAME" : "Name:", @@ -216,8 +222,8 @@ "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", - "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", @@ -414,7 +420,7 @@ "ACTION_NEW_CONNECTION" : "New Connection", "ACTION_NEW_CONNECTION_GROUP" : "New Group", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.", @@ -432,7 +438,7 @@ "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", "FIELD_HEADER_LANGUAGE" : "Display language:", @@ -469,7 +475,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_NEW_USER" : "New User", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_USERS" : "Click or tap on a user below to manage that user. Depending on your access level, users can be added and deleted, and their passwords can be changed.", @@ -484,7 +490,7 @@ "ACTION_DELETE" : "Kill Sessions", "DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions", - "DIALOG_HEADER_ERROR" : "Error", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_PLACEHOLDER_FILTER" : "Filter", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index ba7e6d78a..dd3947b83 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -23,6 +23,7 @@ "DIALOG_HEADER_ERROR" : "Ошибка", + "ERROR_PASSWORD_BLANK" : "Пароль не может быть пустым.", "ERROR_PASSWORD_MISMATCH" : "Указанные пароли не совпадают.", "FIELD_HEADER_PASSWORD" : "Пароль:", @@ -143,7 +144,10 @@ "LOGIN": { - "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_LOGIN" : "@:APP.ACTION_LOGIN", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_INVALID_LOGIN" : "Неверные данные для входа", @@ -161,7 +165,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить подключение", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Размещение:", "FIELD_HEADER_NAME" : "Название:", @@ -194,7 +198,7 @@ "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить группу подключений", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_LOCATION" : "Размещение:", "FIELD_HEADER_NAME" : "Название:", @@ -216,8 +220,8 @@ "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", - "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить пользователя", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_CONFIRM_DELETE" : "Удалить пользователя", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", @@ -393,7 +397,7 @@ "ACTION_NEW_CONNECTION" : "Новое подключение", "ACTION_NEW_CONNECTION_GROUP" : "Новая группа", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_CONNECTIONS" : "Нажмите на подключение, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление подключений, а также изменение их свойств (протокол, название сервера, порт и пр.).", @@ -409,9 +413,9 @@ "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_UPDATE_PASSWORD" : "@:APP.ACTION_UPDATE_PASSWORD", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "ERROR_PASSWORD_BLANK" : "Пароль не может быть пустым.", + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", "FIELD_HEADER_LANGUAGE" : "Язык:", @@ -448,7 +452,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_NEW_USER" : "Новый пользователь", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "HELP_USERS" : "Нажмите на пользователя, чтобы управлять им. В зависимости от прав доступа возможно добавление и удаление пользователей, а также изменение паролей.", @@ -463,7 +467,7 @@ "ACTION_DELETE" : "Завершить сессии", "DIALOG_HEADER_CONFIRM_DELETE" : "Завершение сессий", - "DIALOG_HEADER_ERROR" : "Ошибка", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_PLACEHOLDER_FILTER" : "Фильтр",