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 3997a5e7c5..17fff25f16 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,7 +8,6 @@ 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; @@ -46,8 +45,6 @@ public class PendingPluginResource { }) @Produces(VndMediaType.PLUGIN_COLLECTION) public Response getPending() { - PluginPermissions.manage().check(); - List pending = pluginManager .getAvailable() .stream() @@ -106,8 +103,18 @@ public class PendingPluginResource { @ResponseCode(code = 500, condition = "internal server error") }) public Response executePending() { - PluginPermissions.manage().check(); pluginManager.executePendingAndRestart(); return Response.ok().build(); } + + @POST + @Path("/cancel") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response cancelPending() { + pluginManager.cancelPending(); + return Response.ok().build(); + } } 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 e5587b78cd..e1b0325cb2 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,16 +1,11 @@ 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; @@ -24,8 +19,6 @@ 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; @@ -34,11 +27,8 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,9 +44,6 @@ class PendingPluginResourceTest { @Mock PluginDtoMapper mapper; - @Mock - Subject subject; - @InjectMocks PendingPluginResource pendingPluginResource; @@ -65,7 +52,6 @@ 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); } @@ -74,141 +60,93 @@ class PendingPluginResourceTest { void mockMapper() { lenient().when(mapper.mapAvailable(any())).thenAnswer(invocation -> { PluginDto dto = new PluginDto(); - dto.setName(((AvailablePlugin)invocation.getArgument(0)).getDescriptor().getInformation().getName()); + dto.setName(((AvailablePlugin) invocation.getArgument(0)).getDescriptor().getInformation().getName()); return dto; }); lenient().when(mapper.mapInstalled(any(), any())).thenAnswer(invocation -> { PluginDto dto = new PluginDto(); - dto.setName(((InstalledPlugin)invocation.getArgument(0)).getDescriptor().getInformation().getName()); + dto.setName(((InstalledPlugin) invocation.getArgument(0)).getDescriptor().getInformation().getName()); return dto; }); } - @Nested - class withAuthorization { + @Test + void shouldGetEmptyPluginListsWithoutInstallLinkWhenNoPendingPluginsPresent() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("not-pending-plugin"); + when(availablePlugin.isPending()).thenReturn(false); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - @BeforeEach - void bindSubject() { - ThreadContext.bind(subject); - doNothing().when(subject).checkPermission("plugin:manage"); - } + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); - @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 shouldGetPendingAvailablePluginListWithInstallLink() 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\"}"); - } - - @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(); - } + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getContentAsString()).contains("\"_links\":{\"self\":{\"href\":\"/v2/plugins/pending\"}}"); + assertThat(response.getContentAsString()).doesNotContain("not-pending-plugin"); } - @Nested - class WithoutAuthorization { + @Test + void shouldGetPendingAvailablePluginListWithInstallLink() throws URISyntaxException, UnsupportedEncodingException { + AvailablePlugin availablePlugin = createAvailablePlugin("pending-available-plugin"); + when(availablePlugin.isPending()).thenReturn(true); + when(pluginManager.getAvailable()).thenReturn(singletonList(availablePlugin)); - @BeforeEach - void bindSubject() { - ThreadContext.bind(subject); - doThrow(new ShiroException()).when(subject).checkPermission("plugin:manage"); - } + MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); + dispatcher.invoke(request, response); - @AfterEach - void unbindSubject() { - ThreadContext.unbindSubject(); - } - - @Test - void shouldNotListPendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/v2/plugins/pending"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - verify(pluginManager, never()).executePendingAndRestart(); - } - - @Test - void shouldNotExecutePendingPlugins() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.post("/v2/plugins/pending/execute"); - - dispatcher.invoke(request, response); - - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - verify(pluginManager, never()).executePendingAndRestart(); - } + 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\"}"); } - static class PermissionExceptionMapper implements ExceptionMapper { + @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)); - @Override - public Response toResponse(ShiroException exception) { - return Response.status(401).entity(exception.getMessage()).build(); - } + 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(); } private AvailablePlugin createAvailablePlugin(String name) {