diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java index 6e90106096..f8a8aa561f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailablePluginResource.java @@ -81,7 +81,7 @@ public class AvailablePluginResource { /** * Triggers plugin installation. - * @param name plugin artefact name + * @param name plugin name * @return HTTP Status. */ @POST diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index d670a6609c..0ba8636c66 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -74,6 +74,7 @@ public class DefaultPluginManager implements PluginManager { @Override public Optional getAvailable(String name) { + PluginPermissions.read().check(); return center.getAvailable() .stream() .filter(filterByName(name)) @@ -83,6 +84,7 @@ public class DefaultPluginManager implements PluginManager { @Override public Optional getInstalled(String name) { + PluginPermissions.read().check(); return loader.getInstalledPlugins() .stream() .filter(filterByName(name)) @@ -91,11 +93,13 @@ public class DefaultPluginManager implements PluginManager { @Override public List getInstalled() { + PluginPermissions.read().check(); return ImmutableList.copyOf(loader.getInstalledPlugins()); } @Override public List getAvailable() { + PluginPermissions.read().check(); return center.getAvailable().stream().filter(this::isNotInstalled).collect(Collectors.toList()); } @@ -109,6 +113,7 @@ public class DefaultPluginManager implements PluginManager { @Override public void install(String name) { + PluginPermissions.manage().check(); List plugins = collectPluginsToInstall(name); List pendingInstallations = new ArrayList<>(); for (AvailablePlugin plugin : plugins) { @@ -131,6 +136,7 @@ public class DefaultPluginManager implements PluginManager { collectPluginsToInstall(plugins, name); return plugins; } + private void collectPluginsToInstall(List plugins, String name) { if (!getInstalled(name).isPresent()) { AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name))); diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java index 2ab111689a..a58c721e86 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java @@ -2,12 +2,19 @@ package sonia.scm.plugin; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.NotFoundException; import java.util.List; import java.util.Optional; @@ -31,150 +38,226 @@ class DefaultPluginManagerTest { @InjectMocks private DefaultPluginManager manager; - @Test - void shouldReturnInstalledPlugins() { - InstalledPlugin review = createInstalled("scm-review-plugin"); - InstalledPlugin git = createInstalled("scm-git-plugin"); + @Mock + private Subject subject; - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git)); + @Nested + class WithAdminPermissions { + + @BeforeEach + void setUpSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void clearThreadContext() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldReturnInstalledPlugins() { + InstalledPlugin review = createInstalled("scm-review-plugin"); + InstalledPlugin git = createInstalled("scm-git-plugin"); + + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git)); + + List installed = manager.getInstalled(); + assertThat(installed).containsOnly(review, git); + } + + @Test + void shouldReturnReviewPlugin() { + InstalledPlugin review = createInstalled("scm-review-plugin"); + InstalledPlugin git = createInstalled("scm-git-plugin"); + + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git)); + + Optional plugin = manager.getInstalled("scm-review-plugin"); + assertThat(plugin).contains(review); + } + + @Test + void shouldReturnEmptyForNonInstalledPlugin() { + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of()); + + Optional plugin = manager.getInstalled("scm-review-plugin"); + assertThat(plugin).isEmpty(); + } + + @Test + void shouldReturnAvailablePlugins() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + AvailablePlugin git = createAvailable("scm-git-plugin"); + + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); + + List available = manager.getAvailable(); + assertThat(available).containsOnly(review, git); + } + + @Test + void shouldFilterOutAllInstalled() { + InstalledPlugin installedGit = createInstalled("scm-git-plugin"); + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit)); + + AvailablePlugin review = createAvailable("scm-review-plugin"); + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); + + List available = manager.getAvailable(); + assertThat(available).containsOnly(review); + } + + @Test + void shouldReturnAvailable() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); + + Optional available = manager.getAvailable("scm-git-plugin"); + assertThat(available).contains(git); + } + + @Test + void shouldReturnEmptyForNonExistingAvailable() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review)); + + Optional available = manager.getAvailable("scm-git-plugin"); + assertThat(available).isEmpty(); + } + + @Test + void shouldReturnEmptyForInstalledPlugin() { + InstalledPlugin installedGit = createInstalled("scm-git-plugin"); + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit)); + + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); + + Optional available = manager.getAvailable("scm-git-plugin"); + assertThat(available).isEmpty(); + } + + @Test + void shouldInstallThePlugin() { + AvailablePlugin git = createAvailable("scm-git-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); + + manager.install("scm-git-plugin"); + + verify(installer).install(git); + } + + @Test + void shouldInstallDependingPlugins() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); + + manager.install("scm-review-plugin"); + + verify(installer).install(mail); + verify(installer).install(review); + } + + @Test + void shouldNotInstallAlreadyInstalledDependencies() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); + + InstalledPlugin installedMail = createInstalled("scm-mail-plugin"); + when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail)); + + manager.install("scm-review-plugin"); + + verify(installer).install(review); + } + + @Test + void shouldRollbackOnFailedInstallation() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin"); + when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin")); + AvailablePlugin notification = createAvailable("scm-notification-plugin"); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail, notification)); + + PendingPluginInstallation pendingNotification = mock(PendingPluginInstallation.class); + doReturn(pendingNotification).when(installer).install(notification); + + PendingPluginInstallation pendingMail = mock(PendingPluginInstallation.class); + doReturn(pendingMail).when(installer).install(mail); + + doThrow(new PluginChecksumMismatchException("checksum does not match")).when(installer).install(review); + + assertThrows(PluginInstallException.class, () -> manager.install("scm-review-plugin")); + + verify(pendingNotification).cancel(); + verify(pendingMail).cancel(); + } + + @Test + void shouldInstallNothingIfOneOfTheDependenciesIsNotAvailable() { + AvailablePlugin review = createAvailable("scm-review-plugin"); + when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); + AvailablePlugin mail = createAvailable("scm-mail-plugin"); + when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin")); + when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); + + assertThrows(NotFoundException.class, () -> manager.install("scm-review-plugin")); + + verify(installer, never()).install(any()); + } - List installed = manager.getInstalled(); - assertThat(installed).containsOnly(review, git); } - @Test - void shouldReturnReviewPlugin() { - InstalledPlugin review = createInstalled("scm-review-plugin"); - InstalledPlugin git = createInstalled("scm-git-plugin"); + @Nested + class WithoutReadPermissions { - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git)); + @BeforeEach + void setUpSubject() { + ThreadContext.bind(subject); + doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:read"); + } + + @AfterEach + void clearThreadContext() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldThrowAuthorizationExceptionsForReadMethods() { + assertThrows(AuthorizationException.class, () -> manager.getInstalled()); + assertThrows(AuthorizationException.class, () -> manager.getInstalled("test")); + assertThrows(AuthorizationException.class, () -> manager.getAvailable()); + assertThrows(AuthorizationException.class, () -> manager.getAvailable("test")); + } - Optional plugin = manager.getInstalled("scm-review-plugin"); - assertThat(plugin).contains(review); } - @Test - void shouldReturnEmptyForNonInstalledPlugin() { - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of()); + @Nested + class WithoutManagePermissions { - Optional plugin = manager.getInstalled("scm-review-plugin"); - assertThat(plugin).isEmpty(); - } + @BeforeEach + void setUpSubject() { + ThreadContext.bind(subject); + doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:manage"); + } - @Test - void shouldReturnAvailablePlugins() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - AvailablePlugin git = createAvailable("scm-git-plugin"); + @AfterEach + void clearThreadContext() { + ThreadContext.unbindSubject(); + } - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); + @Test + void shouldThrowAuthorizationExceptionsForInstallMethod() { + assertThrows(AuthorizationException.class, () -> manager.install("test")); + } - List available = manager.getAvailable(); - assertThat(available).containsOnly(review, git); - } - - @Test - void shouldFilterOutAllInstalled() { - InstalledPlugin installedGit = createInstalled("scm-git-plugin"); - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit)); - - AvailablePlugin review = createAvailable("scm-review-plugin"); - AvailablePlugin git = createAvailable("scm-git-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); - - List available = manager.getAvailable(); - assertThat(available).containsOnly(review); - } - - @Test - void shouldReturnAvailable() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - AvailablePlugin git = createAvailable("scm-git-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git)); - - Optional available = manager.getAvailable("scm-git-plugin"); - assertThat(available).contains(git); - } - - @Test - void shouldReturnEmptyForNonExistingAvailable() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review)); - - Optional available = manager.getAvailable("scm-git-plugin"); - assertThat(available).isEmpty(); - } - - @Test - void shouldReturnEmptyForInstalledPlugin() { - InstalledPlugin installedGit = createInstalled("scm-git-plugin"); - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit)); - - AvailablePlugin git = createAvailable("scm-git-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); - - Optional available = manager.getAvailable("scm-git-plugin"); - assertThat(available).isEmpty(); - } - - @Test - void shouldInstallThePlugin() { - AvailablePlugin git = createAvailable("scm-git-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(git)); - - manager.install("scm-git-plugin"); - - verify(installer).install(git); - } - - @Test - void shouldInstallDependingPlugins() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); - AvailablePlugin mail = createAvailable("scm-mail-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); - - manager.install("scm-review-plugin"); - - verify(installer).install(mail); - verify(installer).install(review); - } - - @Test - void shouldNotInstallAlreadyInstalledDependencies() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); - AvailablePlugin mail = createAvailable("scm-mail-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail)); - - InstalledPlugin installedMail = createInstalled("scm-mail-plugin"); - when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail)); - - manager.install("scm-review-plugin"); - - verify(installer).install(review); - } - - @Test - void shouldRollbackOnFailedInstallation() { - AvailablePlugin review = createAvailable("scm-review-plugin"); - when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin")); - AvailablePlugin mail = createAvailable("scm-mail-plugin"); - when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin")); - AvailablePlugin notification = createAvailable("scm-notification-plugin"); - when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail, notification)); - - PendingPluginInstallation pendingNotification = mock(PendingPluginInstallation.class); - doReturn(pendingNotification).when(installer).install(notification); - - PendingPluginInstallation pendingMail = mock(PendingPluginInstallation.class); - doReturn(pendingMail).when(installer).install(mail); - - doThrow(new PluginChecksumMismatchException("checksum does not match")).when(installer).install(review); - - assertThrows(PluginInstallException.class, () -> manager.install("scm-review-plugin")); - - verify(pendingNotification).cancel(); - verify(pendingMail).cancel(); } private AvailablePlugin createAvailable(String name) {