mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-05-06 15:07:25 +02:00
Add patch endpoint for global config (#1629)
Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
@@ -24,19 +24,21 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.io.Resources;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.github.sdorra.jse.ShiroExtension;
|
||||
import org.github.sdorra.jse.SubjectAware;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.NamespaceStrategyValidator;
|
||||
import sonia.scm.util.JsonMerger;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
@@ -48,110 +50,104 @@ import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
@ExtendWith(ShiroExtension.class)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
value = "trillian"
|
||||
)
|
||||
public class ConfigResourceTest {
|
||||
class ConfigResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private RestDispatcher dispatcher = new RestDispatcher();
|
||||
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
@SuppressWarnings("unused") // Is injected
|
||||
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
|
||||
@Mock
|
||||
private NamespaceStrategyValidator namespaceStrategyValidator;
|
||||
|
||||
private final JsonMerger jsonMerger = new JsonMerger(new ObjectMapper());
|
||||
|
||||
@InjectMocks
|
||||
private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper;
|
||||
@InjectMocks
|
||||
private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper;
|
||||
|
||||
public ConfigResourceTest() {
|
||||
// cleanup state that might have been left by other tests
|
||||
ThreadContext.unbindSecurityManager();
|
||||
ThreadContext.unbindSubject();
|
||||
ThreadContext.remove();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
initMocks(this);
|
||||
|
||||
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator);
|
||||
configResource.setStore(config -> {});
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator, jsonMerger);
|
||||
configResource.setStore(config -> {
|
||||
});
|
||||
|
||||
dispatcher.addSingletonResource(configResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetGlobalConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
@SubjectAware(
|
||||
permissions = "configuration:read:global"
|
||||
)
|
||||
void shouldGetGlobalConfig() throws URISyntaxException, UnsupportedEncodingException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"heartOfGold\""));
|
||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config"));
|
||||
assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config"));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
assertThat(response.getContentAsString()).contains("\"proxyPassword\":\"heartOfGold\"");
|
||||
assertThat(response.getContentAsString()).contains("\"self\":{\"href\":\"/v2/config");
|
||||
assertThat(response.getContentAsString()).doesNotContain("\"update\":{\"href\":\"/v2/config");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException, UnsupportedEncodingException {
|
||||
void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals("Subject does not have permission [configuration:read:global]", response.getContentAsString());
|
||||
assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldUpdateConfig() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json");
|
||||
@SubjectAware(
|
||||
permissions = "configuration:read,write:global"
|
||||
)
|
||||
void shouldUpdateConfig() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = put("sonia/scm/api/v2/config-test-update.json");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
|
||||
|
||||
request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\""));
|
||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config"));
|
||||
assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config"));
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
// Should overwrite old realm description with null
|
||||
assertThat(response.getContentAsString()).contains("\"realmDescription\":null");
|
||||
assertThat(response.getContentAsString()).contains("\"proxyPassword\":\"newPassword\"");
|
||||
assertThat(response.getContentAsString()).contains("\"self\":{\"href\":\"/v2/config");
|
||||
assertThat(response.getContentAsString()).contains("\"update\":{\"href\":\"/v2/config");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = post("sonia/scm/api/v2/config-test-update.json");
|
||||
void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = put("sonia/scm/api/v2/config-test-update.json");
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals("Subject does not have permission [configuration:write:global]", response.getContentAsString());
|
||||
assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldValidateNamespaceStrategy() throws URISyntaxException {
|
||||
@SubjectAware(
|
||||
permissions = "configuration:write:global"
|
||||
)
|
||||
void shouldValidateNamespaceStrategy() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
.content("{ \"namespaceStrategy\": \"AwesomeStrategy\" }".getBytes(StandardCharsets.UTF_8));
|
||||
@@ -159,11 +155,44 @@ public class ConfigResourceTest {
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
|
||||
verify(namespaceStrategyValidator).check("AwesomeStrategy");
|
||||
}
|
||||
|
||||
private MockHttpRequest post(String resourceName) throws IOException, URISyntaxException {
|
||||
@Test
|
||||
@SubjectAware(
|
||||
permissions = "configuration:read,write:global"
|
||||
)
|
||||
void shouldUpdateConfigPartially() throws URISyntaxException, IOException {
|
||||
MockHttpRequest request = patch("{ \"proxyPort\":\"1337\", \"proxyPassword\":null }");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
|
||||
|
||||
request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
// Should not change old realm description
|
||||
assertThat(response.getContentAsString()).contains("\"realmDescription\":\"SONIA :: SCM Manager\"");
|
||||
assertThat(response.getContentAsString()).contains("\"proxyPassword\":null");
|
||||
assertThat(response.getContentAsString()).contains("\"proxyPort\":1337");
|
||||
assertThat(response.getContentAsString()).contains("\"self\":{\"href\":\"/v2/config");
|
||||
assertThat(response.getContentAsString()).contains("\"update\":{\"href\":\"/v2/config");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotUpdateConfigPartiallyIfNotAuthorized() throws URISyntaxException {
|
||||
MockHttpRequest request = patch("{ \"proxyPort\":\"1337\", \"proxyPassword\":\"hitchhiker\" }");
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
|
||||
private MockHttpRequest put(String resourceName) throws IOException, URISyntaxException {
|
||||
URL url = Resources.getResource(resourceName);
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
return MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
@@ -171,6 +200,12 @@ public class ConfigResourceTest {
|
||||
.content(configJson);
|
||||
}
|
||||
|
||||
private MockHttpRequest patch(String json) throws URISyntaxException {
|
||||
return MockHttpRequest.patch("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
.contentType(VndMediaType.CONFIG)
|
||||
.content(json.getBytes());
|
||||
}
|
||||
|
||||
private static ScmConfiguration createConfiguration() {
|
||||
ScmConfiguration scmConfiguration = new ScmConfiguration();
|
||||
scmConfiguration.setProxyPassword("heartOfGold");
|
||||
|
||||
@@ -41,6 +41,7 @@ import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.lifecycle.RestartEventFactory;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.util.JsonMerger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -62,7 +63,11 @@ import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class I18nServletTest {
|
||||
@@ -223,7 +228,7 @@ class I18nServletTest {
|
||||
|
||||
@Nonnull
|
||||
private I18nServlet createServlet() {
|
||||
return new I18nServlet(context, pluginLoader, cacheManager);
|
||||
return new I18nServlet(context, pluginLoader, cacheManager, new JsonMerger(new ObjectMapper()));
|
||||
}
|
||||
|
||||
private String doGetString(I18nServlet servlet, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
|
||||
Reference in New Issue
Block a user