diff --git a/guacamole/pom.xml b/guacamole/pom.xml index e362b3391..6cf75842c 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -374,6 +374,13 @@ 1.2.2 + + + junit + junit + test + + diff --git a/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java b/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java new file mode 100644 index 000000000..ef5e85325 --- /dev/null +++ b/guacamole/src/main/java-templates/org/apache/guacamole/extension/GuacamoleVersion.java @@ -0,0 +1,159 @@ +/* + * 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.extension; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Representation of the Guacamole webapp version. This is used both to + * represent the expected minimum webapp version required by an extension and + * to represent the running version of Guacamole. + */ +public class GuacamoleVersion { + + /** + * Regular expression that splits a version number into its major, minor, + * and patch components, as well as an optional version suffix. + */ + private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(.+)?"); + + /** + * The version of this copy of the Guacamole web application. This value is + * substituted by Maven during the build. + */ + public static final GuacamoleVersion RUNNING_VERSION = new GuacamoleVersion("${project.version}"); + + /** + * The first (leftmost) version number of a Guacamole version. This is the + * number of greatest significance and denotes compatibility. If two + * Guacamole versions have the same major number, extensions for the older + * version should still work with the newer version. + */ + private final int major; + + /** + * The second (middle) version number of a Guacamole version. If two + * Guacamole versions have the same major number but different minor + * numbers, they would be expected to differ in the features available. + */ + private final int minor; + + /** + * The third (rightmost) version number of a Guacamole version. This is the + * number of least significance and denotes minor changes or fixes. If + * two Guacamole versions have the same major and minor numbers but + * different patch numbers, the newer version would be expected to have + * fixes or improvements that correct issues affecting the older version, + * but would not be expected to have major new features. + */ + private final int patch; + + /** + * An arbitrary suffix that might be appended by downstream package + * maintainers or vendors. A Guacamole version with a particular suffix + * would be expected to have patches applied relative to the version without + * the suffix but is otherwise compatible (as if the patch or minor numbers + * differed instead of the suffix). + */ + private final String suffix; + + /** + * Creates a new GuacamoleVersion representing the version of Guacamole + * that has the given version string. The version string must be in the + * format "MAJOR.MINOR.PATCH" with an optional and arbitrary version + * suffix. + * + * @param versionStr + * The Guacamole version string to parse. + * + * @throws IllegalArgumentException + * If the version string is not in the correct format. + */ + public GuacamoleVersion(String versionStr) throws IllegalArgumentException { + + // Use all-zeroes without suffix to represent the wildcard version + if ("*".equals(versionStr)) { + major = minor = patch = 0; + suffix = null; + } + + // Extract major/minor/patch and optional suffix from version string + else { + + Matcher versionMatcher = VERSION_PATTERN.matcher(versionStr); + if (!versionMatcher.matches()) + throw new IllegalArgumentException("Format of version " + + "string \"" + versionStr + "\" is not valid"); + + major = Integer.parseInt(versionMatcher.group(1)); + minor = Integer.parseInt(versionMatcher.group(2)); + patch = Integer.parseInt(versionMatcher.group(3)); + suffix = versionMatcher.group(4); + + } + + } + + /** + * Returns whether this Guacamole version is expected to be compatible with + * an extension that requires at least the given Guacamole version. + * + * @param required + * The minimum Guacamole version required. + * + * @return + * true if this Guacamole version is expected to be compatible with an + * extension requiring at least the given version, false otherwise. + */ + public boolean provides(GuacamoleVersion required) { + + // Wildcard version always matches + if (required.major == 0 && required.minor == 0 && required.patch == 0 + && required.suffix == null) + return true; + + // Major version component MUST be identical + if (this.major != required.major) + return false; + + // Minor version component must be the same or greater than the + // requirement + if (this.minor < required.minor) + return false; + + // If both major and minor are identical, the patch version component + // must be the same or greater than the requirement + if (this.minor == required.minor && this.patch < required.patch) + return false; + + // Any suffix denotes vendor-specific patching that must be assumed to + // be an arbitrary requirement only satisfied by that suffix. If no + // suffix is required, any suffix of the current Guacamole version can + // just be ignored. + + if (required.suffix == null) + return true; + + return required.suffix.equals(this.suffix); + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java index 21adbe81e..ae91ce722 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -24,7 +24,6 @@ import com.google.inject.servlet.ServletModule; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -57,27 +56,6 @@ public class ExtensionModule extends ServletModule { */ private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class); - /** - * The version strings of all Guacamole versions whose extensions are - * compatible with this release. - */ - private static final List ALLOWED_GUACAMOLE_VERSIONS = - Collections.unmodifiableList(Arrays.asList( - "*", - "1.0.0", - "1.1.0", - "1.2.0", - "1.3.0", - "1.4.0", - "1.5.0", - "1.5.1", - "1.5.2", - "1.5.3", - "1.5.4", - "1.5.5", - "1.6.0" - )); - /** * The name of the directory within GUACAMOLE_HOME containing any .jars * which should be included in the classpath of all extensions. @@ -394,21 +372,6 @@ public class ExtensionModule extends ServletModule { } - /** - * Returns whether the given version of Guacamole is compatible with this - * version of Guacamole as far as extensions are concerned. - * - * @param guacamoleVersion - * The version of Guacamole the extension was built for. - * - * @return - * true if the given version of Guacamole is compatible with this - * version of Guacamole, false otherwise. - */ - private boolean isCompatible(String guacamoleVersion) { - return ALLOWED_GUACAMOLE_VERSIONS.contains(guacamoleVersion); - } - /** * Returns the set of identifiers of all authentication providers whose * internal failures should be tolerated during the authentication process. @@ -513,7 +476,8 @@ public class ExtensionModule extends ServletModule { Extension extension = new Extension(getParentClassLoader(), extensionFile, temporaryFiles); // Validate Guacamole version of extension - if (!isCompatible(extension.getGuacamoleVersion())) { + GuacamoleVersion requiredVersion = new GuacamoleVersion(extension.getGuacamoleVersion()); + if (!GuacamoleVersion.RUNNING_VERSION.provides(requiredVersion)) { logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.", extension.getGuacamoleVersion(), extensionFile.getName()); throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not " @@ -523,7 +487,7 @@ public class ExtensionModule extends ServletModule { extensions.add(extension); } - catch (GuacamoleException e) { + catch (GuacamoleException | IllegalArgumentException e) { logger.error("Extension \"{}\" could not be loaded: {}", extensionFile.getName(), e.getMessage(), e); } diff --git a/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java b/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java new file mode 100644 index 000000000..53ced59b7 --- /dev/null +++ b/guacamole/src/test/java/org/apache/guacamole/extension/GuacamoleVersionTest.java @@ -0,0 +1,150 @@ +/* + * 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.extension; + +import java.util.Arrays; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests which verify GuacamoleVersion correctly parses and compares + * Guacamole versions. + */ +public class GuacamoleVersionTest { + + /** + * Verifies that trivially valid version numbers are correctly parsed. + */ + @Test + public void testValidVersion() { + GuacamoleVersion version = new GuacamoleVersion("1.2.3"); + Assert.assertTrue(version.provides(version)); + } + + /** + * Verifies that invalid version numbers correctly fail to parse. + */ + @Test + public void testInvalidVersion() { + for (String invalid : Arrays.asList("1.2")) { + try { + GuacamoleVersion version = new GuacamoleVersion(invalid); + Assert.fail("Invalid Guacamole version string \"" + + invalid + "\" should have failed to parse."); + } + catch (IllegalArgumentException e) { + // Expected + } + } + } + + /** + * Verifies that newer versions are recognized as providing older versions + * when they differ by minor number (but not the other way around). + */ + @Test + public void testMinorNewerProvidesOlder() { + + GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3"); + GuacamoleVersion newerVersion = new GuacamoleVersion("1.22.3"); + + Assert.assertTrue(newerVersion.provides(olderVersion)); + Assert.assertFalse(olderVersion.provides(newerVersion)); + + } + + /** + * Verifies that newer versions are recognized as providing older versions + * when they differ by patch number (but not the other way around). + */ + @Test + public void testPatchNewerProvidesOlder() { + + GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3"); + GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.7"); + + Assert.assertTrue(newerVersion.provides(olderVersion)); + Assert.assertFalse(olderVersion.provides(newerVersion)); + + } + + /** + * Verifies that two versions are NOT recognized as providing each other + * when they differ by major number (regardless of which is newer). + */ + @Test + public void testPatchMajorIncompatible() { + + GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3"); + GuacamoleVersion newerVersion = new GuacamoleVersion("2.2.3"); + + Assert.assertFalse(newerVersion.provides(olderVersion)); + Assert.assertFalse(olderVersion.provides(newerVersion)); + + } + + /** + * Verifies that a version number with an arbitrary suffix is recognized + * as providing that same version without the suffix (but not the other way + * around). + */ + @Test + public void testCompatibleSuffix() { + + GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3"); + GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.3-foo"); + + Assert.assertTrue(newerVersion.provides(olderVersion)); + Assert.assertFalse(olderVersion.provides(newerVersion)); + + } + + /** + * Verifies that two version numbers that differ by arbitrary suffix are + * NOT recognized as providing each other, even when the version numbers + * are otherwise the same. + */ + @Test + public void testIncompatibleSuffix() { + + GuacamoleVersion olderVersion = new GuacamoleVersion("1.2.3-bar"); + GuacamoleVersion newerVersion = new GuacamoleVersion("1.2.3-foo"); + + Assert.assertFalse(newerVersion.provides(olderVersion)); + Assert.assertFalse(olderVersion.provides(newerVersion)); + + } + + /** + * Verifies that the special wildcard version is provided by any version, + * but not the other way around. + */ + @Test + public void testWildcardVersion() { + + GuacamoleVersion specificVersion = new GuacamoleVersion("1.2.3-foo"); + GuacamoleVersion wildcardVersion = new GuacamoleVersion("*"); + + Assert.assertTrue(specificVersion.provides(wildcardVersion)); + Assert.assertFalse(wildcardVersion.provides(specificVersion)); + + } + +} diff --git a/pom.xml b/pom.xml index 92070e942..ca7eb5923 100644 --- a/pom.xml +++ b/pom.xml @@ -490,6 +490,31 @@ + + + filter-java-source + + src/main/java-templates + + + + + org.codehaus.mojo + templating-maven-plugin + 3.0.0 + + + filter-java-source + + filter-sources + + + + + + + + filter-resources