mirror of
				https://github.com/scm-manager/scm-manager.git
				synced 2025-10-31 02:25:53 +01:00 
			
		
		
		
	Check plugin dependencies after download
This commit is contained in:
		| @@ -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); | ||||
|   | ||||
| @@ -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"); | ||||
|   } | ||||
| } | ||||
| @@ -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; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user