diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Parameter.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Parameter.java new file mode 100644 index 000000000..033749b88 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/Parameter.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013 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.ArrayList; +import java.util.Collection; + +/** + * Represents an arbitrary parameter, such as an HTTP parameter, or the + * parameter of a remote desktop protocol. + * + * @author Michael Jumper + */ +public class Parameter { + + /** + * All possible types of parameter. + */ + public enum Type { + + /** + * A text parameter, accepting arbitrary values. + */ + TEXT, + + /** + * A username parameter. This parameter type generally behaves + * identically to arbitrary text parameters, but has semantic + * differences. If credential pass-through is in use, the value for this + * parameter may be automatically provided using the credentials + * originally used by the user to authenticate. + */ + USERNAME, + + /** + * A password parameter, whose value is sensitive and must be hidden. If + * credential pass-through is in use, the value for this parameter may + * be automatically provided using the credentials originally used by + * the user to authenticate. + */ + PASSWORD, + + /** + * A numeric parameter, whose value must contain only digits. + */ + NUMERIC, + + /** + * A boolean parameter, whose value is either blank or "true". + */ + BOOLEAN, + + /** + * An enumerated parameter, whose legal values are fully enumerated + * by a provided, finite list. + */ + ENUM, + + /** + * A text parameter that can span more than one line. + */ + MULTILINE + + } + + /** + * The unique name that identifies this parameter. + */ + private String name; + + /** + * A human-readable name to be presented to the user. + */ + private String title; + + /** + * The type of this parameter. + */ + private Type type; + + /** + * The value of this parameter, when checked. This is only applicable to + * BOOLEAN parameters. + */ + private String value; + + /** + * A collection of all associated parameter options. + */ + private Collection options; + + /** + * Creates a new Parameter with no associated name, title, or type. + */ + public Parameter() { + this.options = new ArrayList(); + } + + /** + * Creates a new Parameter with the given name, title, and type. + * + * @param name + * The unique name to associate with this parameter. + * + * @param title + * The human-readable title to associate with this parameter. + * + * @param type + * The type of this parameter. + */ + public Parameter(String name, String title, Type type) { + this.name = name; + this.title = title; + this.type = type; + this.options = new ArrayList(); + } + + /** + * Creates a new BOOLEAN Parameter with the given name, title, and value. + * + * @param name + * The unique name to associate with this parameter. + * + * @param title + * The human-readable title to associate with this parameter. + * + * @param value + * The value that should be assigned to this parameter if enabled. + */ + public Parameter(String name, String title, String value) { + this.name = name; + this.title = title; + this.type = Type.BOOLEAN; + this.value = value; + this.options = new ArrayList(); + } + + /** + * Creates a new ENUM Parameter with the given name, title, and options. + * + * @param name + * The unique name to associate with this parameter. + * + * @param title + * The human-readable title to associate with this parameter. + * + * @param options + * A collection of all possible valid options for this parameter. + */ + public Parameter(String name, String title, Collection options) { + this.name = name; + this.title = title; + this.type = Type.ENUM; + this.options = options; + } + + /** + * Returns the unique name associated with this parameter. + * + * @return + * The unique name associated with this parameter. + */ + public String getName() { + return name; + } + + /** + * Sets the unique name associated with this parameter. + * + * @param name + * The unique name to assign to this parameter. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the human-readable title associated with this parameter. + * + * @return + * The human-readable title associated with this parameter. + */ + public String getTitle() { + return title; + } + + /** + * Sets the title associated with this parameter. The title must be a + * human-readable string which describes accurately this parameter. + * + * @param title + * A human-readable string describing this parameter. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Returns the value that should be assigned to this parameter if enabled. + * This is only applicable to BOOLEAN parameters. + * + * @return + * The value that should be assigned to this parameter if enabled. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that should be assigned to this parameter if enabled. + * This is only applicable to BOOLEAN parameters. + * + * @param value + * The value that should be assigned to this parameter if enabled. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Returns the type of this parameter. + * + * @return + * The type of this parameter. + */ + public Type getType() { + return type; + } + + /** + * Sets the type of this parameter. + * + * @param type + * The type of this parameter. + */ + public void setType(Type type) { + this.type = type; + } + + /** + * Returns a mutable collection of parameter options. Changes to this + * collection directly affect the available options. This is only + * applicable to ENUM parameters. + * + * @return + * A mutable collection of parameter options. + */ + public Collection getOptions() { + return options; + } + + /** + * Sets the options available as possible values of this parameter. This + * is only applicable to ENUM parameters. + * + * @param options + * The options to associate with this parameter. + */ + public void setOptions(Collection options) { + this.options = options; + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameterOption.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/ParameterOption.java similarity index 60% rename from guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameterOption.java rename to guacamole-ext/src/main/java/org/glyptodon/guacamole/form/ParameterOption.java index 52d233a16..a722749bf 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameterOption.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/form/ParameterOption.java @@ -20,18 +20,17 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.protocols; +package org.glyptodon.guacamole.form; /** - * Describes an available legal value for an enumerated protocol parameter. + * Describes an available legal value for an enumerated parameter. * * @author Michael Jumper */ -public class ProtocolParameterOption { +public class ParameterOption { /** - * The value that will be sent to the client plugin if this option is - * chosen. + * The value that will be assigned if this option is chosen. */ private String value; @@ -41,20 +40,40 @@ public class ProtocolParameterOption { private String title; /** - * Returns the value that will be sent to the client plugin if this option - * is chosen. + * Creates a new ParameterOption with no associated value or title. + */ + public ParameterOption() { + } + + /** + * Creates a new ParameterOption having the given value and title. * - * @return The value that will be sent if this option is chosen. + * @param value + * The value to assign if this option is chosen. + * + * @param title + * The human-readable title to associate with this option. + */ + public ParameterOption(String value, String title) { + this.value = value; + this.title = title; + } + + /** + * Returns the value that will be assigned if this option is chosen. + * + * @return + * The value that will be assigned if this option is chosen. */ public String getValue() { return value; } /** - * Sets the value that will be sent to the client plugin if this option is - * chosen. + * Sets the value that will be assigned if this option is chosen. * - * @param value The value to send if this option is chosen. + * @param value + * The value to assign if this option is chosen. */ public void setValue(String value) { this.value = value; @@ -62,7 +81,9 @@ public class ProtocolParameterOption { /** * Returns the human-readable title describing the effect of this option. - * @return The human-readable title describing the effect of this option. + * + * @return + * The human-readable title describing the effect of this option. */ public String getTitle() { return title; @@ -70,7 +91,9 @@ public class ProtocolParameterOption { /** * Sets the human-readable title describing the effect of this option. - * @param title A human-readable title describing the effect of this option. + * + * @param title + * A human-readable title describing the effect of this option. */ public void setTitle(String title) { this.title = title; 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 new file mode 100644 index 000000000..91a415fba --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/CredentialsInfo.java @@ -0,0 +1,79 @@ +/* + * 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.net.auth.credentials; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import org.glyptodon.guacamole.form.Parameter; + +/** + * Information which describes a set of valid credentials. + * + * @author Michael Jumper + */ +public class CredentialsInfo { + + /** + * All parameters required for valid credentials. + */ + private final Collection parameters; + + /** + * Creates a new CredentialsInfo object which requires the given parameters + * for any conforming credentials. + * + * @param parameters + * The parameters to require. + */ + public CredentialsInfo(Collection parameters) { + this.parameters = parameters; + } + + /** + * Returns all parameters required for valid credentials as described by + * this object. + * + * @return + * All parameters required for valid credentials. + */ + public Collection getParameters() { + return Collections.unmodifiableCollection(parameters); + } + + /** + * CredentialsInfo object which describes empty credentials. No parameters + * are required. + */ + public static final CredentialsInfo EMPTY = new CredentialsInfo(Collections.EMPTY_LIST); + + /** + * CredentialsInfo object which describes standard username/password + * credentials. + */ + public static final CredentialsInfo USERNAME_PASSWORD = new CredentialsInfo(Arrays.asList( + new Parameter("username", "username", Parameter.Type.USERNAME), + new Parameter("password", "password", Parameter.Type.PASSWORD) + )); + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleCredentialsException.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleCredentialsException.java new file mode 100644 index 000000000..55fffea34 --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleCredentialsException.java @@ -0,0 +1,100 @@ +/* + * 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.net.auth.credentials; + +import org.glyptodon.guacamole.GuacamoleSecurityException; + +/** + * A security-related exception thrown when access is denied to a user because + * of a problem related to the provided credentials. Additional information + * describing the form of valid credentials is provided. + * + * @author Michael Jumper + */ +public class GuacamoleCredentialsException extends GuacamoleSecurityException { + + /** + * Information describing the form of valid credentials. + */ + private final CredentialsInfo credentialsInfo; + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given + * message, cause, and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleCredentialsException(String message, Throwable cause, + CredentialsInfo credentialsInfo) { + super(message, cause); + this.credentialsInfo = credentialsInfo; + } + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given + * message and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleCredentialsException(String message, CredentialsInfo credentialsInfo) { + super(message); + this.credentialsInfo = credentialsInfo; + } + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given cause + * and associated credential information. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleCredentialsException(Throwable cause, CredentialsInfo credentialsInfo) { + super(cause); + this.credentialsInfo = credentialsInfo; + } + + /** + * Returns information describing the form of valid credentials. + * + * @return + * Information describing the form of valid credentials. + */ + public CredentialsInfo getCredentialsInfo() { + return credentialsInfo; + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInsufficientCredentialsException.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInsufficientCredentialsException.java new file mode 100644 index 000000000..ea8675a3b --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInsufficientCredentialsException.java @@ -0,0 +1,82 @@ +/* + * 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.net.auth.credentials; + +/** + * A security-related exception thrown when access is denied to a user because + * the provided credentials are not sufficient for authentication to succeed. + * The validity or invalidity of the given credentials is not specified, and + * more information is needed before a decision can be made. Additional + * information describing the form of valid credentials is provided. + * + * @author Michael Jumper + */ +public class GuacamoleInsufficientCredentialsException extends GuacamoleCredentialsException { + + /** + * Creates a new GuacamoleInsufficientCredentialsException with the given + * message, cause, and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInsufficientCredentialsException(String message, Throwable cause, + CredentialsInfo credentialsInfo) { + super(message, cause, credentialsInfo); + } + + /** + * Creates a new GuacamoleInsufficientCredentialsException with the given + * message and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInsufficientCredentialsException(String message, CredentialsInfo credentialsInfo) { + super(message, credentialsInfo); + } + + /** + * Creates a new GuacamoleInsufficientCredentialsException with the given + * cause and associated credential information. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInsufficientCredentialsException(Throwable cause, CredentialsInfo credentialsInfo) { + super(cause, credentialsInfo); + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInvalidCredentialsException.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInvalidCredentialsException.java new file mode 100644 index 000000000..903b82bef --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/credentials/GuacamoleInvalidCredentialsException.java @@ -0,0 +1,80 @@ +/* + * 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.net.auth.credentials; + +/** + * A security-related exception thrown when access is denied to a user because + * the provided credentials are invalid. Additional information describing + * the form of valid credentials is provided. + * + * @author Michael Jumper + */ +public class GuacamoleInvalidCredentialsException extends GuacamoleCredentialsException { + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given + * message, cause, and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInvalidCredentialsException(String message, Throwable cause, + CredentialsInfo credentialsInfo) { + super(message, cause, credentialsInfo); + } + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given + * message and associated credential information. + * + * @param message + * A human readable description of the exception that occurred. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInvalidCredentialsException(String message, CredentialsInfo credentialsInfo) { + super(message, credentialsInfo); + } + + /** + * Creates a new GuacamoleInvalidCredentialsException with the given cause + * and associated credential information. + * + * @param cause + * The cause of this exception. + * + * @param credentialsInfo + * Information describing the form of valid credentials. + */ + public GuacamoleInvalidCredentialsException(Throwable cause, CredentialsInfo credentialsInfo) { + super(cause, credentialsInfo); + } + +} 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 ad3b5e7d4..a5a9c3839 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 @@ -24,6 +24,7 @@ package org.glyptodon.guacamole.protocols; import java.util.ArrayList; import java.util.Collection; +import org.glyptodon.guacamole.form.Parameter; /** * Describes a protocol and all parameters associated with it, as required by @@ -47,8 +48,49 @@ public class ProtocolInfo { /** * A collection of all associated protocol parameters. */ - private Collection parameters = - new ArrayList(); + private Collection parameters; + + /** + * Creates a new ProtocolInfo with no associated name, title, or + * parameters. + */ + public ProtocolInfo() { + this.parameters = new ArrayList(); + } + + /** + * Creates a new ProtocolInfo having the given name and title, but without + * any parameters. + * + * @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) { + this.name = name; + this.title = title; + this.parameters = new ArrayList(); + } + + /** + * Creates a new ProtocolInfo having the given name, title, and parameters. + * + * @param name + * The unique name associated with the protocol. + * + * @param title + * The human-readable title to associate with the protocol. + * + * @param parameters + * The parameters to associate with the protocol. + */ + public ProtocolInfo(String name, String title, Collection parameters) { + this.name = name; + this.title = title; + this.parameters = parameters; + } /** * Returns the human-readable title associated with this protocol. @@ -95,8 +137,19 @@ public class ProtocolInfo { * * @return A mutable collection of protocol parameters. */ - public Collection getParameters() { + public Collection getParameters() { return parameters; } + /** + * Sets the collection of protocol parameters associated with this + * protocol. + * + * @param parameters + * The collection of parameters to associate with this protocol. + */ + public void setParameters(Collection parameters) { + this.parameters = parameters; + } + } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameter.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameter.java deleted file mode 100644 index 40b59a497..000000000 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/protocols/ProtocolParameter.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2013 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.protocols; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Represents a parameter of a protocol. - * - * @author Michael Jumper - */ -public class ProtocolParameter { - - /** - * All possible types of protocol parameter. - */ - public enum Type { - - /** - * A text parameter, accepting arbitrary values. - */ - TEXT, - - /** - * A username parameter. This parameter type generally behaves - * identically to arbitrary text parameters, but has semantic - * differences. If credential pass-through is in use, the value for this - * parameter may be automatically provided using the credentials - * originally used by the user to authenticate. - */ - USERNAME, - - /** - * A password parameter, whose value is sensitive and must be hidden. If - * credential pass-through is in use, the value for this parameter may - * be automatically provided using the credentials originally used by - * the user to authenticate. - */ - PASSWORD, - - /** - * A numeric parameter, whose value must contain only digits. - */ - NUMERIC, - - /** - * A boolean parameter, whose value is either blank or "true". - */ - BOOLEAN, - - /** - * An enumerated parameter, whose legal values are fully enumerated - * by a provided, finite list. - */ - ENUM, - - /** - * A text parameter that can span more than one line. - */ - MULTILINE - - } - - /** - * The unique name that identifies this parameter to the protocol plugin. - */ - private String name; - - /** - * A human-readable name to be presented to the user. - */ - private String title; - - /** - * The type of this field. - */ - private Type type; - - /** - * The value of this parameter, for boolean parameters. - */ - private String value; - - /** - * A collection of all associated parameter options. - */ - private Collection options = - new ArrayList(); - - /** - * Returns the name associated with this protocol parameter. - * @return The name associated with this protocol parameter. - */ - public String getName() { - return name; - } - - /** - * Sets the name associated with this protocol parameter. This name must - * uniquely identify this parameter among the others accepted by the - * corresponding protocol. - * - * @param name The name to assign to this protocol parameter. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Returns the title associated with this protocol parameter. - * @return The title associated with this protocol parameter. - */ - public String getTitle() { - return title; - } - - /** - * Sets the title associated with this protocol parameter. The title must - * be a human-readable string which describes accurately this parameter. - * - * @param title A human-readable string describing this parameter. - */ - public void setTitle(String title) { - this.title = title; - } - - /** - * Returns the value associated with this protocol parameter. - * @return The value associated with this protocol parameter. - */ - public String getValue() { - return value; - } - - /** - * Sets the value associated with this protocol parameter. The value must - * be a human-readable string which describes accurately this parameter. - * - * @param value A human-readable string describing this parameter. - */ - public void setValue(String value) { - this.value = value; - } - - /** - * Returns the type of this parameter. - * @return The type of this parameter. - */ - public Type getType() { - return type; - } - - /** - * Sets the type of this parameter. - * @param type The type of this parameter. - */ - public void setType(Type type) { - this.type = type; - } - - /** - * Returns a mutable collection of protocol parameter options. Changes to - * this collection directly affect the available options. - * - * @return A mutable collection of parameter options. - */ - public Collection getOptions() { - return options; - } - -} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/OptionTagHandler.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/OptionTagHandler.java index ce4299081..89743681e 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/OptionTagHandler.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/OptionTagHandler.java @@ -22,7 +22,7 @@ package org.glyptodon.guacamole.xml.protocol; -import org.glyptodon.guacamole.protocols.ProtocolParameterOption; +import org.glyptodon.guacamole.form.ParameterOption; import org.glyptodon.guacamole.xml.TagHandler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -37,7 +37,7 @@ public class OptionTagHandler implements TagHandler { /** * The option backing this option tag. */ - private ProtocolParameterOption option = new ProtocolParameterOption(); + private ParameterOption option = new ParameterOption(); @Override public void init(Attributes attributes) throws SAXException { @@ -55,10 +55,10 @@ public class OptionTagHandler implements TagHandler { } /** - * Returns the ProtocolParameterOption backing this tag. - * @return The ProtocolParameterOption backing this tag. + * Returns the ParameterOption backing this tag. + * @return The ParameterOption backing this tag. */ - public ProtocolParameterOption asProtocolParameterOption() { + public ParameterOption asParameterOption() { return option; } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ParamTagHandler.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ParamTagHandler.java index cb59c04ed..f927891a6 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ParamTagHandler.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ParamTagHandler.java @@ -22,7 +22,7 @@ package org.glyptodon.guacamole.xml.protocol; -import org.glyptodon.guacamole.protocols.ProtocolParameter; +import org.glyptodon.guacamole.form.Parameter; import org.glyptodon.guacamole.xml.TagHandler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -35,9 +35,9 @@ import org.xml.sax.SAXException; public class ParamTagHandler implements TagHandler { /** - * The ProtocolParameter backing this tag handler. + * The Parameter backing this tag handler. */ - private ProtocolParameter protocolParameter = new ProtocolParameter(); + private Parameter protocolParameter = new Parameter(); @Override public void init(Attributes attributes) throws SAXException { @@ -51,31 +51,31 @@ public class ParamTagHandler implements TagHandler { // Text field if ("text".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.TEXT); + protocolParameter.setType(Parameter.Type.TEXT); // Numeric field else if ("numeric".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.NUMERIC); + protocolParameter.setType(Parameter.Type.NUMERIC); // Username field else if ("username".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.USERNAME); + protocolParameter.setType(Parameter.Type.USERNAME); // Password field else if ("password".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.PASSWORD); + protocolParameter.setType(Parameter.Type.PASSWORD); // Enumerated field else if ("enum".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.ENUM); + protocolParameter.setType(Parameter.Type.ENUM); // Multiline field else if ("multiline".equals(type)) - protocolParameter.setType(ProtocolParameter.Type.MULTILINE); + protocolParameter.setType(Parameter.Type.MULTILINE); // Boolean field else if ("boolean".equals(type)) { - protocolParameter.setType(ProtocolParameter.Type.BOOLEAN); + protocolParameter.setType(Parameter.Type.BOOLEAN); if(protocolParameter.getValue() == null) throw new SAXException @@ -99,7 +99,7 @@ public class ParamTagHandler implements TagHandler { // Store stub in options collection protocolParameter.getOptions().add( - tagHandler.asProtocolParameterOption()); + tagHandler.asParameterOption()); return tagHandler; } @@ -114,10 +114,10 @@ public class ParamTagHandler implements TagHandler { } /** - * Returns the ProtocolParameter backing this tag. - * @return The ProtocolParameter backing this tag. + * Returns the Parameter backing this tag. + * @return The Parameter backing this tag. */ - public ProtocolParameter asProtocolParameter() { + public Parameter asParameter() { return protocolParameter; } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ProtocolTagHandler.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ProtocolTagHandler.java index 562784509..38da5030f 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ProtocolTagHandler.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/xml/protocol/ProtocolTagHandler.java @@ -56,7 +56,7 @@ public class ProtocolTagHandler implements TagHandler { ParamTagHandler tagHandler = new ParamTagHandler(); // Store stub in parameters collection - info.getParameters().add(tagHandler.asProtocolParameter()); + info.getParameters().add(tagHandler.asParameter()); return tagHandler; } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java index f08c2e402..3c62725c8 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIError.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 @@ -22,31 +22,161 @@ package org.glyptodon.guacamole.net.basic.rest; +import java.util.Collection; +import javax.ws.rs.core.Response; +import org.glyptodon.guacamole.form.Parameter; + /** - * A simple object to represent an error to be sent from the REST API. + * Describes an error that occurred within a REST endpoint. + * * @author James Muehlner + * @author Michael Jumper */ public class APIError { - + /** * The error message. */ private final String message; /** - * Get the error message. - * @return The error message. + * All expected request parameters, if any. + */ + private final Collection expected; + + /** + * The type of error that occurred. + */ + private final Type type; + + /** + * All possible types of REST API errors. + */ + public enum Type { + + /** + * The requested operation could not be performed because the request + * itself was malformed. + */ + BAD_REQUEST(Response.Status.BAD_REQUEST), + + /** + * The credentials provided were invalid. + */ + INVALID_CREDENTIALS(Response.Status.FORBIDDEN), + + /** + * The credentials provided were not necessarily invalid, but were not + * sufficient to determine validity. + */ + INSUFFICIENT_CREDENTIALS(Response.Status.FORBIDDEN), + + /** + * An internal server error has occurred. + */ + INTERNAL_ERROR(Response.Status.INTERNAL_SERVER_ERROR), + + /** + * An object related to the request does not exist. + */ + NOT_FOUND(Response.Status.NOT_FOUND), + + /** + * Permission was denied to perform the requested operation. + */ + PERMISSION_DENIED(Response.Status.FORBIDDEN); + + /** + * The HTTP status associated with this error type. + */ + private final Response.Status status; + + /** + * Defines a new error type associated with the given HTTP status. + * + * @param status + * The HTTP status to associate with the error type. + */ + Type(Response.Status status) { + this.status = status; + } + + /** + * Returns the HTTP status associated with this error type. + * + * @return + * The HTTP status associated with this error type. + */ + public Response.Status getStatus() { + return status; + } + + } + + /** + * Create a new APIError with the specified error message. + * + * @param type + * The type of error that occurred. + * + * @param message + * The error message. + */ + public APIError(Type type, String message) { + this.type = type; + this.message = message; + this.expected = null; + } + + /** + * Create a new APIError with the specified error message and parameter + * information. + * + * @param type + * The type of error that occurred. + * + * @param message + * The error message. + * + * @param expected + * All parameters expected in the original request, or now required as + * a result of the original request. + */ + public APIError(Type type, String message, Collection expected) { + this.type = type; + this.message = message; + this.expected = expected; + } + + /** + * Returns the type of error that occurred. + * + * @return + * The type of error that occurred. + */ + public Type getType() { + return type; + } + + /** + * Returns an object which describes the required credentials. + * + * @return + * An object which describes the required credentials. + */ + public Collection getExpected() { + return expected; + } + + /** + * Returns a human-readable error message describing the error that + * occurred. + * + * @return + * A human-readable error message. */ public String getMessage() { return message; } - - /** - * Create a new APIError with the specified error message. - * @param message The error message. - */ - public APIError(String message) { - this.message = message; - } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIException.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIException.java new file mode 100644 index 000000000..b26f9bd90 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIException.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 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.net.basic.rest; + +import java.util.Collection; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import org.glyptodon.guacamole.form.Parameter; + +/** + * An exception that will result in the given error error information being + * returned from the API layer. All error messages have the same format which + * is defined by APIError. + * + * @author James Muehlner + * @author Michael Jumper + */ +public class APIException extends WebApplicationException { + + /** + * Construct a new APIException with the given error. All information + * associated with this new exception will be extracted from the given + * APIError. + * + * @param error + * The error that occurred. + */ + public APIException(APIError error) { + super(Response.status(error.getType().getStatus()).entity(error).build()); + } + + /** + * Creates a new APIException with the given type and message. The + * corresponding APIError will be created from the provided information. + * + * @param type + * The type of error that occurred. + * + * @param message + * A human-readable message describing the error. + */ + public APIException(APIError.Type type, String message) { + this(new APIError(type, message)); + } + + /** + * Creates a new APIException with the given type, message, and parameter + * information. The corresponding APIError will be created from the + * provided information. + * + * @param type + * The type of error that occurred. + * + * @param message + * A human-readable message describing the error. + * + * @param expected + * All parameters expected in the original request, or now required as + * a result of the original request. + */ + public APIException(APIError.Type type, String message, Collection expected) { + this(new APIError(type, message, expected)); + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java index 38ec68b5d..0878d9632 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java @@ -27,7 +27,11 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; import org.glyptodon.guacamole.GuacamoleSecurityException; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,15 +53,96 @@ public class AuthProviderRESTExceptionWrapper implements MethodInterceptor { try { return invocation.proceed(); } - catch(GuacamoleSecurityException e) { - throw new HTTPException(Response.Status.FORBIDDEN, e.getMessage() != null ? e.getMessage() : "Permission denied."); + + // Additional credentials are needed + catch (GuacamoleInsufficientCredentialsException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Permission denied."; + + throw new APIException( + APIError.Type.INSUFFICIENT_CREDENTIALS, + message, + e.getCredentialsInfo().getParameters() + ); } - catch(GuacamoleClientException e) { - throw new HTTPException(Response.Status.BAD_REQUEST, e.getMessage() != null ? e.getMessage() : "Invalid Request."); + + // The provided credentials are wrong + catch (GuacamoleInvalidCredentialsException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Permission denied."; + + throw new APIException( + APIError.Type.INVALID_CREDENTIALS, + message, + e.getCredentialsInfo().getParameters() + ); } - catch(GuacamoleException e) { - logger.error("Unexpected GuacamoleException caught while executing " + invocation.getMethod().getName() + ".", e); - throw new HTTPException(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage() != null ? e.getMessage() : "Unexpected server error."); + + // Generic permission denied + catch (GuacamoleSecurityException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Permission denied."; + + throw new APIException( + APIError.Type.PERMISSION_DENIED, + message + ); + + } + + // Arbitrary resource not found + catch (GuacamoleResourceNotFoundException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Not found."; + + throw new APIException( + APIError.Type.NOT_FOUND, + message + ); + + } + + // Arbitrary bad requests + catch (GuacamoleClientException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Invalid request."; + + throw new APIException( + APIError.Type.BAD_REQUEST, + message + ); + + } + + // All other errors + catch (GuacamoleException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Unexpected server error."; + + logger.debug("Unexpected exception in REST endpoint.", e); + throw new APIException( + APIError.Type.INTERNAL_ERROR, + message + ); + } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java deleted file mode 100644 index b4d00d540..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/HTTPException.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 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.net.basic.rest; - -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - -/** - * An exception that will result in the given HTTP Status and message or entity - * being returned from the API layer. - * - * @author James Muehlner - */ -public class HTTPException extends WebApplicationException { - - /** - * Construct a new HTTPException with the given HTTP status and entity. - * - * @param status The HTTP Status to use for the response. - * @param entity The entity to use as the body of the response. - */ - public HTTPException(Status status, Object entity) { - super(Response.status(status).entity(entity).build()); - } - - /** - * Construct a new HTTPException with the given HTTP status and message. The - * message will be wrapped in an APIError container. - * - * @param status The HTTP Status to use for the response. - * @param message The message to build the response entity with. - */ - public HTTPException(Status status, String message) { - super(Response.status(status).entity(new APIError(message)).build()); - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java index c7f7da6e8..3951c59e4 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java @@ -35,16 +35,18 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response.Status; import javax.xml.bind.DatatypeConverter; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; import org.glyptodon.guacamole.net.basic.GuacamoleSession; +import org.glyptodon.guacamole.net.basic.rest.APIError; import org.glyptodon.guacamole.net.basic.rest.APIRequest; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; -import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.APIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -233,15 +235,13 @@ public class TokenRESTService { } + // Request standard username/password if no user context was produced + if (userContext == null) + throw new GuacamoleInvalidCredentialsException("Permission Denied.", + CredentialsInfo.USERNAME_PASSWORD); + } - catch(GuacamoleException e) { - logger.error("Exception caught while authenticating user.", e); - throw new HTTPException(Status.INTERNAL_SERVER_ERROR, - "Unexpected server error."); - } - - // Authentication failed. - if (userContext == null) { + catch (GuacamoleException e) { // Log authentication failures with associated usernames if (username != null) { @@ -255,10 +255,9 @@ public class TokenRESTService { logger.debug("Anonymous authentication attempt from {} failed.", getLoggableAddress(request), username); - throw new HTTPException(Status.UNAUTHORIZED, "Permission Denied."); - + throw e; } - + // Update existing session, if it exists String authToken; if (existingSession != null) { @@ -291,7 +290,7 @@ public class TokenRESTService { GuacamoleSession session = tokenSessionMap.remove(authToken); if (session == null) - throw new HTTPException(Status.NOT_FOUND, "No such token."); + throw new APIException(APIError.Type.NOT_FOUND, "No such token."); session.invalidate(); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index 85f124476..af785fc65 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -39,8 +39,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; @@ -53,11 +51,12 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; import org.glyptodon.guacamole.net.auth.permission.Permission; import org.glyptodon.guacamole.net.auth.permission.SystemPermission; import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet; +import org.glyptodon.guacamole.net.basic.rest.APIError; import org.glyptodon.guacamole.net.basic.rest.APIPatch; import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add; import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.remove; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; -import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.APIException; import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.PATCH; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; @@ -276,12 +275,12 @@ public class UserRESTService { // Validate data and path are sane if (!user.getUsername().equals(username)) - throw new HTTPException(Response.Status.BAD_REQUEST, + throw new APIException(APIError.Type.BAD_REQUEST, "Username in path does not match username provided JSON data."); // A user may not use this endpoint to modify himself if (userContext.self().getIdentifier().equals(user.getUsername())) { - throw new HTTPException(Response.Status.FORBIDDEN, + throw new APIException(APIError.Type.PERMISSION_DENIED, "Permission denied."); } @@ -336,7 +335,7 @@ public class UserRESTService { // Verify that the old password was correct if (authProvider.getUserContext(credentials) == null) { - throw new HTTPException(Response.Status.FORBIDDEN, + throw new APIException(APIError.Type.PERMISSION_DENIED, "Permission denied."); } @@ -467,7 +466,7 @@ public class UserRESTService { // Unsupported patch operation default: - throw new HTTPException(Status.BAD_REQUEST, + throw new APIException(APIError.Type.BAD_REQUEST, "Unsupported patch operation: \"" + operation + "\""); } @@ -586,7 +585,7 @@ public class UserRESTService { // Otherwise, the path is not supported else - throw new HTTPException(Status.BAD_REQUEST, "Unsupported patch path: \"" + path + "\""); + throw new APIException(APIError.Type.BAD_REQUEST, "Unsupported patch path: \"" + path + "\""); } // end for each patch operation diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index 7ad9f4995..6671052ed 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -32,10 +32,20 @@ * If a login attempt results in an existing token being replaced, 'guacLogout' * will be broadcast first for the token being replaced, followed by * 'guacLogin' for the new token. + * + * 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 + * expected credentials returned by the REST endpoint. This set of credentials + * will be in the form of a Field array. */ angular.module('auth').factory('authenticationService', ['$injector', function authenticationService($injector) { + // Required types + var Error = $injector.get('Error'); + // Required services var $cookieStore = $injector.get('$cookieStore'); var $http = $injector.get('$http'); @@ -140,7 +150,16 @@ angular.module('auth').factory('authenticationService', ['$injector', }) // If authentication fails, propogate failure to returned promise - .error(function authenticationFailed() { + .error(function authenticationFailed(error) { + + // Request credentials if provided credentials were invalid + if (error.type === Error.Type.INVALID_CREDENTIALS) + $rootScope.$broadcast('guacInvalidCredentials', parameters, error.expected); + + // Request more credentials if provided credentials were not enough + else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) + $rootScope.$broadcast('guacInsufficientCredentials', parameters, error.expected); + authenticationProcess.reject(); }); diff --git a/guacamole/src/main/webapp/app/form/directives/form.js b/guacamole/src/main/webapp/app/form/directives/form.js index f22b9952b..269b32eb9 100644 --- a/guacamole/src/main/webapp/app/form/directives/form.js +++ b/guacamole/src/main/webapp/app/form/directives/form.js @@ -113,34 +113,6 @@ angular.module('form').directive('guacForm', [function form() { }; - /** - * Produces the translation string for the header of the given - * field. The translation string will be of the form: - * - * NAMESPACE.FIELD_HEADER_NAME - * - * where NAMESPACE is the namespace provided to the - * directive and NAME is the field name transformed - * via translationStringService.canonicalize(). - * - * @param {Field} field - * The field for which to produce the translation string. - * - * @returns {String} - * The translation string which produces the translated header - * of the field. - */ - $scope.getFieldHeader = function getFieldHeader(field) { - - // If no field, or no name, then no header - if (!field || !field.name) - return ''; - - return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') - + '.FIELD_HEADER_' + translationStringService.canonicalize(field.name); - - }; - /** * Determines whether the given object is a form, under the * assumption that the object is either a form or a field. diff --git a/guacamole/src/main/webapp/app/form/directives/formField.js b/guacamole/src/main/webapp/app/form/directives/formField.js index 60b440267..c928af112 100644 --- a/guacamole/src/main/webapp/app/form/directives/formField.js +++ b/guacamole/src/main/webapp/app/form/directives/formField.js @@ -110,6 +110,31 @@ angular.module('form').directive('guacFormField', [function formField() { }; + /** + * Produces the translation string for the header of the current + * field. The translation string will be of the form: + * + * NAMESPACE.FIELD_HEADER_NAME + * + * where NAMESPACE is the namespace provided to the + * directive and NAME is the field name transformed + * via translationStringService.canonicalize(). + * + * @returns {String} + * The translation string which produces the translated header + * of the field. + */ + $scope.getFieldHeader = function getFieldHeader() { + + // If no field, or no name, then no header + if (!$scope.field || !$scope.field.name) + return ''; + + return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE') + + '.FIELD_HEADER_' + translationStringService.canonicalize($scope.field.name); + + }; + /** * 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/form.html b/guacamole/src/main/webapp/app/form/templates/form.html index 962ce4931..0cc2b8d86 100644 --- a/guacamole/src/main/webapp/app/form/templates/form.html +++ b/guacamole/src/main/webapp/app/form/templates/form.html @@ -25,13 +25,9 @@

{{getSectionHeader(form) | translate}}

- - - - - -
{{getFieldHeader(field) | translate}} - -
+
+ +
diff --git a/guacamole/src/main/webapp/app/form/templates/formField.html b/guacamole/src/main/webapp/app/form/templates/formField.html index ebcf3573f..e9ecced7b 100644 --- a/guacamole/src/main/webapp/app/form/templates/formField.html +++ b/guacamole/src/main/webapp/app/form/templates/formField.html @@ -1,4 +1,4 @@ -
+ diff --git a/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js b/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js deleted file mode 100644 index 8ceb6c09f..000000000 --- a/guacamole/src/main/webapp/app/index/config/indexInterceptorConfig.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -/** - * The config block for setting up the authentication interceptor. - */ -angular.module('index').config(['$httpProvider', - function indexInterceptorConfig($httpProvider) { - $httpProvider.interceptors.push('authenticationInterceptor'); -}]); - - diff --git a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js index 3d51f4b52..e8efc5676 100644 --- a/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js +++ b/guacamole/src/main/webapp/app/index/config/indexRouteConfig.js @@ -39,26 +39,17 @@ angular.module('index').config(['$routeProvider', '$locationProvider', * * @returns {Promise} * A promise which resolves successfully only after an attempt to - * re-authenticate has been made. + * re-authenticate has been made. If the authentication attempt fails, + * the promise will be rejected. */ var updateCurrentToken = ['$injector', function updateCurrentToken($injector) { // Required services var $location = $injector.get('$location'); - var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); - // Promise for authentication attempt - var authAttempt = $q.defer(); - // Re-authenticate including any parameters in URL - authenticationService.updateCurrentToken($location.search()) - ['finally'](function authenticationAttemptComplete() { - authAttempt.resolve(); - }); - - // Return promise that will resolve regardless of success/failure - return authAttempt.promise; + return authenticationService.updateCurrentToken($location.search()); }]; @@ -161,15 +152,6 @@ angular.module('index').config(['$routeProvider', '$locationProvider', resolve : { updateCurrentToken: updateCurrentToken } }) - // Login screen - .when('/login/', { - title : 'APP.NAME', - bodyClassName : 'login', - templateUrl : 'app/login/templates/login.html', - controller : 'loginController' - // No need to update token here - the login screen ignores all auth - }) - // Client view .when('/client/:type/:id/:params?', { bodyClassName : 'client', diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 75b38c1fe..cd0897b2f 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -36,6 +36,14 @@ angular.module('index').controller('indexController', ['$scope', '$injector', */ $scope.guacNotification = guacNotification; + /** + * 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 + */ + $scope.expectedCredentials = null; + /** * Basic page-level information. */ @@ -63,6 +71,10 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Broadcast keydown events keyboard.onkeydown = function onkeydown(keysym) { + // Do not handle key events if not logged in + if ($scope.expectedCredentials) + return true; + // Warn of pending keydown var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeydown', keysym, keyboard); if (guacBeforeKeydownEvent.defaultPrevented) @@ -77,6 +89,10 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Broadcast keyup events keyboard.onkeyup = function onkeyup(keysym) { + // Do not handle key events if not logged in + if ($scope.expectedCredentials) + return; + // Warn of pending keyup var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeyup', keysym, keyboard); if (guacBeforeKeydownEvent.defaultPrevented) @@ -92,6 +108,25 @@ angular.module('index').controller('indexController', ['$scope', '$injector', keyboard.reset(); }; + // Display login screen if a whole new set of credentials is needed + $scope.$on('guacInvalidCredentials', function loginInvalid(event, parameters, expected) { + $scope.page.title = 'APP.NAME'; + $scope.page.bodyClassName = ''; + $scope.expectedCredentials = expected; + }); + + // Prompt for remaining credentials if provided credentials were not enough + $scope.$on('guacInsufficientCredentials', function loginInsufficient(event, parameters, expected) { + $scope.page.title = 'APP.NAME'; + $scope.page.bodyClassName = ''; + $scope.expectedCredentials = expected; + }); + + // Clear login screen if login was successful + $scope.$on('guacLogin', function loginSuccessful() { + $scope.expectedCredentials = null; + }); + // Update title and CSS class upon navigation $scope.$on('$routeChangeSuccess', function(event, current, previous) { diff --git a/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js b/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js deleted file mode 100644 index d7bfe8aee..000000000 --- a/guacamole/src/main/webapp/app/index/services/authenticationInterceptor.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -angular.module('index').factory('authenticationInterceptor', ['$location', '$q', - function authenticationInterceptor($location, $q) { - - return { - - // Redirect users to login if authorization fails - responseError: function handleErrorResponse(rejection) { - - // Only redirect failed authentication requests - if ((rejection.status === 401 || rejection.status === 403) - && rejection.config.url === 'api/tokens') { - - // Only redirect if not already on login page - if ($location.path() !== '/login/') - $location.path('/login/'); - - } - - return $q.reject(rejection); - - } - - }; - -}]); diff --git a/guacamole/src/main/webapp/app/login/controllers/loginController.js b/guacamole/src/main/webapp/app/login/controllers/loginController.js deleted file mode 100644 index e43faf3ca..000000000 --- a/guacamole/src/main/webapp/app/login/controllers/loginController.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -angular.module('login').controller('loginController', ['$scope', '$injector', - function loginController($scope, $injector) { - - // Required services - var $location = $injector.get('$location'); - var authenticationService = $injector.get('authenticationService'); - var userPageService = $injector.get('userPageService'); - - /** - * Whether an error occurred during login. - * - * @type Boolean - */ - $scope.loginError = false; - - /** - * Whether the password field has focus. - * - * @type Boolean - */ - $scope.passwordFocused = false; - - /** - * Submits the currently-specified username and password to the - * authentication service, redirecting to the main view if successful. - */ - $scope.login = function login() { - - // Attempt login once existing session is destroyed - authenticationService.login($scope.username, $scope.password) - - // Redirect to main view upon success - .then(function loginSuccessful() { - userPageService.getHomePage() - .then(function homePageRetrieved(homePage) { - $location.url(homePage.url); - }); - }) - - // Reset and focus password upon failure - ['catch'](function loginFailed() { - $scope.loginError = true; - $scope.passwordFocused = true; - $scope.password = ''; - }); - - }; - -}]); diff --git a/guacamole/src/main/webapp/app/login/directives/login.js b/guacamole/src/main/webapp/app/login/directives/login.js new file mode 100644 index 000000000..1b8b407ca --- /dev/null +++ b/guacamole/src/main/webapp/app/login/directives/login.js @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * A directive for displaying an arbitrary login form. + */ +angular.module('login').directive('guacLogin', [function guacLogin() { + + // Login directive + var directive = { + restrict : 'E', + replace : true, + templateUrl : 'app/login/templates/login.html' + }; + + // Login directive scope + directive.scope = { + + /** + * The login form or set of fields. This will be displayed to the user + * to capture their credentials. + * + * @type Form[]|Form|Field[]|Field + */ + form : '=' + + }; + + // Controller for login directive + directive.controller = ['$scope', '$injector', + function loginController($scope, $injector) { + + // Required services + var $route = $injector.get('$route'); + var authenticationService = $injector.get('authenticationService'); + + /** + * Whether an error occurred during login. + * + * @type Boolean + */ + $scope.loginError = false; + + /** + * All form values entered by the user. + */ + $scope.values = {}; + + /** + * Submits the currently-specified username and password to the + * authentication service, redirecting to the main view if successful. + */ + $scope.login = function login() { + + // Attempt login once existing session is destroyed + authenticationService.authenticate($scope.values) + + // Clear and reload upon success + .then(function loginSuccessful() { + $scope.loginError = false; + $scope.values = {}; + $route.reload(); + }) + + // Reset upon failure + ['catch'](function loginFailed() { + $scope.loginError = true; + $scope.values.password = ''; + }); + + }; + + }]; + + return directive; + +}]); diff --git a/guacamole/src/main/webapp/app/login/loginModule.js b/guacamole/src/main/webapp/app/login/loginModule.js index 54e531233..aa879f727 100644 --- a/guacamole/src/main/webapp/app/login/loginModule.js +++ b/guacamole/src/main/webapp/app/login/loginModule.js @@ -23,4 +23,8 @@ /** * The module for the login functionality. */ -angular.module('login', ['element', 'navigation']); +angular.module('login', [ + 'element', + 'form', + 'navigation' +]); diff --git a/guacamole/src/main/webapp/app/login/styles/login.css b/guacamole/src/main/webapp/app/login/styles/login.css index 87f636095..8f5a6d895 100644 --- a/guacamole/src/main/webapp/app/login/styles/login.css +++ b/guacamole/src/main/webapp/app/login/styles/login.css @@ -27,6 +27,8 @@ div.login-ui { left: 0; top: 0; display: table; + background: white; + z-index: 20; } p.login-error { @@ -52,3 +54,35 @@ p.login-error { text-align: center; color: #964040; } + +.login-fields .form-field .password-field .toggle-password { + display: none; +} + +.login-fields .labeled-field { + display: block; + position: relative; +} + +.login-fields .labeled-field .field-header { + + display: block; + position: absolute; + left: 0; + right: 0; + overflow: hidden; + + z-index: -1; + margin: 0.5em; + font-size: 0.9em; + opacity: 0.5; + +} + +.login-fields .labeled-field.empty input { + background: transparent; +} + +.login-fields .labeled-field input:focus { + background: white; +} diff --git a/guacamole/src/main/webapp/app/login/templates/login.html b/guacamole/src/main/webapp/app/login/templates/login.html index e07611894..ac6879a14 100644 --- a/guacamole/src/main/webapp/app/login/templates/login.html +++ b/guacamole/src/main/webapp/app/login/templates/login.html @@ -1,26 +1,25 @@ - -