From 0287724b97024d123f3613993f5980b0556da3bc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 5 Aug 2020 16:54:48 +0200 Subject: [PATCH] adds verification if name and version of a downloaded plugin matches plugin center information --- .../DependencyVersionMismatchException.java | 2 +- .../PluginChecksumMismatchException.java | 1 + .../PluginInformationMismatchException.java | 51 +++++++++++++++++++ .../sonia/scm/plugin/PluginInstaller.java | 47 +++++++++++++---- .../sonia/scm/plugin/PluginInstallerTest.java | 37 +++++++++++++- 5 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/plugin/PluginInformationMismatchException.java diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DependencyVersionMismatchException.java b/scm-webapp/src/main/java/sonia/scm/plugin/DependencyVersionMismatchException.java index 285a2a84b5..aefdec537b 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DependencyVersionMismatchException.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DependencyVersionMismatchException.java @@ -55,6 +55,6 @@ public class DependencyVersionMismatchException extends PluginInstallException { @Override public String getCode() { - return null; + return "E5S6niWwi1"; } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginChecksumMismatchException.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginChecksumMismatchException.java index af419fa297..389ab6439f 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginChecksumMismatchException.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginChecksumMismatchException.java @@ -26,6 +26,7 @@ package sonia.scm.plugin; import static sonia.scm.ContextEntry.ContextBuilder.entity; +@SuppressWarnings("java:S110") public class PluginChecksumMismatchException extends PluginInstallException { public PluginChecksumMismatchException(AvailablePlugin plugin, String calculatedChecksum, String expectedChecksum) { super( diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInformationMismatchException.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInformationMismatchException.java new file mode 100644 index 0000000000..d4317f25e2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInformationMismatchException.java @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.plugin; + +import lombok.Getter; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; + +@Getter +@SuppressWarnings("java:S110") +public class PluginInformationMismatchException extends PluginInstallException { + + private final PluginInformation api; + private final PluginInformation downloaded; + + public PluginInformationMismatchException(PluginInformation api, PluginInformation downloaded, String message) { + super( + entity("Plugin", api.getName()).build(), + message + ); + this.api = api; + this.downloaded = downloaded; + } + + @Override + public String getCode() { + return "4RS6niPRX1"; + } +} 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 e1115c9384..79fb14d647 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java @@ -60,14 +60,49 @@ class PluginInstaller { Files.copy(input, file); verifyChecksum(plugin, input.hash(), file); - verifyConditions(context, file); + + InstalledPluginDescriptor descriptor = smpDescriptorExtractor.extractPluginDescriptor(file); + PluginInstallationVerifier.verify(context, descriptor); + + verifyInformation(plugin.getDescriptor(), descriptor); + return new PendingPluginInstallation(plugin.install(), file); + } catch (PluginException ex) { + cleanup(file); + throw ex; } catch (IOException ex) { cleanup(file); throw new PluginDownloadException(plugin, ex); } } + private void verifyInformation(AvailablePluginDescriptor api, InstalledPluginDescriptor downloaded) { + verifyInformation(api.getInformation(), downloaded.getInformation()); + } + + private void verifyInformation(PluginInformation api, PluginInformation downloaded) { + if (!api.getName().equals(downloaded.getName())) { + throw new PluginInformationMismatchException( + api, downloaded, + String.format( + "downloaded plugin name \"%s\" does not match the expected name \"%s\" from plugin-center", + downloaded.getName(), + api.getName() + ) + ); + } + if (!api.getVersion().equals(downloaded.getVersion())) { + throw new PluginInformationMismatchException( + api, downloaded, + String.format( + "downloaded plugin version \"%s\" does not match the expected version \"%s\" from plugin-center", + downloaded.getVersion(), + api.getVersion() + ) + ); + } + } + private void cleanup(Path file) { try { if (file != null) { @@ -89,16 +124,6 @@ class PluginInstaller { } } - private void verifyConditions(PluginInstallationContext context, Path file) throws IOException { - InstalledPluginDescriptor pluginDescriptor = smpDescriptorExtractor.extractPluginDescriptor(file); - try { - PluginInstallationVerifier.verify(context, pluginDescriptor); - } catch (PluginException ex) { - cleanup(file); - throw ex; - } - } - private InputStream download(AvailablePlugin plugin) throws IOException { return client.get(plugin.getDescriptor().getUrl()).request().contentAsStream(); } 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 dced524ce1..a619289094 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java @@ -156,9 +156,38 @@ class PluginInstallerTest { assertThat(directory.resolve("plugins").resolve("scm-git-plugin.smp")).doesNotExist(); } + @Test + void shouldFailForNameMismatch() throws IOException { + mockContent("42"); + + InstalledPluginDescriptor supportedPlugin = createPluginDescriptor("scm-svn-plugin", "1.0.0", true); + when(extractor.extractPluginDescriptor(any())).thenReturn(supportedPlugin); + + PluginInstallationContext context = PluginInstallationContext.empty(); + AvailablePlugin gitPlugin = createGitPlugin(); + PluginInformationMismatchException exception = assertThrows(PluginInformationMismatchException.class, () -> installer.install(context, gitPlugin)); + assertThat(exception.getApi().getName()).isEqualTo("scm-git-plugin"); + assertThat(exception.getDownloaded().getName()).isEqualTo("scm-svn-plugin"); + } + + @Test + void shouldFailForVersionMismatch() throws IOException { + mockContent("42"); + + InstalledPluginDescriptor supportedPlugin = createPluginDescriptor("scm-git-plugin", "1.1.0", true); + when(extractor.extractPluginDescriptor(any())).thenReturn(supportedPlugin); + + PluginInstallationContext context = PluginInstallationContext.empty(); + AvailablePlugin gitPlugin = createGitPlugin(); + PluginInformationMismatchException exception = assertThrows(PluginInformationMismatchException.class, () -> installer.install(context, gitPlugin)); + assertThat(exception.getApi().getVersion()).isEqualTo("1.0.0"); + assertThat(exception.getDownloaded().getVersion()).isEqualTo("1.1.0"); + } + private AvailablePlugin createPlugin(String name, String url, String checksum) { PluginInformation information = new PluginInformation(); information.setName(name); + information.setVersion("1.0.0"); AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor( information, null, Collections.emptySet(), url, checksum ); @@ -166,9 +195,15 @@ class PluginInstallerTest { } private InstalledPluginDescriptor createPluginDescriptor(boolean supported) { + return createPluginDescriptor("scm-git-plugin", "1.0.0", supported); + } + + private InstalledPluginDescriptor createPluginDescriptor(String name, String version, boolean supported) { InstalledPluginDescriptor installedPluginDescriptor = mock(InstalledPluginDescriptor.class, RETURNS_DEEP_STUBS); + lenient().when(installedPluginDescriptor.getInformation().getId()).thenReturn(name); + lenient().when(installedPluginDescriptor.getInformation().getName()).thenReturn(name); + lenient().when(installedPluginDescriptor.getInformation().getVersion()).thenReturn(version); lenient().when(installedPluginDescriptor.getCondition().isSupported()).thenReturn(supported); - lenient().when(installedPluginDescriptor.getInformation().getId()).thenReturn("scm-git-plugin"); return installedPluginDescriptor; } }