diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriver.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriver.java index 32bf6722b..ea74f9937 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriver.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriver.java @@ -19,6 +19,8 @@ package org.apache.guacamole.auth.mysql; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + /** * The possible JDBC drivers to use when talking to a MySQL-compatible database * server. @@ -28,10 +30,12 @@ public enum MySQLDriver { /** * MySQL driver. */ + @PropertyValue("mysql") MYSQL, /** * MariaDB driver. */ + @PropertyValue("mariadb") MARIADB; } \ No newline at end of file diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriverProperty.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriverProperty.java deleted file mode 100644 index 0df40c297..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLDriverProperty.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.mysql; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A property whose value is a MySQL-compatible JDBC driver. The string values - * of either "mysql" or "mariadb" are parsed into the corresponding MySQLDriver - * enum value. Any values that are not valid result in a parse error. - */ -public abstract class MySQLDriverProperty implements GuacamoleProperty { - - @Override - public MySQLDriver parseValue(String value) throws GuacamoleException { - - // If no value provided, return null. - if (value == null) - return null; - - // MySQL Driver - if (value.equals("mysql")) - return MySQLDriver.MYSQL; - - // MariaDB Driver - if (value.equals("mariadb")) - return MySQLDriver.MARIADB; - - throw new GuacamoleServerException("MySQL driver must be one of \"mysql\" or \"mariadb\"."); - - } - -} \ No newline at end of file diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLGuacamoleProperties.java index 72f5aea04..c0770b777 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.mysql; import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -36,8 +37,8 @@ public class MySQLGuacamoleProperties { /** * The JDBC driver that should be used to talk to MySQL-compatible servers. */ - public static final MySQLDriverProperty MYSQL_DRIVER = - new MySQLDriverProperty() { + public static final EnumGuacamoleProperty MYSQL_DRIVER = + new EnumGuacamoleProperty(MySQLDriver.class) { @Override public String getName() { return "mysql-driver"; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java index ec01d0668..d1fdc8f1d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriver.java @@ -19,6 +19,8 @@ package org.apache.guacamole.auth.sqlserver; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + /** * The possible SQL Server drivers to use when using a TDS-compatible database. */ @@ -27,20 +29,24 @@ public enum SQLServerDriver { /** * The open source jTDS driver. */ + @PropertyValue("jtds") JTDS, /** * The Progress DataDirect driver. */ + @PropertyValue("datadirect") DATA_DIRECT, /** * The Microsoft Legacy SQL Server driver. */ + @PropertyValue("microsoft") MICROSOFT_LEGACY, /** * The Microsoft 2005 SQL Server driver. */ + @PropertyValue("microsoft2005") MICROSOFT_2005; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java deleted file mode 100644 index 21a62721c..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerDriverProperty.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.sqlserver; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A property whose value is a SQLServerDriver. The incoming string values of "jtds", "datadirect", - * "microsoft", and "microsoft2005" into the corresponding SQLServerDriver enum value. Any - * values that are not valid result in a parse error. - */ -public abstract class SQLServerDriverProperty implements GuacamoleProperty { - - @Override - public SQLServerDriver parseValue(String value) throws GuacamoleException { - - // If no value provided, return null. - if (value == null) - return null; - - // jTDS Driver - if (value.equals("jtds")) - return SQLServerDriver.JTDS; - - // Progress DataDirect Driver - if (value.equals("datadirect")) - return SQLServerDriver.DATA_DIRECT; - - // Microsoft Legacy Driver - if (value.equals("microsoft")) - return SQLServerDriver.MICROSOFT_LEGACY; - - // Microsoft 2005 Driver - if (value.equals("microsoft2005")) - return SQLServerDriver.MICROSOFT_2005; - - throw new GuacamoleServerException("SQLServer driver must be one of \"jtds\", \"datadirect\", \"microsoft\", \"microsoft2005\"."); - - } - -} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java index 45635996f..dff1c93c3 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/SQLServerGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.sqlserver; import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -174,8 +175,8 @@ public class SQLServerGuacamoleProperties { /** * Which TDS-compatible JDBC driver should be used for the connection. */ - public static final SQLServerDriverProperty - SQLSERVER_DRIVER = new SQLServerDriverProperty() { + public static final EnumGuacamoleProperty + SQLSERVER_DRIVER = new EnumGuacamoleProperty(SQLServerDriver.class) { @Override public String getName() { return "sqlserver-driver"; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DereferenceAliasesProperty.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DereferenceAliasesProperty.java deleted file mode 100644 index b33aa191a..000000000 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DereferenceAliasesProperty.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.ldap.conf; - -import org.apache.directory.api.ldap.model.message.AliasDerefMode; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A GuacamoleProperty with a value of AliasDerefMode. The possible strings - * "never", "searching", "finding", and "always" are mapped to their values as - * an AliasDerefMode object. Anything else results in a parse error. - */ -public abstract class DereferenceAliasesProperty implements GuacamoleProperty { - - @Override - public AliasDerefMode parseValue(String value) throws GuacamoleException { - - // No value provided, so return null. - if (value == null) - return null; - - // Never dereference aliases - if (value.equals("never")) - return AliasDerefMode.NEVER_DEREF_ALIASES; - - // Dereference aliases during search operations, but not at base - if (value.equals("searching")) - return AliasDerefMode.DEREF_IN_SEARCHING; - - // Dereference aliases to locate base, but not during searches - if (value.equals("finding")) - return AliasDerefMode.DEREF_FINDING_BASE_OBJ; - - // Always dereference aliases - if (value.equals("always")) - return AliasDerefMode.DEREF_ALWAYS; - - // Anything else is invalid and results in an error - throw new GuacamoleServerException("Dereference aliases must be one of \"never\", \"searching\", \"finding\", or \"always\"."); - - } - -} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethod.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethod.java index 95c93afe2..9d1e937c0 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethod.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethod.java @@ -19,6 +19,8 @@ package org.apache.guacamole.auth.ldap.conf; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + /** * All possible encryption methods which may be used when connecting to an LDAP * server. @@ -29,12 +31,14 @@ public enum EncryptionMethod { * No encryption will be used. All data will be sent to the LDAP server in * plaintext. Unencrypted LDAP connections use port 389 by default. */ + @PropertyValue("none") NONE(389), /** * The connection to the LDAP server will be encrypted with SSL. LDAP over * SSL (LDAPS) will use port 636 by default. */ + @PropertyValue("ssl") SSL(636), /** @@ -42,6 +46,7 @@ public enum EncryptionMethod { * connections are negotiated over the standard LDAP port of 389 - the same * port used for unencrypted traffic. */ + @PropertyValue("starttls") STARTTLS(389); /** diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethodProperty.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethodProperty.java deleted file mode 100644 index d76cc4d2a..000000000 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EncryptionMethodProperty.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.ldap.conf; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A GuacamoleProperty whose value is an EncryptionMethod. The string values - * "none", "ssl", and "starttls" are each parsed to their corresponding values - * within the EncryptionMethod enum. All other string values result in parse - * errors. - */ -public abstract class EncryptionMethodProperty implements GuacamoleProperty { - - @Override - public EncryptionMethod parseValue(String value) throws GuacamoleException { - - // If no value provided, return null. - if (value == null) - return null; - - // Plaintext (no encryption) - if (value.equals("none")) - return EncryptionMethod.NONE; - - // SSL - if (value.equals("ssl")) - return EncryptionMethod.SSL; - - // STARTTLS - if (value.equals("starttls")) - return EncryptionMethod.STARTTLS; - - // The provided value is not legal - throw new GuacamoleServerException("Encryption method must be one of \"none\", \"ssl\", or \"starttls\"."); - - } - -} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java index f10844a9c..e6ec51861 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java @@ -19,7 +19,9 @@ package org.apache.guacamole.auth.ldap.conf; +import org.apache.directory.api.ldap.model.message.AliasDerefMode; import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -158,8 +160,8 @@ public class LDAPGuacamoleProperties { * The chosen method will also dictate the default port if not already * explicitly specified via LDAP_PORT. */ - public static final EncryptionMethodProperty LDAP_ENCRYPTION_METHOD = - new EncryptionMethodProperty() { + public static final EnumGuacamoleProperty LDAP_ENCRYPTION_METHOD = + new EnumGuacamoleProperty(EncryptionMethod.class) { @Override public String getName() { return "ldap-encryption-method"; } @@ -181,8 +183,13 @@ public class LDAPGuacamoleProperties { * Property that controls whether or not the LDAP connection follows * (dereferences) aliases as it searches the tree. */ - public static final DereferenceAliasesProperty LDAP_DEREFERENCE_ALIASES = - new DereferenceAliasesProperty() { + public static final EnumGuacamoleProperty LDAP_DEREFERENCE_ALIASES = + new EnumGuacamoleProperty( + "never", AliasDerefMode.NEVER_DEREF_ALIASES, + "searching", AliasDerefMode.DEREF_IN_SEARCHING, + "finding", AliasDerefMode.DEREF_FINDING_BASE_OBJ, + "always", AliasDerefMode.DEREF_ALWAYS + ) { @Override public String getName() { return "ldap-dereference-aliases"; } @@ -257,10 +264,10 @@ public class LDAPGuacamoleProperties { }; /** - * Specify the type of data contained in 'ldap-member-attribute' + * Specify the type of data contained in 'ldap-member-attribute'. */ - public static final MemberAttributeTypeProperty LDAP_MEMBER_ATTRIBUTE_TYPE = - new MemberAttributeTypeProperty() { + public static final EnumGuacamoleProperty LDAP_MEMBER_ATTRIBUTE_TYPE = + new EnumGuacamoleProperty(MemberAttributeType.class) { @Override public String getName() { return "ldap-member-attribute-type"; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeType.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeType.java index 9a09fa24a..02cd7aa94 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeType.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeType.java @@ -19,20 +19,24 @@ package org.apache.guacamole.auth.ldap.conf; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + /** - * All possible means of describing membership within - * LDAP group directory records. + * All possible means of describing membership within LDAP group directory + * records. */ public enum MemberAttributeType { /** - * group membership is specified by DN + * Group membership is specified by DN. */ + @PropertyValue("dn") DN, /** - * group membership is specified by usercode + * Group membership is specified by usercode. */ + @PropertyValue("uid") UID; } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeTypeProperty.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeTypeProperty.java deleted file mode 100644 index 8d9627d9c..000000000 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/MemberAttributeTypeProperty.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.ldap.conf; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A GuacamoleProperty whose value is a MemberAttributeType. The possible - * strings "dn" or "uid" are mapped to their values as a MemberAttributeType - * enum. Anything else results in a parse error. - */ -public abstract class MemberAttributeTypeProperty - implements GuacamoleProperty { - - @Override - public MemberAttributeType parseValue(String value) - throws GuacamoleException { - - // If no value provided, return null. - if (value == null) - return null; - - // dn - if (value.equals("dn")) - return MemberAttributeType.DN; - - // uid - if (value.equals("uid")) - return MemberAttributeType.UID; - - // The provided value is not legal - throw new GuacamoleServerException("Member attribute type must be " - + "one of \"dn\" or \"uid\"."); - - } - -} diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusAuthenticationProviderModule.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusAuthenticationProviderModule.java index 4224f77f9..fa8b1cf88 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusAuthenticationProviderModule.java @@ -68,10 +68,10 @@ public class RadiusAuthenticationProviderModule extends AbstractModule { // Check for MD4 requirement RadiusAuthenticationProtocol authProtocol = environment.getProperty(RadiusGuacamoleProperties.RADIUS_AUTH_PROTOCOL); RadiusAuthenticationProtocol innerProtocol = environment.getProperty(RadiusGuacamoleProperties.RADIUS_EAP_TTLS_INNER_PROTOCOL); - if (authProtocol == RadiusAuthenticationProtocol.MSCHAPv1 - || authProtocol == RadiusAuthenticationProtocol.MSCHAPv2 - || innerProtocol == RadiusAuthenticationProtocol.MSCHAPv1 - || innerProtocol == RadiusAuthenticationProtocol.MSCHAPv2) { + if (authProtocol == RadiusAuthenticationProtocol.MSCHAP_V1 + || authProtocol == RadiusAuthenticationProtocol.MSCHAP_V2 + || innerProtocol == RadiusAuthenticationProtocol.MSCHAP_V1 + || innerProtocol == RadiusAuthenticationProtocol.MSCHAP_V2) { try { MessageDigest.getInstance("MD4"); diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java index c8a21d6a1..0f5dcf646 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/RadiusConnectionService.java @@ -44,7 +44,6 @@ import net.jradius.packet.attribute.AttributeList; import net.jradius.client.auth.EAPTLSAuthenticator; import net.jradius.client.auth.EAPTTLSAuthenticator; import net.jradius.client.auth.RadiusAuthenticator; -import net.jradius.client.auth.PEAPAuthenticator; import net.jradius.packet.attribute.AttributeFactory; import net.jradius.packet.AccessChallenge; import net.jradius.packet.RadiusResponse; @@ -99,74 +98,57 @@ public class RadiusConnectionService { } /** - * Creates a new instance of RadiusAuthentictor, configured with + * Creates a new instance of RadiusAuthenticator, configured with * parameters specified within guacamole.properties. * - * @param radiusClient - * A RadiusClient instance that has been initialized to - * communicate with a RADIUS server. - * * @return * A new RadiusAuthenticator instance which has been configured - * with parameters from guacamole.properties, or null if - * configuration fails. + * with parameters from guacamole.properties. * * @throws GuacamoleException * If the configuration cannot be read or the inner protocol is * not configured when the client is set up for a tunneled * RADIUS connection. */ - private RadiusAuthenticator setupRadiusAuthenticator( - RadiusClient radiusClient) throws GuacamoleException { + private RadiusAuthenticator getRadiusAuthenticator() throws GuacamoleException { - // If we don't have a radiusClient object, yet, don't go any further. - if (radiusClient == null) { - logger.error("RADIUS client hasn't been set up, yet."); - logger.debug("We can't run this method until the RADIUS client has been set up."); - return null; - } - - RadiusAuthenticator radAuth = radiusClient.getAuthProtocol( - confService.getRadiusAuthProtocol().toString()); - - if (radAuth == null) - throw new GuacamoleException("Could not get a valid RadiusAuthenticator for specified protocol: " + confService.getRadiusAuthProtocol()); + RadiusAuthenticator radAuth = confService.getRadiusAuthProtocol().getAuthenticator(); // If we're using any of the TLS protocols, we need to configure them - if (radAuth instanceof PEAPAuthenticator || - radAuth instanceof EAPTLSAuthenticator || - radAuth instanceof EAPTTLSAuthenticator) { + if (radAuth instanceof EAPTLSAuthenticator) { - // Pull TLS configuration parameters from guacamole.properties + EAPTLSAuthenticator tlsAuth = (EAPTLSAuthenticator) radAuth; + + // If provided, use the configured certificate authority for + // validating the connection to the RADIUS server File caFile = confService.getRadiusCAFile(); - String caPassword = confService.getRadiusCAPassword(); - File keyFile = confService.getRadiusKeyFile(); - String keyPassword = confService.getRadiusKeyPassword(); - if (caFile != null) { - ((EAPTLSAuthenticator)radAuth).setCaFile(caFile.toString()); - ((EAPTLSAuthenticator)radAuth).setCaFileType(confService.getRadiusCAType()); + tlsAuth.setCaFile(caFile.toString()); + tlsAuth.setCaFileType(confService.getRadiusCAType()); + String caPassword = confService.getRadiusCAPassword(); if (caPassword != null) - ((EAPTLSAuthenticator)radAuth).setCaPassword(caPassword); + tlsAuth.setCaPassword(caPassword); } + // Use configured password for unlocking the RADIUS private key, + // if specified + String keyPassword = confService.getRadiusKeyPassword(); if (keyPassword != null) - ((EAPTLSAuthenticator)radAuth).setKeyPassword(keyPassword); + tlsAuth.setKeyPassword(keyPassword); + + // Use configured RADIUS certificate and private key (always + // required for TLS-based protocols) + File keyFile = confService.getRadiusKeyFile(); + tlsAuth.setKeyFile(keyFile.toString()); + tlsAuth.setKeyFileType(confService.getRadiusKeyType()); + tlsAuth.setTrustAll(confService.getRadiusTrustAll()); - ((EAPTLSAuthenticator)radAuth).setKeyFile(keyFile.toString()); - ((EAPTLSAuthenticator)radAuth).setKeyFileType(confService.getRadiusKeyType()); - ((EAPTLSAuthenticator)radAuth).setTrustAll(confService.getRadiusTrustAll()); } // If we're using EAP-TTLS, we need to define tunneled protocol if (radAuth instanceof EAPTTLSAuthenticator) { - RadiusAuthenticationProtocol innerProtocol = - confService.getRadiusEAPTTLSInnerProtocol(); - - if (innerProtocol == null) - throw new GuacamoleException("Missing or invalid inner protocol for EAP-TTLS."); - - ((EAPTTLSAuthenticator)radAuth).setInnerProtocol(innerProtocol.toString()); + RadiusAuthenticationProtocol innerProtocol = confService.getRadiusEAPTTLSInnerProtocol(); + ((EAPTTLSAuthenticator)radAuth).setInnerProtocol(innerProtocol.JRADIUS_PROTOCOL_NAME); } return radAuth; @@ -211,14 +193,8 @@ public class RadiusConnectionService { RadiusClient radiusClient = createRadiusConnection(); AttributeFactory.loadAttributeDictionary("net.jradius.dictionary.AttributeDictionaryImpl"); - // Client failed to set up, so we return null - if (radiusClient == null) - return null; - // Set up the RadiusAuthenticator - RadiusAuthenticator radAuth = setupRadiusAuthenticator(radiusClient); - if (radAuth == null) - throw new GuacamoleException("Unknown RADIUS authentication protocol."); + RadiusAuthenticator radAuth = getRadiusAuthenticator(); // Add attributes to the connection and send the packet try { diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java index 2809f7c76..68ea6898e 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/ConfigurationService.java @@ -317,7 +317,7 @@ public class ConfigurationService { public RadiusAuthenticationProtocol getRadiusEAPTTLSInnerProtocol() throws GuacamoleException { - RadiusAuthenticationProtocol authProtocol = environment.getProperty( + RadiusAuthenticationProtocol authProtocol = environment.getRequiredProperty( RadiusGuacamoleProperties.RADIUS_EAP_TTLS_INNER_PROTOCOL ); diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocol.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocol.java index e64a69584..4db295973 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocol.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocol.java @@ -19,6 +19,17 @@ package org.apache.guacamole.auth.radius.conf; +import net.jradius.client.RadiusClient; +import net.jradius.client.auth.CHAPAuthenticator; +import net.jradius.client.auth.EAPMD5Authenticator; +import net.jradius.client.auth.EAPTLSAuthenticator; +import net.jradius.client.auth.EAPTTLSAuthenticator; +import net.jradius.client.auth.MSCHAPv1Authenticator; +import net.jradius.client.auth.MSCHAPv2Authenticator; +import net.jradius.client.auth.PAPAuthenticator; +import net.jradius.client.auth.RadiusAuthenticator; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + /** * This enum represents supported RADIUS authentication protocols for * the guacamole-auth-radius extension. @@ -26,93 +37,91 @@ package org.apache.guacamole.auth.radius.conf; public enum RadiusAuthenticationProtocol { /** - * Password Authentication Protocol (PAP) + * Password Authentication Protocol (PAP). */ - PAP("pap"), + @PropertyValue("pap") + PAP(PAPAuthenticator.NAME), /** - * Challenge-Handshake Authentication Protocol (CHAP) + * Challenge-Handshake Authentication Protocol (CHAP). */ - CHAP("chap"), + @PropertyValue("chap") + CHAP(CHAPAuthenticator.NAME), /** - * Microsoft implementation of CHAP, Version 1 (MS-CHAPv1) + * Microsoft implementation of CHAP, Version 1 (MS-CHAPv1). */ - MSCHAPv1("mschapv1"), + @PropertyValue("mschapv1") + MSCHAP_V1(MSCHAPv1Authenticator.NAME), /** - * Microsoft implementation of CHAP, Version 2 (MS-CHAPv2) + * Microsoft implementation of CHAP, Version 2 (MS-CHAPv2). */ - MSCHAPv2("mschapv2"), + @PropertyValue("mschapv2") + MSCHAP_V2(MSCHAPv2Authenticator.NAME), /** - * Extensible Authentication Protocol (EAP) with MD5 Hashing (EAP-MD5) + * Extensible Authentication Protocol (EAP) with MD5 Hashing (EAP-MD5). */ - EAP_MD5("eap-md5"), + @PropertyValue("eap-md5") + EAP_MD5(EAPMD5Authenticator.NAME), /** * Extensible Authentication Protocol (EAP) with TLS encryption (EAP-TLS). */ - EAP_TLS("eap-tls"), + @PropertyValue("eap-tls") + EAP_TLS(EAPTLSAuthenticator.NAME), /** * Extensible Authentication Protocol (EAP) with Tunneled TLS (EAP-TTLS). */ - EAP_TTLS("eap-ttls"); + @PropertyValue("eap-ttls") + EAP_TTLS(EAPTTLSAuthenticator.NAME); /** - * This variable stores the string value of the protocol, and is also - * used within the extension to pass to JRadius for configuring the - * library to talk to the RADIUS server. + * The unique name of the JRadius {@link RadiusAuthenticator} that + * implements this protocol. */ - private final String strValue; - + public final String JRADIUS_PROTOCOL_NAME; + /** - * Create a new RadiusAuthenticationProtocol object having the - * given string value. - * - * @param strValue - * The value of the protocol to store as a string, which will be used - * in specifying the protocol within the guacamole.properties file, and - * will also be used by the JRadius library for its configuration. + * Creates a new RadiusAuthenticationProtocol associated with the given + * JRadius protocol name. + * + * @param protocolName + * The unique name of the JRadius {@link RadiusAuthenticator} that + * implements this protocol. */ - RadiusAuthenticationProtocol(String strValue) { - this.strValue = strValue; + RadiusAuthenticationProtocol(String protocolName) { + this.JRADIUS_PROTOCOL_NAME = protocolName; } - + /** - * {@inheritDoc} - *

- * This function returns the stored string values of the selected RADIUS - * protocol, which is used both in Guacamole configuration and also to pass - * on to the JRadius library for its configuration. - * - * @return - * The string value stored for the selected RADIUS protocol. - */ - @Override - public String toString() { - return strValue; - } - - /** - * For a given String value, return the enum value that matches that string, - * or null if no matchi is found. - * - * @param value - * The string value to search for in the list of enums. - * + * Returns a new instance of the JRadius {@link RadiusAuthenticator} that + * implements this protocol. This function will never return null. + * * @return - * The RadiusAuthenticationProtocol value that is identified by the - * provided String value. + * A new instance of the JRadius {@link RadiusAuthenticator} that + * implements this protocol. + * + * @throws IllegalStateException + * If a bug within the JRadius library prevents retrieval of the + * authenticator for a protocol that is known to be supported. */ - public static RadiusAuthenticationProtocol getEnum(String value) { - - for (RadiusAuthenticationProtocol v : values()) - if(v.toString().equals(value)) - return v; - - return null; + public RadiusAuthenticator getAuthenticator() throws IllegalStateException { + + // As we are using JRadius' own NAME constants for retrieving + // authenticator instances, the retrieval operation should always + // succeed except in the case of a bug within the JRadius library + RadiusAuthenticator authenticator = RadiusClient.getAuthProtocol(JRADIUS_PROTOCOL_NAME); + if (authenticator == null) + throw new IllegalStateException(String.format("JRadius failed " + +"to locate its own support for protocol \"%s\". This is " + + "likely a bug in the JRadius library.", + JRADIUS_PROTOCOL_NAME)); + + return authenticator; + } - + } diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocolProperty.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocolProperty.java deleted file mode 100644 index c92c0a3ed..000000000 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusAuthenticationProtocolProperty.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.radius.conf; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; - -/** - * A GuacamoleProperty whose value is a RadiusAuthenticationProtocol. - */ -public abstract class RadiusAuthenticationProtocolProperty - implements GuacamoleProperty { - - @Override - public RadiusAuthenticationProtocol parseValue(String value) - throws GuacamoleException { - - // Nothing provided, nothing returned - if (value == null) - return null; - - // Attempt to parse the string value - RadiusAuthenticationProtocol authProtocol = - RadiusAuthenticationProtocol.getEnum(value); - - // Throw an exception if nothing matched. - if (authProtocol == null) - throw new GuacamoleServerException( - "Invalid or unsupported RADIUS authentication protocol."); - - // Return the answer - return authProtocol; - - } - -} diff --git a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java index af6839b04..927bc7096 100644 --- a/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java +++ b/extensions/guacamole-auth-radius/src/main/java/org/apache/guacamole/auth/radius/conf/RadiusGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.radius.conf; import org.apache.guacamole.properties.BooleanGuacamoleProperty; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.FileGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -81,8 +82,8 @@ public class RadiusGuacamoleProperties { /** * The authentication protocol of the RADIUS server to connect to when authenticating users. */ - public static final RadiusAuthenticationProtocolProperty RADIUS_AUTH_PROTOCOL = - new RadiusAuthenticationProtocolProperty() { + public static final EnumGuacamoleProperty RADIUS_AUTH_PROTOCOL = + new EnumGuacamoleProperty(RadiusAuthenticationProtocol.class) { @Override public String getName() { return "radius-auth-protocol"; } @@ -182,8 +183,8 @@ public class RadiusGuacamoleProperties { /** * The tunneled protocol to use inside a RADIUS EAP-TTLS connection. */ - public static final RadiusAuthenticationProtocolProperty RADIUS_EAP_TTLS_INNER_PROTOCOL = - new RadiusAuthenticationProtocolProperty() { + public static final EnumGuacamoleProperty RADIUS_EAP_TTLS_INNER_PROTOCOL = + new EnumGuacamoleProperty(RadiusAuthenticationProtocol.class) { @Override public String getName() { return "radius-eap-ttls-inner-protocol"; } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java index 8658849be..06984ce40 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java @@ -23,6 +23,7 @@ import com.google.inject.Inject; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.IntegerGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; import org.apache.guacamole.totp.TOTPGenerator; @@ -80,8 +81,8 @@ public class ConfigurationService { * default, this will be "sha1". Legal values are "sha1", "sha256", and * "sha512". */ - private static final TOTPModeProperty TOTP_MODE = - new TOTPModeProperty() { + private static final EnumGuacamoleProperty TOTP_MODE = + new EnumGuacamoleProperty(TOTPGenerator.Mode.class) { @Override public String getName() { return "totp-mode"; } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java deleted file mode 100644 index bfe3ef307..000000000 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.guacamole.auth.totp.conf; - -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; -import org.apache.guacamole.properties.GuacamoleProperty; -import org.apache.guacamole.totp.TOTPGenerator; - -/** - * A GuacamoleProperty whose value is a TOTP generation method. The string - * values "sha1", "sha256", and "sha512" are each parsed to their corresponding - * values within the TOTPGenerator.Mode enum. All other string values result in - * parse errors. - */ -public abstract class TOTPModeProperty - implements GuacamoleProperty { - - @Override - public TOTPGenerator.Mode parseValue(String value) - throws GuacamoleException { - - // If no value provided, return null. - if (value == null) - return null; - - // SHA1 - if (value.equals("sha1")) - return TOTPGenerator.Mode.SHA1; - - // SHA256 - if (value.equals("sha256")) - return TOTPGenerator.Mode.SHA256; - - // SHA512 - if (value.equals("sha512")) - return TOTPGenerator.Mode.SHA512; - - // The provided value is not legal - throw new GuacamoleServerException("TOTP mode must be one of " - + "\"sha1\", \"sha256\", or \"sha512\"."); - - } - -} diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java index d075c8afc..0c303f1f3 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/totp/TOTPGenerator.java @@ -25,6 +25,7 @@ import java.security.Key; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; /* * NOTE: This TOTP implementation is based on the TOTP reference implementation @@ -124,18 +125,21 @@ public class TOTPGenerator { * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode * requires 160-bit keys. */ + @PropertyValue("sha1") SHA1("HmacSHA1", 20), /** * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode * requires 256-bit keys. */ + @PropertyValue("sha256") SHA256("HmacSHA256", 32), /** * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode * requires 512-bit keys. */ + @PropertyValue("sha512") SHA512("HmacSHA512", 64); /** diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java b/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java new file mode 100644 index 000000000..255e4b09a --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/properties/EnumGuacamoleProperty.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.properties; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; + +/** + * A GuacamoleProperty whose possible values are defined by an enum. Possible + * values may be defined either through providing an explicit mapping or + * through annotating the enum constant definitions with the + * {@link PropertyValue} annotation. + * + * @param + * The enum which defines the possible values of this property. + */ +public abstract class EnumGuacamoleProperty> implements GuacamoleProperty { + + /** + * Defines the string value which should be accepted and parsed into the + * annotated enum constant. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public static @interface PropertyValue { + + /** + * Returns the String value that should produce the annotated enum + * constant when parsed. + * + * @return + * The String value that should produce the annotated enum constant + * when parsed. + */ + String value(); + + } + + /** + * Mapping of valid property values to the corresponding enum constants + * that those values parse to. + */ + private final Map valueMapping; + + /** + * Produces a mapping of Guacamole property value to corresponding enum + * constant. All enum constants annotated with {@link PropertyValue} are + * included in the resulting Map. + * + * @param + * The enum for which a value mapping is being produced. + * + * @param enumClass + * The Class of the enum for which a value mapping is being produced. + * + * @return + * A new Map which associates the Guacamole property string values of + * enum constants (as defined by {@link PropertyValue} annotations) + * with their corresponding enum constants. + */ + private static > Map getValueMapping(Class enumClass) { + + T[] values = enumClass.getEnumConstants(); + Map valueMapping = new HashMap<>(values.length); + + for (T value : values) { + + // Retrieve Field which corresponds to the current enum constant + Field field; + try { + field = enumClass.getDeclaredField(value.name()); + } + catch (NoSuchFieldException e) { + // This SHOULD be impossible + throw new IllegalStateException("Fields of enum do not " + + "match declared values.", e); + } + + // Map enum constant only if PropertyValue annotation is present + PropertyValue valueAnnotation = field.getAnnotation(PropertyValue.class); + if (valueAnnotation != null) + valueMapping.put(valueAnnotation.value(), value); + + } + + return valueMapping; + + } + + /** + * Produces a new Map having the given key/value pairs. Each key MUST be a + * String, and each value MUST be an enum constant belonging to the given + * enum. + * + * @param + * The enum whose constants may be used as values within the Map. + * + * @param key + * The key of the first key/value pair to include within the Map. + * + * @param value + * The value of the first key/value pair to include within the Map. + * + * @param additional + * Any additional key/value pairs to be included beyond the first. This + * array must be even in length, where each even element is a String + * key and each odd element is the enum constant value to be associated + * with the key immediately preceding it. + * + * @return + * A new Map having each of the given key/value pairs. + * + * @throws IllegalArgumentException + * If any provided key is not a String, if any provided value is not + * an enum constant from the given enum type, or if the length of + * {@code additional} is not even. + */ + @SuppressWarnings("unchecked") // We check this ourselves with instanceof and getDeclaringClass() + private static > Map mapOf(String key, T value, + Object... additional) throws IllegalArgumentException { + + // Verify length of additional pairs is even + if (additional.length % 2 != 0) + throw new IllegalArgumentException("Array of additional key/value pairs must be even in length."); + + // Add first type-checked pair + Map valueMapping = new HashMap<>(1 + additional.length); + valueMapping.put(key, value); + + Class enumClass = value.getDeclaringClass(); + + // Add remaining, unchecked pairs + for (int i = 0; i < additional.length; i += 2) { + + // Verify that unchecked keys are indeed Strings + Object additionalKey = additional[i]; + if (!(additionalKey instanceof String)) + throw new IllegalArgumentException("Keys of additional key/value pairs must be strings."); + + // Verify that unchecked values are indeed constants defined by the + // expected enum + Object additionalValue = additional[i + 1]; + if (!(additionalValue instanceof Enum) || enumClass != ((Enum) additionalValue).getDeclaringClass()) + throw new IllegalArgumentException("Values of additional key/value pairs must be enum constants of the correct type."); + + valueMapping.put((String) additionalKey, (T) additionalValue); + + } + + return valueMapping; + + } + + /** + * Creates a new EnumGuacamoleProperty which parses String property values + * into corresponding enum constants as defined by the given Map. + * + * @param valueMapping + * A Map which maps all legal String values to their corresponding enum + * constants. + */ + public EnumGuacamoleProperty(Map valueMapping) { + this.valueMapping = valueMapping; + } + + /** + * Creates a new EnumGuacamoleProperty which parses String property values + * into corresponding enum constants as defined by the + * {@link PropertyValue} annotations associated with those constants. + * + * @param enumClass + * The enum whose annotated constants should be used as legal values of + * this property. + */ + public EnumGuacamoleProperty(Class enumClass) { + this(getValueMapping(enumClass)); + } + + /** + * Creates a new EnumGuacamoleProperty which parses the given String + * property values into the given corresponding enum constants. + * + * @param key + * The first String value to accept as a legal value of this property. + * + * @param value + * The enum constant that {@code key} should be parsed into. + * + * @param additional + * Any additional key/value pairs to be included beyond the first. This + * array must be even in length, where each even element is a String + * key and each odd element is the enum constant value to be associated + * with the key immediately preceding it. + * + * @throws IllegalArgumentException + * If any provided key is not a String, if any provided value is not + * an enum constant from the given enum type, or if the length of + * {@code additional} is not even. + */ + public EnumGuacamoleProperty(String key, T value, Object... additional) + throws IllegalArgumentException { + this(mapOf(key, value, additional)); + } + + @Override + public T parseValue(String value) throws GuacamoleException { + + // Simply pass through null values + if (value == null) + return null; + + // Translate values based on explicit string/constant mapping + T parsedValue = valueMapping.get(value); + if (parsedValue != null) + return parsedValue; + + // Produce human-readable error if no matching constant is found + List legalValues = new ArrayList<>(valueMapping.keySet()); + Collections.sort(legalValues); + + throw new GuacamoleServerException(String.format("\"%s\" is not a " + + "valid value for property \"%s\". Valid values are: \"%s\"", + value, getName(), String.join("\", \"", legalValues))); + + } + +} diff --git a/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java b/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java new file mode 100644 index 000000000..5b9a3d742 --- /dev/null +++ b/guacamole-ext/src/test/java/org/apache/guacamole/properties/EnumGuacamolePropertyTest.java @@ -0,0 +1,372 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.properties; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * Test which verifies that EnumGuacamoleProperty functions correctly. + */ +public class EnumGuacamolePropertyTest { + + /** + * Example enum consisting of a small set of possible fish. All values of + * this enum are annotated with {@link PropertyValue}. + */ + public static enum Fish { + + /** + * Salmon are large, anadromous fish prized for their pink/red/orange + * flesh. + * + * @see Salmon (Wikipedia) + */ + @PropertyValue("salmon") + SALMON, + + /** + * Trout are freshwater fish related to salmon, popular both as food + * and as game fish. + * + * @see Trout (Wikipedia) + */ + @PropertyValue("trout") + TROUT, + + /** + * Mackerel are pelagic fish, typically having vertical stripes along + * their backs. + * + * @see Mackerel (Wikipedia) + */ + @PropertyValue("mackerel") + MACKEREL, + + /** + * Tuna are large, predatory, saltwater fish in the same family as + * mackerel. They are one of the few fish that can maintain a body + * temperature higher than the surrounding water. + * + * @see Tuna (Wikipedia) + */ + @PropertyValue("tuna") + TUNA, + + /** + * Sardines are small, herring-like fish commonly served in cans. + * Sardines are considered prey fish and feed almost exclusively on + * zooplankton. + * + * @see Sardine (Wikipedia) + */ + @PropertyValue("sardine") + SARDINE + + } + + /** + * Example enum consisting of a small set of possible vegetables. None of + * the values of this enum are annotated with {@link PropertyValue}. + */ + public static enum Vegetable { + + /** + * Potatoes are starchy root vegetables native to the Americas. The + * tuber itself is edible, but other parts can be toxic. + * + * @see Potato (Wikipedia) + */ + POTATO, + + /** + * Carrots are root vegetables, tapered in shape and generally orange + * in color. + * + * @see Carrot (Wikipedia) + */ + CARROT + + } + + /** + * Example Guacamole property which parses String values as Fish constants. + */ + private static final EnumGuacamoleProperty FAVORITE_FISH = new EnumGuacamoleProperty(Fish.class) { + + @Override + public String getName() { + return "favorite-fish"; + } + + }; + + /** + * Verifies that EnumGuacamoleProperty correctly parses string values that + * are associated with their corresponding enum constants using the + * {@link PropertyValue} annotation. + * + * @throws GuacamoleException + * If a valid test value is incorrectly recognized by parseValue() as + * invalid. + */ + @Test + public void testParseValue() throws GuacamoleException { + assertEquals(Fish.SALMON, FAVORITE_FISH.parseValue("salmon")); + assertEquals(Fish.TROUT, FAVORITE_FISH.parseValue("trout")); + assertEquals(Fish.MACKEREL, FAVORITE_FISH.parseValue("mackerel")); + assertEquals(Fish.TUNA, FAVORITE_FISH.parseValue("tuna")); + assertEquals(Fish.SARDINE, FAVORITE_FISH.parseValue("sardine")); + } + + /** + * Verifies that the absence of a property value (null) is parsed by + * EnumGuacamoleProperty as the absence of an enum constant (also null). + * + * @throws GuacamoleException + * If a valid test value is incorrectly recognized by parseValue() as + * invalid. + */ + @Test + public void testParseNullValue() throws GuacamoleException { + assertNull(FAVORITE_FISH.parseValue(null)); + } + + /** + * Verifies that GuacamoleException is thrown when attempting to parse an + * invalid value, and that the error message contains a sorted list of all + * allowed values. + */ + @Test + public void testParseInvalidValue() { + try { + FAVORITE_FISH.parseValue("anchovy"); + fail("Invalid EnumGuacamoleProperty values should fail to parse with an exception."); + } + catch (GuacamoleException e) { + String message = e.getMessage(); + assertTrue(message.contains("\"mackerel\", \"salmon\", \"sardine\", \"trout\", \"tuna\"")); + } + } + + /** + * Verifies that EnumGuacamoleProperty can be constructed for enums that + * are not annotated with {@link PropertyValue}. + * + * @throws GuacamoleException + * If a valid test value is incorrectly recognized by parseValue() as + * invalid. + */ + @Test + public void testUnannotatedEnum() throws GuacamoleException { + + EnumGuacamoleProperty favoriteVegetable = new EnumGuacamoleProperty( + "potato", Vegetable.POTATO, + "carrot", Vegetable.CARROT + ) { + + @Override + public String getName() { + return "favorite-vegetable"; + } + + }; + + assertEquals(Vegetable.POTATO, favoriteVegetable.parseValue("potato")); + assertEquals(Vegetable.CARROT, favoriteVegetable.parseValue("carrot")); + + } + + /** + * Verifies that an IllegalArgumentException is thrown if key/value pairs + * are provided in the wrong order (value followed by key instead of key + * followed by value). + */ + @Test + public void testUnannotatedEnumBadOrder() { + + try { + + new EnumGuacamoleProperty( + "potato", Vegetable.POTATO, + Vegetable.CARROT, "carrot" + ) { + + @Override + public String getName() { + return "favorite-vegetable"; + } + + }; + + fail("EnumGuacamoleProperty should not accept key/value pairs in value/key order."); + + } + catch (IllegalArgumentException e) { + // Success + } + + } + + /** + * Verifies that an IllegalArgumentException is thrown if constants from + * the wrong enum are provided in an explicit mapping. + */ + @Test + public void testUnannotatedEnumBadValue() { + + try { + + new EnumGuacamoleProperty( + "potato", Vegetable.POTATO, + "carrot", Fish.TROUT + ) { + + @Override + public String getName() { + return "favorite-vegetable"; + } + + }; + + fail("EnumGuacamoleProperty should not accept values from the wrong enum."); + + } + catch (IllegalArgumentException e) { + // Success + } + + } + + /** + * Verifies that an IllegalArgumentException is thrown if non-String keys + * are provided in an explicit mapping. + */ + @Test + public void testUnannotatedEnumBadKey() { + + try { + + new EnumGuacamoleProperty( + "potato", Vegetable.POTATO, + 1, Vegetable.CARROT + ) { + + @Override + public String getName() { + return "favorite-vegetable"; + } + + }; + + fail("EnumGuacamoleProperty should not accept keys that are not Strings."); + + } + catch (IllegalArgumentException e) { + // Success + } + + } + + /** + * Verifies that an IllegalArgumentException is thrown if the length of the + * {@code additional} array is not even. + */ + @Test + public void testUnannotatedEnumBadLength() { + + try { + + new EnumGuacamoleProperty( + "potato", Vegetable.POTATO, + 1, Vegetable.CARROT, 2 + ) { + + @Override + public String getName() { + return "favorite-vegetable"; + } + + }; + + fail("EnumGuacamoleProperty should not accept additional key/value pairs from an array that is not even in length."); + + } + catch (IllegalArgumentException e) { + // Success + } + + } + + /** + * Verifies that explicit string/constant mappings take priority over the + * {@link PropertyValue} annotation when both are used. + * + * @throws GuacamoleException + * If a valid test value is incorrectly recognized by parseValue() as + * invalid. + */ + @Test + public void testAnnotationPrecedence() throws GuacamoleException { + + EnumGuacamoleProperty favoriteFish = new EnumGuacamoleProperty( + "chinook", Fish.SALMON, + "rainbow", Fish.TROUT + ) { + + @Override + public String getName() { + return "favorite-fish"; + } + + }; + + assertEquals(Fish.SALMON, favoriteFish.parseValue("chinook")); + assertEquals(Fish.TROUT, favoriteFish.parseValue("rainbow")); + + try { + favoriteFish.parseValue("salmon"); + fail("Explicit key/value mapping should take priority over annotations."); + } + catch (GuacamoleException e) { + // Success + } + + try { + favoriteFish.parseValue("trout"); + fail("Explicit key/value mapping should take priority over annotations."); + } + catch (GuacamoleException e) { + // Success + } + + try { + favoriteFish.parseValue("tuna"); + fail("Annotations should not have any effect if explicit key/value mapping is used."); + } + catch (GuacamoleException e) { + // Success + } + + } + +}