diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java
index 6f003c1e31..c06039bf0a 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java
@@ -19,11 +19,13 @@ class PluginInstaller {
private final SCMContextProvider context;
private final AdvancedHttpClient client;
+ private final SmpDDescriptorExtractor smpDDescriptorExtractor;
@Inject
- public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client) {
+ public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client, SmpDDescriptorExtractor smpDDescriptorExtractor) {
this.context = context;
this.client = client;
+ this.smpDDescriptorExtractor = smpDDescriptorExtractor;
}
@SuppressWarnings("squid:S4790") // hashing should be safe
@@ -34,6 +36,17 @@ class PluginInstaller {
Files.copy(input, file);
verifyChecksum(plugin, input.hash(), file);
+ InstalledPluginDescriptor pluginDescriptor = smpDDescriptorExtractor.extractPluginDescriptor(file);
+ if (!pluginDescriptor.getCondition().isSupported()) {
+ cleanup(file);
+ throw new PluginConditionFailedException(
+ pluginDescriptor.getCondition(),
+ String.format(
+ "could not load plugin %s, the plugin condition does not match",
+ pluginDescriptor.getInformation().getId()
+ )
+ );
+ }
return new PendingPluginInstallation(plugin.install(), file);
} catch (IOException ex) {
cleanup(file);
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/SmpDDescriptorExtractor.java b/scm-webapp/src/main/java/sonia/scm/plugin/SmpDDescriptorExtractor.java
new file mode 100644
index 0000000000..b218acf157
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/SmpDDescriptorExtractor.java
@@ -0,0 +1,28 @@
+package sonia.scm.plugin;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+class SmpDDescriptorExtractor {
+
+ InstalledPluginDescriptor extractPluginDescriptor(Path file) throws IOException {
+ try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8)) {
+ ZipEntry nextEntry;
+ while ((nextEntry = zipInputStream.getNextEntry()) != null) {
+ if ("META-INF/scm/plugin.xml".equals(nextEntry.getName())) {
+ JAXBContext context = JAXBContext.newInstance(ScmModule.class, InstalledPluginDescriptor.class);
+ return (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(zipInputStream);
+ }
+ }
+ } catch (JAXBException e) {
+ throw new IOException("failed to read descriptor file META-INF/scm/plugin.xml from plugin", e);
+ }
+ throw new IOException("Missing plugin descriptor META-INF/scm/plugin.xml in download package");
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java
index 3f918cd4fa..b71fec5bc6 100644
--- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java
@@ -32,18 +32,23 @@ class PluginInstallerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private AdvancedHttpClient client;
+ @Mock
+ private SmpDDescriptorExtractor extractor;
+
@InjectMocks
private PluginInstaller installer;
private Path directory;
@BeforeEach
- void setUpContext(@TempDirectory.TempDir Path directory) {
+ void setUpContext(@TempDirectory.TempDir Path directory) throws IOException {
this.directory = directory;
lenient().when(context.resolve(any())).then(ic -> {
Path arg = ic.getArgument(0);
return directory.resolve(arg);
});
+ InstalledPluginDescriptor supportedPlugin = createPluginDescriptor(true);
+ lenient().when(extractor.extractPluginDescriptor(any())).thenReturn(supportedPlugin);
}
@Test
@@ -105,6 +110,15 @@ class PluginInstallerTest {
assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist();
}
+ @Test
+ void shouldFailForUnsupportedPlugin() throws IOException {
+ mockContent("42");
+ InstalledPluginDescriptor supportedPlugin = createPluginDescriptor(false);
+ when(extractor.extractPluginDescriptor(any())).thenReturn(supportedPlugin);
+
+ assertThrows(PluginConditionFailedException.class, () -> installer.install(createGitPlugin()));
+ assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist();
+ }
private AvailablePlugin createPlugin(String name, String url, String checksum) {
PluginInformation information = new PluginInformation();
@@ -114,4 +128,11 @@ class PluginInstallerTest {
);
return new AvailablePlugin(descriptor);
}
+
+ private InstalledPluginDescriptor createPluginDescriptor(boolean supported) {
+ InstalledPluginDescriptor installedPluginDescriptor = mock(InstalledPluginDescriptor.class, RETURNS_DEEP_STUBS);
+ lenient().when(installedPluginDescriptor.getCondition().isSupported()).thenReturn(supported);
+ lenient().when(installedPluginDescriptor.getInformation().getId()).thenReturn("scm-git-plugin");
+ return installedPluginDescriptor;
+ }
}
diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/SmpDDescriptorExtractorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/SmpDDescriptorExtractorTest.java
new file mode 100644
index 0000000000..51d9005cc0
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/plugin/SmpDDescriptorExtractorTest.java
@@ -0,0 +1,76 @@
+package sonia.scm.plugin;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junitpioneer.jupiter.TempDirectory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(TempDirectory.class)
+class SmpDDescriptorExtractorTest {
+
+ private static final String PLUGIN_XML = "\n" +
+ "\n" +
+ "\n" +
+ " 2\n" +
+ "\n" +
+ " \n" +
+ " Test\n" +
+ " Cloudogu GmbH\n" +
+ " Testing\n" +
+ " scm-test-plugin\n" +
+ "2.0.0\n" +
+ "Collects information for support cases\n" +
+ "\n" +
+ "\n" +
+ " \n" +
+ " 2.0.0-rc1\n" +
+ " \n" +
+ "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "\n" +
+ "\n";
+
+ @Test
+ void shouldExtractPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
+ Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", PLUGIN_XML);
+
+ InstalledPluginDescriptor installedPluginDescriptor = new SmpDDescriptorExtractor().extractPluginDescriptor(pluginFile);
+
+ Assertions.assertThat(installedPluginDescriptor.getInformation().getName()).isEqualTo("scm-test-plugin");
+ }
+
+ @Test
+ void shouldFailWithoutPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
+ Path pluginFile = createZipFile(tempDir, "META-INF/wrong/plugin.xml", PLUGIN_XML);
+
+ assertThrows(IOException.class, () -> new SmpDDescriptorExtractor().extractPluginDescriptor(pluginFile));
+ }
+
+ @Test
+ void shouldFailWithIllegalPluginXml(@TempDirectory.TempDir Path tempDir) throws IOException {
+ Path pluginFile = createZipFile(tempDir, "META-INF/scm/plugin.xml", "content");
+
+ assertThrows(IOException.class, () -> new SmpDDescriptorExtractor().extractPluginDescriptor(pluginFile));
+ }
+
+ Path createZipFile(Path tempDir, String internalFileName, String content) throws IOException {
+ Path pluginFile = tempDir.resolve("scm-test-plugin.smp");
+ ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(pluginFile), UTF_8);
+ zipOutputStream.putNextEntry(new ZipEntry(internalFileName));
+ zipOutputStream.write(content.getBytes(UTF_8));
+ zipOutputStream.closeEntry();
+ zipOutputStream.close();
+ return pluginFile;
+ }
+}