Check plugin dependencies after download

This commit is contained in:
Rene Pfeuffer
2020-01-23 17:02:53 +01:00
parent 787f9c7dec
commit 97600b26e2
4 changed files with 140 additions and 2 deletions

View File

@@ -19,11 +19,13 @@ class PluginInstaller {
private final SCMContextProvider context; private final SCMContextProvider context;
private final AdvancedHttpClient client; private final AdvancedHttpClient client;
private final SmpDDescriptorExtractor smpDDescriptorExtractor;
@Inject @Inject
public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client) { public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client, SmpDDescriptorExtractor smpDDescriptorExtractor) {
this.context = context; this.context = context;
this.client = client; this.client = client;
this.smpDDescriptorExtractor = smpDDescriptorExtractor;
} }
@SuppressWarnings("squid:S4790") // hashing should be safe @SuppressWarnings("squid:S4790") // hashing should be safe
@@ -34,6 +36,17 @@ class PluginInstaller {
Files.copy(input, file); Files.copy(input, file);
verifyChecksum(plugin, input.hash(), 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); return new PendingPluginInstallation(plugin.install(), file);
} catch (IOException ex) { } catch (IOException ex) {
cleanup(file); cleanup(file);

View File

@@ -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");
}
}

View File

@@ -32,18 +32,23 @@ class PluginInstallerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private AdvancedHttpClient client; private AdvancedHttpClient client;
@Mock
private SmpDDescriptorExtractor extractor;
@InjectMocks @InjectMocks
private PluginInstaller installer; private PluginInstaller installer;
private Path directory; private Path directory;
@BeforeEach @BeforeEach
void setUpContext(@TempDirectory.TempDir Path directory) { void setUpContext(@TempDirectory.TempDir Path directory) throws IOException {
this.directory = directory; this.directory = directory;
lenient().when(context.resolve(any())).then(ic -> { lenient().when(context.resolve(any())).then(ic -> {
Path arg = ic.getArgument(0); Path arg = ic.getArgument(0);
return directory.resolve(arg); return directory.resolve(arg);
}); });
InstalledPluginDescriptor supportedPlugin = createPluginDescriptor(true);
lenient().when(extractor.extractPluginDescriptor(any())).thenReturn(supportedPlugin);
} }
@Test @Test
@@ -105,6 +110,15 @@ class PluginInstallerTest {
assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist(); 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) { private AvailablePlugin createPlugin(String name, String url, String checksum) {
PluginInformation information = new PluginInformation(); PluginInformation information = new PluginInformation();
@@ -114,4 +128,11 @@ class PluginInstallerTest {
); );
return new AvailablePlugin(descriptor); 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;
}
} }

View File

@@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" +
"<plugin>\n" +
"\n" +
" <scm-version>2</scm-version>\n" +
"\n" +
" <information>\n" +
" <displayName>Test</displayName>\n" +
" <author>Cloudogu GmbH</author>\n" +
" <category>Testing</category>\n" +
" <name>scm-test-plugin</name>\n" +
"<version>2.0.0</version>\n" +
"<description>Collects information for support cases</description>\n" +
"</information>\n" +
"\n" +
" <conditions>\n" +
" <min-version>2.0.0-rc1</min-version>\n" +
" </conditions>\n" +
"\n" +
" <resources>\n" +
" <script>assets/scm-support-plugin.bundle.js</script>\n" +
" </resources>\n" +
"\n" +
"</plugin>\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", "<not><parsable>content</parsable></not>");
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;
}
}