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