diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java new file mode 100644 index 0000000000..928daf5c3a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java @@ -0,0 +1,58 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.xml.XmlSetStringAdapter; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Set; + +@NoArgsConstructor +@Getter +@Setter +public class GlobalConfigDto extends HalRepresentation { + + private String proxyPassword; + private int proxyPort; + private String proxyServer; + private String proxyUser; + private boolean enableProxy; + private String realmDescription; + private boolean enableRepositoryArchive; + private boolean disableGroupingGrid; + private String dateFormat; + private boolean anonymousAccessEnabled; + @XmlElement(name = "admin-groups") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminGroups; + @XmlElement(name = "admin-users") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminUsers; + @XmlElement(name = "base-url") + private String baseUrl; + @XmlElement(name = "force-base-url") + private boolean forceBaseUrl; + @XmlElement(name = "login-attempt-limit") + private int loginAttemptLimit; + @XmlElement(name = "proxy-excludes") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set proxyExcludes; + @XmlElement(name = "skip-failed-authenticators") + private boolean skipFailedAuthenticators; + @XmlElement(name = "plugin-url") + private String pluginUrl; + @XmlElement(name = "login-attempt-limit-timeout") + private long loginAttemptLimitTimeout; + @XmlElement(name = "xsrf-protection") + private boolean enabledXsrfProtection; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java new file mode 100644 index 0000000000..3746f00e08 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java @@ -0,0 +1,12 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.config.ScmConfiguration; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class GlobalConfigDtoToScmConfigurationMapper { + + public abstract ScmConfiguration map(GlobalConfigDto dto); +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java new file mode 100644 index 0000000000..1e1698a45a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java @@ -0,0 +1,91 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import org.apache.shiro.SecurityUtils; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; +import sonia.scm.util.ScmConfigurationUtil; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +@Path(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) +public class GlobalConfigResource { + + static final String GLOBAL_CONFIG_PATH_V2 = "v2/config/global"; + private final GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper; + private final ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper; + private final ScmConfiguration configuration; + + @Inject + public GlobalConfigResource(GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.configuration = configuration; + } + + /** + * Returns the global scm config. + */ + @GET + @Path("") + @Produces(VndMediaType.GLOBAL_CONFIG) + @TypeHint(UserDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + Response response; + + // TODO ConfigPermisions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + response = Response.ok(configToDtoMapper.map(configuration)).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } + + /** + * Modifies the global scm config. + * + * @param configDto new global scm configuration as DTO + */ + @PUT + @Path("") + @Consumes(VndMediaType.GLOBAL_CONFIG) + @StatusCodes({ + @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(GlobalConfigDto configDto, @Context UriInfo uriInfo) { + Response response; + + // TODO ConfigPermisions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + ScmConfiguration config = dtoToConfigMapper.map(configDto); + configuration.load(config); + synchronized (ScmConfiguration.class) { + ScmConfigurationUtil.getInstance().store(configuration); + } + response = Response.created(uriInfo.getRequestUri()).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index fec2a677b5..72a4e5e062 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule { bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass()); bind(GroupCollectionToDtoMapper.class); + bind(ScmConfigurationToGlobalConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToGlobalConfigDtoMapper.class).getClass()); + bind(GlobalConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(GlobalConfigDtoToScmConfigurationMapper.class).getClass()); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } 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 13691da0f6..83faa57e35 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 @@ -76,7 +76,7 @@ class ResourceLinks { return userLinkBuilder.method("getUserResource").parameters(name).method("delete").parameters().href(); } - String update(String name) { + String update(String name) { return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href(); } } @@ -100,4 +100,24 @@ class ResourceLinks { return collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().href(); } } + + GlobalConfigLinks globalConfig() { + return new GlobalConfigLinks(uriInfoStore.get()); + } + + static class GlobalConfigLinks { + private final LinkBuilder globalConfigLinkBuilder; + + private GlobalConfigLinks(UriInfo uriInfo) { + globalConfigLinkBuilder = new LinkBuilder(uriInfo, GlobalConfigResource.class); + } + + String self() { + return globalConfigLinkBuilder.method("get").parameters().href(); + } + + String update() { + return globalConfigLinkBuilder.method("update").parameters().href(); + } + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java new file mode 100644 index 0000000000..129ebc7fbe --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java @@ -0,0 +1,36 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.apache.shiro.SecurityUtils; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class ScmConfigurationToGlobalConfigDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + public abstract GlobalConfigDto map(ScmConfiguration config); + + @AfterMapping + void appendLinks(ScmConfiguration config, @MappingTarget GlobalConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.globalConfig().self()); + // TODO: ConfigPermissions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + linksBuilder.single(link("update", resourceLinks.globalConfig().update())); + } + target.add(linksBuilder.build()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java new file mode 100644 index 0000000000..b08303ca4e --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java @@ -0,0 +1,33 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import sonia.scm.config.ScmConfiguration; + +import static org.junit.Assert.assertEquals; +import static org.mockito.MockitoAnnotations.initMocks; + +public class GlobalConfigDtoToScmConfigurationMapperTest { + + @InjectMocks + private GlobalConfigDtoToScmConfigurationMapperImpl mapper; + + @Test + public void shouldMapFields() { + GlobalConfigDto dto = createDefaultDto(); + ScmConfiguration config = mapper.map(dto); + assertEquals("baseurl" , config.getBaseUrl()); + } + + @Before + public void init() { + initMocks(this); + } + + private GlobalConfigDto createDefaultDto() { + GlobalConfigDto globalConfigDto = new GlobalConfigDto(); + globalConfigDto.setBaseUrl("baseurl"); + return globalConfigDto; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java new file mode 100644 index 0000000000..49945bedd2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java @@ -0,0 +1,148 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import com.google.common.io.Resources; +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.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.web.VndMediaType; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +@SubjectAware( + username = "trillian", + password = "secret", + configuration = "classpath:sonia/scm/repository/shiro.ini" +) +public class GlobalConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ResourceLinks resourceLinks; + + @InjectMocks + private GlobalConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper; + @InjectMocks + private ScmConfigurationToGlobalConfigDtoMapperImpl configToDtoMapper; + + @Before + public void prepareEnvironment() throws IOException { + initMocks(this); + + ResourceLinksMock.initMock(resourceLinks, URI.create("/")); + + GlobalConfigResource globalConfigResource = new GlobalConfigResource(dtoToConfigMapper, + configToDtoMapper, createConfiguration()); + + dispatcher.getRegistry().addSingletonResource(globalConfigResource); + } + + @Test + public void shouldGetGlobalConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_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/global")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + } + + @SubjectAware( + username = "dent" + ) + @Test + public void shouldGetForbiddenGlobalConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + @Test + public void shouldUpdateGlobalConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) + .contentType(VndMediaType.GLOBAL_CONFIG) + .content(configJson); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_CREATED, response.getStatus()); + + request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_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/global")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + + } + + @SubjectAware( + username = "dent" + ) + @Test + public void shouldUpdateForbiddenGlobalConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) + .contentType(VndMediaType.GLOBAL_CONFIG) + .content(configJson); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + public static ScmConfiguration createConfiguration() { + ScmConfiguration scmConfiguration = new ScmConfiguration(); + scmConfiguration.setProxyPassword("heartOfGold"); + scmConfiguration.setProxyPort(1234); + scmConfiguration.setProxyServer("proxyserver"); + scmConfiguration.setProxyUser("trillian"); + scmConfiguration.setEnableProxy(true); + scmConfiguration.setRealmDescription("description"); + scmConfiguration.setEnableRepositoryArchive(true); + scmConfiguration.setDisableGroupingGrid(true); + scmConfiguration.setDateFormat("dd"); + scmConfiguration.setAnonymousAccessEnabled(true); + scmConfiguration.setAdminGroups(new HashSet<>(Arrays.asList("group"))); + scmConfiguration.setAdminUsers(new HashSet<>(Arrays.asList("user1"))); + scmConfiguration.setBaseUrl("baseurl"); + scmConfiguration.setForceBaseUrl(true); + scmConfiguration.setLoginAttemptLimit(1); + scmConfiguration.setProxyExcludes(new HashSet<>(Arrays.asList("arthur", "dent"))); + scmConfiguration.setSkipFailedAuthenticators(true); + scmConfiguration.setPluginUrl("pluginurl"); + scmConfiguration.setLoginAttemptLimitTimeout(2); + scmConfiguration.setEnabledXsrfProtection(true); + return scmConfiguration; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 119ced5397..dc1c277984 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -4,6 +4,7 @@ import java.net.URI; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.GlobalConfigResource.GLOBAL_CONFIG_PATH_V2; import static sonia.scm.api.v2.resources.GroupRootResource.GROUPS_PATH_V2; import static sonia.scm.api.v2.resources.UserRootResource.USERS_PATH_V2; @@ -22,5 +23,8 @@ public class ResourceLinksMock { when(resourceLinks.groupCollection().self()).thenAnswer(invocation -> baseUri + GROUPS_PATH_V2); when(resourceLinks.groupCollection().create()).thenAnswer(invocation -> baseUri + GROUPS_PATH_V2); + + when(resourceLinks.globalConfig().self()).thenAnswer(invocation -> baseUri + GLOBAL_CONFIG_PATH_V2); + when(resourceLinks.globalConfig().update()).thenAnswer(invocation -> baseUri + GLOBAL_CONFIG_PATH_V2); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index 103eebd411..53a8578496 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -84,6 +84,18 @@ public class ResourceLinksTest { assertEquals(BASE_URL + GroupRootResource.GROUPS_PATH_V2, url); } + @Test + public void shouldCreateCorrectGlobalConfigSelfUrl() { + String url = resourceLinks.globalConfig().self(); + assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + } + + @Test + public void shouldCreateCorrectGlobalConfigUpdateUrl() { + String url = resourceLinks.globalConfig().update(); + assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + } + @Before public void initUriInfo() { initMocks(this); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java new file mode 100644 index 0000000000..e67e8764d5 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java @@ -0,0 +1,72 @@ +package sonia.scm.api.v2.resources; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; +import static sonia.scm.api.v2.resources.GlobalConfigResourceTest.createConfiguration; + +public class ScmConfigurationToGlobalConfigDtoMapperTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ResourceLinks resourceLinks; + + @InjectMocks + private ScmConfigurationToGlobalConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() throws URISyntaxException { + initMocks(this); + URI baseUri = new URI("http://example.com/base/"); + expectedBaseUri = baseUri.resolve(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + subjectThreadState.bind(); + ResourceLinksMock.initMock(resourceLinks, baseUri); + ThreadContext.bind(subject); + } + + @Test + public void shouldMapFields() { + ScmConfiguration config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(true); + GlobalConfigDto dto = mapper.map(config); + + assertEquals("baseurl", dto.getBaseUrl()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + @Test + public void shouldMapFieldsWithoutUpdate() { + ScmConfiguration config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(false); + GlobalConfigDto dto = mapper.map(config); + + assertEquals("baseurl", dto.getBaseUrl()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + +} diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json new file mode 100644 index 0000000000..bc45315a0c --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json @@ -0,0 +1,3 @@ +{ + "proxyPassword": "newPassword" +}