From 52a9429ef5e511453443458af53562888863fdc9 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 27 Sep 2019 12:47:41 +0200 Subject: [PATCH] Add cancel link --- .../v2/resources/PendingPluginResource.java | 7 +- .../scm/api/v2/resources/ResourceLinks.java | 4 + .../resources/PendingPluginResourceTest.java | 202 ++++++++++++------ 3 files changed, 144 insertions(+), 69 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java index 17fff25f16..7d4663829e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PendingPluginResource.java @@ -8,6 +8,7 @@ import de.otto.edison.hal.Links; import sonia.scm.plugin.AvailablePlugin; import sonia.scm.plugin.InstalledPlugin; import sonia.scm.plugin.PluginManager; +import sonia.scm.plugin.PluginPermissions; import sonia.scm.web.VndMediaType; import javax.inject.Inject; @@ -68,8 +69,12 @@ public class PendingPluginResource { List updateDtos = updatePlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); List uninstallDtos = uninstallPlugins.map(i -> mapper.mapInstalled(i, pending)).collect(toList()); - if (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty()) { + if ( + PluginPermissions.manage().isPermitted() && + (!installDtos.isEmpty() || !updateDtos.isEmpty() || !uninstallDtos.isEmpty()) + ) { linksBuilder.single(link("execute", resourceLinks.pendingPluginCollection().executePending())); + linksBuilder.single(link("cancel", resourceLinks.pendingPluginCollection().cancelPending())); } Embedded.Builder embedded = Embedded.embeddedBuilder(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index c36cfc09ad..416af2e478 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -739,6 +739,10 @@ class ResourceLinks { return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("executePending").parameters().href(); } + String cancelPending() { + return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("cancelPending").parameters().href(); + } + String self() { return pendingPluginCollectionLinkBuilder.method("pendingPlugins").parameters().method("getPending").parameters().href(); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java index e1b0325cb2..da6e1b65d7 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PendingPluginResourceTest.java @@ -1,11 +1,16 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; +import org.apache.shiro.ShiroException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockDispatcherFactory; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; +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.InjectMocks; @@ -19,6 +24,8 @@ import sonia.scm.plugin.PluginInformation; import sonia.scm.plugin.PluginManager; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; @@ -44,6 +51,9 @@ class PendingPluginResourceTest { @Mock PluginDtoMapper mapper; + @Mock + Subject subject; + @InjectMocks PendingPluginResource pendingPluginResource; @@ -52,6 +62,7 @@ class PendingPluginResourceTest { @BeforeEach void prepareEnvironment() { dispatcher = MockDispatcherFactory.createDispatcher(); + dispatcher.getProviderFactory().register(new PermissionExceptionMapper()); PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource)); dispatcher.getRegistry().addSingletonResource(pluginRootResource); } @@ -70,83 +81,138 @@ class PendingPluginResourceTest { }); } - @Test - void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); - when(availablePlugin.isPending()).thenReturn(false); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + @Nested + class withAuthorization { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + lenient().when(subject.isPermitted("plugin:manage")).thenReturn(true); + } + + @AfterEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); + when(availablePlugin.isPending()).thenReturn(false); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); + assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); + } + + @Test + void shouldGetPendingAvailablePluginListWithInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + assertThat(response.getContentAsString()).contains("\"cancel\":{\"href\":\"/v2/plugins/pending/cancel\"}"); + } + + @Test + void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + when(pluginManager.getAvailable()).thenReturn(emptyList()); + InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); + when(installedPlugin.isMarkedForUninstall()).thenReturn(true); + when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); + assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + } + + @Test + void shouldExecutePendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).executePendingAndRestart(); + } + + @Test + void shouldCancelPendingPlugins() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/cancel"); + + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(pluginManager).cancelPending(); + } - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); - assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); } - @Test - void shouldGetPendingAvailablePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + @Nested + class WithoutAuthorization { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + when(subject.isPermitted("plugin:manage")).thenReturn(false); + } - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + @AfterEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldGetPendingAvailablePluginListWithoutInstallAndCancelLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); + + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"new\":[{\"name\":\"pending-available-plugin\""); + assertThat(response.getContentAsString()).doesNotContain("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); + assertThat(response.getContentAsString()).doesNotContain("\"cancel\":{\"href\":\"/v2/plugins/pending/cancel\"}"); + } } - @Test - void shouldGetPendingUpdatePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - AvailablePlugin availablePlugin = createAvailablePlugin("available-plugin"); - when(availablePlugin.isPending()).thenReturn(true); - when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - InstalledPlugin installedPlugin = createInstalledPlugin("available-plugin"); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); + static class PermissionExceptionMapper implements ExceptionMapper { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"update\":[{\"name\":\"available-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldGetPendingUninstallPluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { - when(pluginManager.getAvailable()).thenReturn(emptyList()); - InstalledPlugin installedPlugin = createInstalledPlugin("uninstalled-plugin"); - when(installedPlugin.isMarkedForUninstall()).thenReturn(true); - when(pluginManager.getInstalled()).thenReturn(singletonList(installedPlugin)); - - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(response.getContentAsString()).contains("\"uninstall\":[{\"name\":\"uninstalled-plugin\""); - assertThat(response.getContentAsString()).contains("\"execute\":{\"href\":\"/v2/plugins/pending/execute\"}"); - } - - @Test - void shouldExecutePendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(pluginManager).executePendingAndRestart(); - } - - @Test - void shouldCancelPendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/cancel"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(pluginManager).cancelPending(); + @Override + public Response toResponse(ShiroException exception) { + return Response.status(401).entity(exception.getMessage()).build(); + } } private AvailablePlugin createAvailablePlugin(String name) {