GUACAMOLE-2139: Automatically determine extension compatibility.
This commit is contained in:
5 files changed
+344
-39
No files matched your search
@@ -374,6 +374,13 @@
|
|||||||
<version>1.2.2</version>
|
<version>1.2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JUnit for unit testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
+159
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import com.google.inject.servlet.ServletModule;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -57,27 +56,6 @@ public class ExtensionModule extends ServletModule {
|
|||||||
*/
|
*/
|
||||||
private final Logger logger = LoggerFactory.getLogger(ExtensionModule.class);
|
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<String> 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
|
* The name of the directory within GUACAMOLE_HOME containing any .jars
|
||||||
* which should be included in the classpath of all extensions.
|
* 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
|
* Returns the set of identifiers of all authentication providers whose
|
||||||
* internal failures should be tolerated during the authentication process.
|
* 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);
|
Extension extension = new Extension(getParentClassLoader(), extensionFile, temporaryFiles);
|
||||||
|
|
||||||
// Validate Guacamole version of extension
|
// 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.",
|
logger.debug("Declared Guacamole version \"{}\" of extension \"{}\" is not compatible with this version of Guacamole.",
|
||||||
extension.getGuacamoleVersion(), extensionFile.getName());
|
extension.getGuacamoleVersion(), extensionFile.getName());
|
||||||
throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not "
|
throw new GuacamoleServerException("Extension \"" + extension.getName() + "\" is not "
|
||||||
@@ -523,7 +487,7 @@ public class ExtensionModule extends ServletModule {
|
|||||||
extensions.add(extension);
|
extensions.add(extension);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (GuacamoleException e) {
|
catch (GuacamoleException | IllegalArgumentException e) {
|
||||||
logger.error("Extension \"{}\" could not be loaded: {}",
|
logger.error("Extension \"{}\" could not be loaded: {}",
|
||||||
extensionFile.getName(), e.getMessage(), e);
|
extensionFile.getName(), e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -490,6 +490,31 @@
|
|||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
|
<!-- Automatically filter Java source if located within src/main/java-templates -->
|
||||||
|
<profile>
|
||||||
|
<id>filter-java-source</id>
|
||||||
|
<activation>
|
||||||
|
<file><exists>src/main/java-templates</exists></file>
|
||||||
|
</activation>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>templating-maven-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>filter-java-source</id>
|
||||||
|
<goals>
|
||||||
|
<goal>filter-sources</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
|
||||||
<!-- Automatically filter resources if located within src/main/resource-templates -->
|
<!-- Automatically filter resources if located within src/main/resource-templates -->
|
||||||
<profile>
|
<profile>
|
||||||
<id>filter-resources</id>
|
<id>filter-resources</id>
|
||||||
|
|||||||
Reference in new issue
Block a user