From 9e85cb81a7961e1c0d636447d82d4cb822be3772 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 10:51:14 +0200 Subject: [PATCH 01/13] Create new branch From 40ee98dafeb522d97dfb9efff3643f3b38c3d871 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 11:08:52 +0200 Subject: [PATCH 02/13] Add media type for global config --- scm-core/src/main/java/sonia/scm/web/VndMediaType.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 3ec121f9a4..31ea2222c9 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -17,6 +17,8 @@ public class VndMediaType { public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; + public static final String GLOBAL_CONFIG = PREFIX + "global_config" + SUFFIX; + private VndMediaType() { } From 50b6b58692ba88b9822c20f6bec1c3b8e13590b3 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 13:49:42 +0200 Subject: [PATCH 03/13] Remove unused code in GroupRootResourceTest --- .../sonia/scm/api/v2/resources/GroupRootResourceTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 7f88ceb50f..9c943efdfa 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -115,8 +115,6 @@ public class GroupRootResourceTest { Group group = createDummyGroup(); when(groupManager.get("admin")).thenReturn(group); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "admin") @@ -138,9 +136,6 @@ public class GroupRootResourceTest { URL url = Resources.getResource("sonia/scm/api/v2/group-test-update.json"); byte[] groupJson = Resources.toByteArray(url); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); - MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "idontexist") .contentType(VndMediaType.GROUP) From 8c68a2de2403279a1d6e94d19f2f2b984e1659e2 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 14:45:00 +0200 Subject: [PATCH 04/13] Implement global config endpoint v2 --- .../scm/api/v2/resources/GlobalConfigDto.java | 58 +++++++ ...obalConfigDtoToScmConfigurationMapper.java | 12 ++ .../v2/resources/GlobalConfigResource.java | 91 +++++++++++ .../scm/api/v2/resources/MapperModule.java | 3 + .../scm/api/v2/resources/ResourceLinks.java | 22 ++- ...mConfigurationToGlobalConfigDtoMapper.java | 36 +++++ ...ConfigDtoToScmConfigurationMapperTest.java | 33 ++++ .../resources/GlobalConfigResourceTest.java | 148 ++++++++++++++++++ .../api/v2/resources/ResourceLinksMock.java | 4 + .../api/v2/resources/ResourceLinksTest.java | 12 ++ ...figurationToGlobalConfigDtoMapperTest.java | 72 +++++++++ .../scm/api/v2/globalConfig-test-update.json | 3 + 12 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java create mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json 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" +} From 5dca6d402d3c90f23e7525ff68e0774bc14cd48e Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Mon, 16 Jul 2018 11:04:47 +0200 Subject: [PATCH 05/13] Unbind security subject after tests --- .../ScmConfigurationToGlobalConfigDtoMapperTest.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 index d62924050b..1cf08769f0 100644 --- 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 @@ -4,6 +4,7 @@ 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.After; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -41,6 +42,11 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { ThreadContext.bind(subject); } + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + @Test public void shouldMapFields() { ScmConfiguration config = createConfiguration(); From 7572580ac124d235d5bd2287fd212fc2d6f3605c Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Mon, 30 Jul 2018 17:41:00 +0200 Subject: [PATCH 06/13] Create fine-grained configuration permissions. No more hard-coded isAdmin() checks. --- pom.xml | 17 +++++++- scm-core/pom.xml | 9 ++--- .../java/sonia/scm/config/Configuration.java | 28 +++++++++++++ .../sonia/scm/config/ScmConfiguration.java | 12 +++++- .../repository/AbstractRepositoryHandler.java | 2 +- .../AbstractSimpleRepositoryHandler.java | 2 +- ...itoryConfig.java => RepositoryConfig.java} | 26 ++++++++---- .../RepositoryHandlerConfigChangedEvent.java | 2 +- .../sonia/scm/repository/RepositoryUtil.java | 2 +- .../scm/repository/RepositoryUtilTest.java | 9 ++++- .../java/sonia/scm/repository/GitConfig.java | 11 ++++- .../java/sonia/scm/repository/HgConfig.java | 14 +++++-- .../java/sonia/scm/repository/SvnConfig.java | 12 ++++-- .../repository/DummyRepositoryHandler.java | 19 ++++++--- .../v2/resources/GlobalConfigResource.java | 40 +++++++++---------- ...mConfigurationToGlobalConfigDtoMapper.java | 6 +-- .../sonia/scm/filter/AdminSecurityFilter.java | 1 + .../resources/GlobalConfigResourceTest.java | 33 ++++++++------- ...figurationToGlobalConfigDtoMapperTest.java | 4 +- .../sonia/scm/configuration/shiro.ini | 9 +++++ 20 files changed, 181 insertions(+), 77 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/config/Configuration.java rename scm-core/src/main/java/sonia/scm/repository/{SimpleRepositoryConfig.java => RepositoryConfig.java} (82%) create mode 100644 scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini diff --git a/pom.xml b/pom.xml index 82e0c050d6..a249734dca 100644 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,6 @@ - jitpack https://jitpack.io @@ -167,6 +166,20 @@ test + + + com.github.schnatterer.shiro-static-permissions + ssp-lib + ${ssp.version} + + + + com.github.schnatterer.shiro-static-permissions + ssp-processor + ${ssp.version} + true + + @@ -551,7 +564,7 @@ 9.2.10.v20150310 - 1.0.0-SNAPSHOT + -SNAPSHOT 1.4.0 diff --git a/scm-core/pom.xml b/scm-core/pom.xml index d80ff3bcaa..a500dba32e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -128,17 +128,16 @@ 2.0.0-SNAPSHOT provided - + + - com.github.sdorra + com.github.schnatterer.shiro-static-permissions ssp-lib - ${ssp.version} - com.github.sdorra + com.github.schnatterer.shiro-static-permissions ssp-processor - ${ssp.version} true diff --git a/scm-core/src/main/java/sonia/scm/config/Configuration.java b/scm-core/src/main/java/sonia/scm/config/Configuration.java new file mode 100644 index 0000000000..e9bf3528d5 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/config/Configuration.java @@ -0,0 +1,28 @@ +package sonia.scm.config; + +import com.github.sdorra.ssp.PermissionObject; +import com.github.sdorra.ssp.StaticPermissions; + +/** + * Base for all kinds of configurations. + * + * Allows for permission like + * + *
    + *
  • "configuration:read:global",
  • + *
  • "configuration:write:svn",
  • + *
  • "configuration:*:git",
  • + *
  • "configuration:*"
  • + *
+ * + *
+ * + * And for permission checks like {@code ConfigurationPermissions.read(configurationObject).check();} + */ +@StaticPermissions( + value = "configuration", + permissions = {"read", "write"}, + globalPermissions = {} +) +public interface Configuration extends PermissionObject { +} diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index c7c3517099..6e1db68f91 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -44,6 +44,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.io.File; import java.util.Set; @@ -57,10 +58,11 @@ import java.util.concurrent.TimeUnit; * * @author Sebastian Sdorra */ + @Singleton @XmlRootElement(name = "scm-config") @XmlAccessorType(XmlAccessType.FIELD) -public class ScmConfiguration { +public class ScmConfiguration implements Configuration { /** * Default JavaScript date format @@ -501,4 +503,12 @@ public class ScmConfiguration { public void setDefaultNamespaceStrategy(String defaultNamespaceStrategy) { this.defaultNamespaceStrategy = defaultNamespaceStrategy; } + + @Override + // Only for permission checks, don't serialize to XML + @XmlTransient + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "global"; + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java index a327b50dae..33b6cb8030 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java @@ -56,7 +56,7 @@ import sonia.scm.store.ConfigurationStoreFactory; * * @param */ -public abstract class AbstractRepositoryHandler +public abstract class AbstractRepositoryHandler implements RepositoryHandler { diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index aa28403116..536b7bd9e9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -55,7 +55,7 @@ import java.net.URL; * @param * @author Sebastian Sdorra */ -public abstract class AbstractSimpleRepositoryHandler +public abstract class AbstractSimpleRepositoryHandler extends AbstractRepositoryHandler implements RepositoryDirectoryHandler { public static final String DEFAULT_VERSION_INFORMATION = "unknown"; diff --git a/scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java similarity index 82% rename from scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java rename to scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java index 3654b9c8b4..3db13e5618 100644 --- a/scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java @@ -33,15 +33,12 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import sonia.scm.Validateable; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; +import sonia.scm.config.Configuration; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import java.io.File; /** * Basic {@link Repository} configuration class. @@ -49,7 +46,7 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra */ @XmlRootElement -public class SimpleRepositoryConfig implements Validateable +public abstract class RepositoryConfig implements Validateable, Configuration { /** @@ -119,4 +116,19 @@ public class SimpleRepositoryConfig implements Validateable /** directory for repositories */ private File repositoryDirectory; + + /** + * Specifies the identifier of the concrete {@link RepositoryConfig} when checking permissions of an object. + * The permission Strings will have the following format: "configuration:*:ID", where the ID part is defined by this + * method. + * + * For example: "configuration:read:git". + * + * No need to serialize this. + * + * @return identifier of this RepositoryConfig in permission strings + */ + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public abstract String getId(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java index 844c4a78d6..645274e494 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java @@ -37,7 +37,7 @@ import sonia.scm.event.Event; * @since 2.0.0 */ @Event -public class RepositoryHandlerConfigChangedEvent +public class RepositoryHandlerConfigChangedEvent { private final C configuration; diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java index 0be291d4e1..a90eb4cc34 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java @@ -74,7 +74,7 @@ public final class RepositoryUtil { return getRepositoryId(handler.getConfig(), directory); } - public static String getRepositoryId(SimpleRepositoryConfig config, File directory) throws IOException { + public static String getRepositoryId(RepositoryConfig config, File directory) throws IOException { return getRepositoryId(config.getRepositoryDirectory(), directory); } diff --git a/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java index 25fa3eeeb3..82e85568d0 100644 --- a/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java @@ -21,9 +21,14 @@ public class RepositoryUtilTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Mock - private AbstractRepositoryHandler repositoryHandler; + private AbstractRepositoryHandler repositoryHandler; - private SimpleRepositoryConfig repositoryConfig = new SimpleRepositoryConfig(); + private RepositoryConfig repositoryConfig = new RepositoryConfig() { + @Override + public String getId() { + return "repository"; + } + }; @Before public void setUpMocks() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java index 80fe8907ac..2ec93ed83d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java @@ -39,6 +39,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; /** * @@ -46,7 +47,7 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "config") @XmlAccessorType(XmlAccessType.FIELD) -public class GitConfig extends SimpleRepositoryConfig { +public class GitConfig extends RepositoryConfig { @XmlElement(name = "gc-expression") private String gcExpression; @@ -55,5 +56,11 @@ public class GitConfig extends SimpleRepositoryConfig { { return gcExpression; } - + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "git"; + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java index b9bd650925..6438f49d4c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java @@ -33,20 +33,19 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + /** * * @author Sebastian Sdorra */ @XmlRootElement(name = "config") -public class HgConfig extends SimpleRepositoryConfig +public class HgConfig extends RepositoryConfig { /** @@ -223,4 +222,11 @@ public class HgConfig extends SimpleRepositoryConfig /** Field description */ private boolean showRevisionInId = false; + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "hg"; + } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java index c4b0af57a1..73b4f39219 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java @@ -33,12 +33,11 @@ package sonia.scm.repository; -//~--- JDK imports ------------------------------------------------------------ - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; /** * @@ -46,7 +45,7 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "config") @XmlAccessorType(XmlAccessType.FIELD) -public class SvnConfig extends SimpleRepositoryConfig +public class SvnConfig extends RepositoryConfig { /** @@ -108,4 +107,11 @@ public class SvnConfig extends SimpleRepositoryConfig /** Field description */ private Compatibility compatibility = Compatibility.NONE; + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "svn"; + } } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 870127b14e..bf68c27b19 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -37,6 +37,7 @@ import sonia.scm.Type; import sonia.scm.io.DefaultFileSystem; import sonia.scm.store.ConfigurationStoreFactory; +import javax.xml.bind.annotation.XmlRootElement; import java.io.File; import java.util.HashSet; import java.util.Set; @@ -48,7 +49,7 @@ import java.util.Set; * @author Sebastian Sdorra */ public class DummyRepositoryHandler - extends AbstractSimpleRepositoryHandler { + extends AbstractSimpleRepositoryHandler { public static final String TYPE_DISPLAYNAME = "Dummy"; @@ -79,12 +80,20 @@ public class DummyRepositoryHandler } @Override - protected SimpleRepositoryConfig createInitialConfig() { - return new SimpleRepositoryConfig(); + protected DummyRepositoryConfig createInitialConfig() { + return new DummyRepositoryConfig(); } @Override - protected Class getConfigClass() { - return SimpleRepositoryConfig.class; + protected Class getConfigClass() { + return DummyRepositoryConfig.class; + } + + @XmlRootElement(name = "config") + public static class DummyRepositoryConfig extends RepositoryConfig { + @Override + public String getId() { + return TYPE_NAME; + } } } 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 index 1e1698a45a..97cfd7fa26 100644 --- 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 @@ -3,14 +3,17 @@ 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.ConfigurationPermissions; 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.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -44,16 +47,12 @@ public class GlobalConfigResource { @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(); - } + // We do this permission check in Resource and not in ScmConfiguration, because it must be available for reading + // from within the code (plugins, etc.), but not for the whole anonymous world outside. + ConfigurationPermissions.read(configuration).check(); - return response; + return Response.ok(configToDtoMapper.map(configuration)).build(); } /** @@ -72,20 +71,17 @@ public class GlobalConfigResource { }) @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); + // This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes. + // But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later. + ConfigurationPermissions.write(configuration).check(); + + ScmConfiguration config = dtoToConfigMapper.map(configDto); + synchronized (ScmConfiguration.class) { configuration.load(config); - synchronized (ScmConfiguration.class) { - ScmConfigurationUtil.getInstance().store(configuration); - } - response = Response.created(uriInfo.getRequestUri()).build(); - } else { - response = Response.status(Response.Status.FORBIDDEN).build(); + ScmConfigurationUtil.getInstance().store(configuration); } - return response; + return Response.created(uriInfo.getRequestUri()).build(); } } 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 index 129ebc7fbe..f0090d5fb9 100644 --- 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 @@ -1,12 +1,11 @@ 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.ConfigurationPermissions; import sonia.scm.config.ScmConfiguration; -import sonia.scm.security.Role; import javax.inject.Inject; @@ -26,8 +25,7 @@ public abstract class ScmConfigurationToGlobalConfigDtoMapper { @AfterMapping void appendLinks(ScmConfiguration config, @MappingTarget GlobalConfigDto target) { Links.Builder linksBuilder = linkingTo().self(resourceLinks.globalConfig().self()); - // TODO: ConfigPermissions? - if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + if (ConfigurationPermissions.write(config).isPermitted()) { linksBuilder.single(link("update", resourceLinks.globalConfig().update())); } target.add(linksBuilder.build()); diff --git a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java index 633f6aa3b5..a5455c6d21 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java @@ -49,6 +49,7 @@ import sonia.scm.security.Role; * * @author Sebastian Sdorra */ +// TODO before releasing v2, delete this filter (we use Permission objects now) @WebElement( value = Filters.PATTERN_CONFIG, morePatterns = { 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 index e8e0f490e7..c033c0843e 100644 --- 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 @@ -10,6 +10,7 @@ import org.jboss.resteasy.mock.MockHttpResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.mockito.InjectMocks; import sonia.scm.config.ScmConfiguration; import sonia.scm.web.VndMediaType; @@ -23,19 +24,22 @@ import java.util.Arrays; import java.util.HashSet; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.MockitoAnnotations.initMocks; @SubjectAware( - username = "trillian", - password = "secret", - configuration = "classpath:sonia/scm/repository/shiro.ini" + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" ) public class GlobalConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); private final URI baseUri = URI.create("/"); @@ -58,6 +62,7 @@ public class GlobalConfigResourceTest { } @Test + @SubjectAware(username = "readOnly") public void shouldGetGlobalConfig() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); @@ -65,22 +70,22 @@ public class GlobalConfigResourceTest { 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")); + assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); } - @SubjectAware( - username = "dent" - ) @Test - public void shouldGetForbiddenGlobalConfig() throws URISyntaxException { + @SubjectAware(username = "writeOnly") + public void shouldGetGlobalConfigOnlyWhenAuthorized() throws URISyntaxException { MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); + thrown.expectMessage("Subject does not have permission [configuration:read:global]"); + dispatcher.invoke(request, response); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } @Test + @SubjectAware(username = "readWrite") public void shouldUpdateGlobalConfig() throws URISyntaxException, IOException { URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); byte[] configJson = Resources.toByteArray(url); @@ -102,11 +107,9 @@ public class GlobalConfigResourceTest { } - @SubjectAware( - username = "dent" - ) @Test - public void shouldUpdateForbiddenGlobalConfig() throws URISyntaxException, IOException { + @SubjectAware(username = "readOnly") + public void shouldUpdateGlobalConfigOnlyWhenAuthorized() 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) @@ -114,8 +117,10 @@ public class GlobalConfigResourceTest { .content(configJson); MockHttpResponse response = new MockHttpResponse(); + thrown.expectMessage("Subject does not have permission [configuration:write:global]"); + + dispatcher.invoke(request, response); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); } public static ScmConfiguration createConfiguration() { 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 index 1cf08769f0..38ea7b6af7 100644 --- 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 @@ -51,7 +51,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { public void shouldMapFields() { ScmConfiguration config = createConfiguration(); - when(subject.hasRole(Role.ADMIN)).thenReturn(true); + when(subject.isPermitted("configuration:write:global")).thenReturn(true); GlobalConfigDto dto = mapper.map(config); assertEquals("baseurl", dto.getBaseUrl()); @@ -63,7 +63,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { public void shouldMapFieldsWithoutUpdate() { ScmConfiguration config = createConfiguration(); - when(subject.hasRole(Role.ADMIN)).thenReturn(false); + when(subject.hasRole("configuration:write:global")).thenReturn(false); GlobalConfigDto dto = mapper.map(config); assertEquals("baseurl", dto.getBaseUrl()); diff --git a/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini new file mode 100644 index 0000000000..8647142b19 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini @@ -0,0 +1,9 @@ +[users] +readOnly = secret, reader +writeOnly = secret, writer +readWrite = secret, readerWriter + +[roles] +reader = configuration:read +writer = configuration:write +readerWriter = configuration:* From c7ce1705133a40a63e0b9fca7743b6aae672af1a Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 13:18:27 +0200 Subject: [PATCH 07/13] Config DTO Mapper: Adds tests for all properties --- ...ConfigDtoToScmConfigurationMapperTest.java | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) 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 index b08303ca4e..cfd5d33080 100644 --- 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 @@ -3,9 +3,13 @@ package sonia.scm.api.v2.resources; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; +import org.mockito.internal.util.collections.Sets; import sonia.scm.config.ScmConfiguration; +import java.util.Arrays; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.MockitoAnnotations.initMocks; public class GlobalConfigDtoToScmConfigurationMapperTest { @@ -13,21 +17,67 @@ public class GlobalConfigDtoToScmConfigurationMapperTest { @InjectMocks private GlobalConfigDtoToScmConfigurationMapperImpl mapper; - @Test - public void shouldMapFields() { - GlobalConfigDto dto = createDefaultDto(); - ScmConfiguration config = mapper.map(dto); - assertEquals("baseurl" , config.getBaseUrl()); - } + private String[] expectedUsers = { "trillian", "arthur" }; + private String[] expectedGroups = { "admin", "plebs" }; + private String[] expectedExcludes = { "ex", "clude" }; @Before public void init() { initMocks(this); } + @Test + public void shouldMapFields() { + GlobalConfigDto dto = createDefaultDto(); + ScmConfiguration config = mapper.map(dto); + + assertEquals("prPw" , config.getProxyPassword()); + assertEquals(42 , config.getProxyPort()); + assertEquals("srvr" , config.getProxyServer()); + assertEquals("user" , config.getProxyUser()); + assertTrue(config.isEnableProxy()); + assertEquals("realm" , config.getRealmDescription()); + assertTrue(config.isEnableRepositoryArchive()); + assertTrue(config.isDisableGroupingGrid()); + assertEquals("yyyy" , config.getDateFormat()); + assertTrue(config.isAnonymousAccessEnabled()); + assertTrue("adminGroups", config.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); + assertTrue("adminUsers", config.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); + assertEquals("baseurl" , config.getBaseUrl()); + assertTrue(config.isForceBaseUrl()); + assertEquals(41 , config.getLoginAttemptLimit()); + assertTrue("proxyExcludes", config.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes))); + assertTrue(config.isSkipFailedAuthenticators()); + assertEquals("https://plug.ins" , config.getPluginUrl()); + assertEquals(40 , config.getLoginAttemptLimitTimeout()); + assertTrue(config.isEnabledXsrfProtection()); + + + } + private GlobalConfigDto createDefaultDto() { GlobalConfigDto globalConfigDto = new GlobalConfigDto(); + globalConfigDto.setProxyPassword("prPw"); + globalConfigDto.setProxyPort(42); + globalConfigDto.setProxyServer("srvr"); + globalConfigDto.setProxyUser("user"); + globalConfigDto.setEnableProxy(true); + globalConfigDto.setRealmDescription("realm"); + globalConfigDto.setEnableRepositoryArchive(true); + globalConfigDto.setDisableGroupingGrid(true); + globalConfigDto.setDateFormat("yyyy"); + globalConfigDto.setAnonymousAccessEnabled(true); + globalConfigDto.setAdminGroups(Sets.newSet(expectedGroups)); + globalConfigDto.setAdminUsers(Sets.newSet(expectedUsers)); globalConfigDto.setBaseUrl("baseurl"); + globalConfigDto.setForceBaseUrl(true); + globalConfigDto.setLoginAttemptLimit(41); + globalConfigDto.setProxyExcludes(Sets.newSet(expectedExcludes)); + globalConfigDto.setSkipFailedAuthenticators(true); + globalConfigDto.setPluginUrl("https://plug.ins"); + globalConfigDto.setLoginAttemptLimitTimeout(40); + globalConfigDto.setEnabledXsrfProtection(true); + return globalConfigDto; } } From 9aaca229423b7f107409318998a5eb74361d62d9 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 13:43:43 +0200 Subject: [PATCH 08/13] DTO Config Mapper: Adds tests for all properties --- ...ConfigDtoToScmConfigurationMapperTest.java | 2 - .../resources/GlobalConfigResourceTest.java | 24 +------- ...figurationToGlobalConfigDtoMapperTest.java | 58 ++++++++++++++++++- 3 files changed, 57 insertions(+), 27 deletions(-) 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 index cfd5d33080..c992bd2ca4 100644 --- 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 @@ -51,8 +51,6 @@ public class GlobalConfigDtoToScmConfigurationMapperTest { assertEquals("https://plug.ins" , config.getPluginUrl()); assertEquals(40 , config.getLoginAttemptLimitTimeout()); assertTrue(config.isEnabledXsrfProtection()); - - } private GlobalConfigDto createDefaultDto() { 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 index c033c0843e..d59be3b19a 100644 --- 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 @@ -20,8 +20,6 @@ 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.assertFalse; @@ -123,28 +121,10 @@ public class GlobalConfigResourceTest { dispatcher.invoke(request, response); } - public static ScmConfiguration createConfiguration() { + private 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/ScmConfigurationToGlobalConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java index 38ea7b6af7..3ae2d41f8d 100644 --- 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 @@ -8,21 +8,27 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; +import org.mockito.internal.util.collections.Sets; import sonia.scm.config.ScmConfiguration; -import sonia.scm.security.Role; import java.net.URI; +import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; 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 { private URI baseUri = URI.create("http://example.com/base/"); + + private String[] expectedUsers = { "trillian", "arthur" }; + private String[] expectedGroups = { "admin", "plebs" }; + private String[] expectedExcludes = { "ex", "clude" }; + @SuppressWarnings("unused") // Is injected private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @@ -51,10 +57,31 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { public void shouldMapFields() { ScmConfiguration config = createConfiguration(); + when(subject.isPermitted("configuration:write:global")).thenReturn(true); GlobalConfigDto dto = mapper.map(config); - assertEquals("baseurl", dto.getBaseUrl()); + assertEquals("heartOfGold" , dto.getProxyPassword()); + assertEquals(1234 , dto.getProxyPort()); + assertEquals("proxyserver" , dto.getProxyServer()); + assertEquals("trillian" , dto.getProxyUser()); + assertTrue(dto.isEnableProxy()); + assertEquals("description" , dto.getRealmDescription()); + assertTrue(dto.isEnableRepositoryArchive()); + assertTrue(dto.isDisableGroupingGrid()); + assertEquals("dd" , dto.getDateFormat()); + assertTrue(dto.isAnonymousAccessEnabled()); + assertTrue("adminGroups", dto.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); + assertTrue("adminUsers", dto.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); + assertEquals("baseurl" , dto.getBaseUrl()); + assertTrue(dto.isForceBaseUrl()); + assertEquals(1 , dto.getLoginAttemptLimit()); + assertTrue("proxyExcludes", dto.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes))); + assertTrue(dto.isSkipFailedAuthenticators()); + assertEquals("pluginurl" , dto.getPluginUrl()); + assertEquals(2 , dto.getLoginAttemptLimitTimeout()); + assertTrue(dto.isEnabledXsrfProtection()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); } @@ -71,4 +98,29 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { assertFalse(dto.getLinks().hasLink("update")); } + private ScmConfiguration createConfiguration() { + ScmConfiguration config = new ScmConfiguration(); + config.setProxyPassword("heartOfGold"); + config.setProxyPort(1234); + config.setProxyServer("proxyserver"); + config.setProxyUser("trillian"); + config.setEnableProxy(true); + config.setRealmDescription("description"); + config.setEnableRepositoryArchive(true); + config.setDisableGroupingGrid(true); + config.setDateFormat("dd"); + config.setAnonymousAccessEnabled(true); + config.setAdminGroups(Sets.newSet(expectedGroups)); + config.setAdminUsers(Sets.newSet(expectedUsers)); + config.setBaseUrl("baseurl"); + config.setForceBaseUrl(true); + config.setLoginAttemptLimit(1); + config.setProxyExcludes(Sets.newSet(expectedExcludes)); + config.setSkipFailedAuthenticators(true); + config.setPluginUrl("pluginurl"); + config.setLoginAttemptLimitTimeout(2); + config.setEnabledXsrfProtection(true); + return config; + } + } From 912dea6d6ea41ea9d3600e06530aec9b3dbbede7 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 14:13:41 +0200 Subject: [PATCH 09/13] Simplifies GlobalConfigDto. Use camel case everywhere and no XML annotation. --- .../scm/api/v2/resources/GlobalConfigDto.java | 16 ---------------- .../v2/resources/GlobalConfigResourceTest.java | 1 - 2 files changed, 17 deletions(-) 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 index 928daf5c3a..4d41e7306d 100644 --- 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 @@ -5,10 +5,7 @@ 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 @@ -26,28 +23,15 @@ public class GlobalConfigDto extends HalRepresentation { 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 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 index d59be3b19a..da2c9775e8 100644 --- 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 @@ -102,7 +102,6 @@ public class GlobalConfigResourceTest { 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")); - } @Test From ca93735514a742bbc75107fb3944869382e4ac45 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 14:14:21 +0200 Subject: [PATCH 10/13] Return no content on PUT /config --- .../java/sonia/scm/api/v2/resources/GlobalConfigResource.java | 2 +- .../sonia/scm/api/v2/resources/GlobalConfigResourceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 97cfd7fa26..4f5e2344dc 100644 --- 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 @@ -82,6 +82,6 @@ public class GlobalConfigResource { ScmConfigurationUtil.getInstance().store(configuration); } - return Response.created(uriInfo.getRequestUri()).build(); + return Response.noContent().build(); } } 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 index da2c9775e8..01ab83d3ac 100644 --- 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 @@ -93,7 +93,7 @@ public class GlobalConfigResourceTest { MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); - assertEquals(HttpServletResponse.SC_CREATED, response.getStatus()); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); response = new MockHttpResponse(); From 1db45a789220d5851324d140a406188edcd813d9 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 14:42:47 +0200 Subject: [PATCH 11/13] Updates Shiro Static Permission to deterministic version. Contains PR that can generate interfaces. --- pom.xml | 6 +++--- scm-core/pom.xml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index a249734dca..0883bc958a 100644 --- a/pom.xml +++ b/pom.xml @@ -168,13 +168,13 @@ - com.github.schnatterer.shiro-static-permissions + com.github.sdorra.shiro-static-permissions ssp-lib ${ssp.version} - com.github.schnatterer.shiro-static-permissions + com.github.sdorra.shiro-static-permissions ssp-processor ${ssp.version} true @@ -564,7 +564,7 @@ 9.2.10.v20150310 - -SNAPSHOT + 967c8fd521 1.4.0 diff --git a/scm-core/pom.xml b/scm-core/pom.xml index a500dba32e..f19ad7453e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -131,12 +131,12 @@ - com.github.schnatterer.shiro-static-permissions + com.github.sdorra.shiro-static-permissions ssp-lib - com.github.schnatterer.shiro-static-permissions + com.github.sdorra.shiro-static-permissions ssp-processor true From 865929b3281c5a994a853ef65449b908798a066f Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 14:47:15 +0200 Subject: [PATCH 12/13] Renames globalConfig to config. From an SCMM point of view there is only one config, so no "global" necessary. The others configs are provided by plugins. --- .../main/java/sonia/scm/web/VndMediaType.java | 2 +- .../{GlobalConfigDto.java => ConfigDto.java} | 2 +- ...=> ConfigDtoToScmConfigurationMapper.java} | 4 +- ...onfigResource.java => ConfigResource.java} | 18 +++---- .../scm/api/v2/resources/MapperModule.java | 4 +- .../scm/api/v2/resources/ResourceLinks.java | 16 +++--- ...=> ScmConfigurationToConfigDtoMapper.java} | 10 ++-- ...onfigDtoToScmConfigurationMapperTest.java} | 52 +++++++++---------- ...ourceTest.java => ConfigResourceTest.java} | 44 ++++++++-------- .../api/v2/resources/ResourceLinksMock.java | 2 +- .../api/v2/resources/ResourceLinksTest.java | 12 ++--- ...cmConfigurationToConfigDtoMapperTest.java} | 10 ++-- ...st-update.json => config-test-update.json} | 0 13 files changed, 87 insertions(+), 89 deletions(-) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{GlobalConfigDto.java => ConfigDto.java} (95%) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{GlobalConfigDtoToScmConfigurationMapper.java => ConfigDtoToScmConfigurationMapper.java} (68%) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{GlobalConfigResource.java => ConfigResource.java} (80%) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{ScmConfigurationToGlobalConfigDtoMapper.java => ScmConfigurationToConfigDtoMapper.java} (71%) rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{GlobalConfigDtoToScmConfigurationMapperTest.java => ConfigDtoToScmConfigurationMapperTest.java} (59%) rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{GlobalConfigResourceTest.java => ConfigResourceTest.java} (68%) rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{ScmConfigurationToGlobalConfigDtoMapperTest.java => ScmConfigurationToConfigDtoMapperTest.java} (93%) rename scm-webapp/src/test/resources/sonia/scm/api/v2/{globalConfig-test-update.json => config-test-update.json} (100%) diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 9642089bfa..7c764685f1 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -19,7 +19,7 @@ public class VndMediaType { public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; - public static final String GLOBAL_CONFIG = PREFIX + "global_config" + SUFFIX; + public static final String CONFIG = PREFIX + "config" + SUFFIX; private VndMediaType() { } 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/ConfigDto.java similarity index 95% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java index 4d41e7306d..13d9ff5351 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -11,7 +11,7 @@ import java.util.Set; @NoArgsConstructor @Getter @Setter -public class GlobalConfigDto extends HalRepresentation { +public class ConfigDto extends HalRepresentation { private String proxyPassword; private int proxyPort; 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/ConfigDtoToScmConfigurationMapper.java similarity index 68% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapper.java index 3746f00e08..b26d22232d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapper.java @@ -6,7 +6,7 @@ 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 class ConfigDtoToScmConfigurationMapper { - public abstract ScmConfiguration map(GlobalConfigDto dto); + public abstract ScmConfiguration map(ConfigDto 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/ConfigResource.java similarity index 80% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index 4f5e2344dc..bf3da19416 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -18,16 +18,16 @@ 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 { +@Path(ConfigResource.CONFIG_PATH_V2) +public class ConfigResource { - static final String GLOBAL_CONFIG_PATH_V2 = "v2/config/global"; - private final GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper; - private final ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper; + static final String CONFIG_PATH_V2 = "v2/config"; + private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper; + private final ScmConfigurationToConfigDtoMapper configToDtoMapper; private final ScmConfiguration configuration; @Inject - public GlobalConfigResource(GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) { + public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) { this.dtoToConfigMapper = dtoToConfigMapper; this.configToDtoMapper = configToDtoMapper; this.configuration = configuration; @@ -38,7 +38,7 @@ public class GlobalConfigResource { */ @GET @Path("") - @Produces(VndMediaType.GLOBAL_CONFIG) + @Produces(VndMediaType.CONFIG) @TypeHint(UserDto.class) @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @@ -62,7 +62,7 @@ public class GlobalConfigResource { */ @PUT @Path("") - @Consumes(VndMediaType.GLOBAL_CONFIG) + @Consumes(VndMediaType.CONFIG) @StatusCodes({ @ResponseCode(code = 201, condition = "update success"), @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), @@ -70,7 +70,7 @@ public class GlobalConfigResource { @ResponseCode(code = 500, condition = "internal server error") }) @TypeHint(TypeHint.NO_CONTENT.class) - public Response update(GlobalConfigDto configDto, @Context UriInfo uriInfo) { + public Response update(ConfigDto configDto, @Context UriInfo uriInfo) { // This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes. // But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later. 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 84805f8aff..69430bf43b 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,8 +15,8 @@ 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(ScmConfigurationToConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToConfigDtoMapper.class).getClass()); + bind(ConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(ConfigDtoToScmConfigurationMapper.class).getClass()); bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass()); bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass()); 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 979782768b..d02747d2af 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 @@ -101,23 +101,23 @@ class ResourceLinks { } } - GlobalConfigLinks globalConfig() { - return new GlobalConfigLinks(uriInfoStore.get()); + ConfigLinks config() { + return new ConfigLinks(uriInfoStore.get()); } - static class GlobalConfigLinks { - private final LinkBuilder globalConfigLinkBuilder; + static class ConfigLinks { + private final LinkBuilder configLinkBuilder; - GlobalConfigLinks(UriInfo uriInfo) { - globalConfigLinkBuilder = new LinkBuilder(uriInfo, GlobalConfigResource.class); + ConfigLinks(UriInfo uriInfo) { + configLinkBuilder = new LinkBuilder(uriInfo, ConfigResource.class); } String self() { - return globalConfigLinkBuilder.method("get").parameters().href(); + return configLinkBuilder.method("get").parameters().href(); } String update() { - return globalConfigLinkBuilder.method("update").parameters().href(); + return configLinkBuilder.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/ScmConfigurationToConfigDtoMapper.java similarity index 71% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java index f0090d5fb9..59e56feb11 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java @@ -15,18 +15,18 @@ 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 { +public abstract class ScmConfigurationToConfigDtoMapper { @Inject private ResourceLinks resourceLinks; - public abstract GlobalConfigDto map(ScmConfiguration config); + public abstract ConfigDto map(ScmConfiguration config); @AfterMapping - void appendLinks(ScmConfiguration config, @MappingTarget GlobalConfigDto target) { - Links.Builder linksBuilder = linkingTo().self(resourceLinks.globalConfig().self()); + void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self()); if (ConfigurationPermissions.write(config).isPermitted()) { - linksBuilder.single(link("update", resourceLinks.globalConfig().update())); + linksBuilder.single(link("update", resourceLinks.config().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/ConfigDtoToScmConfigurationMapperTest.java similarity index 59% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java index c992bd2ca4..ad1e71368e 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java @@ -12,10 +12,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.MockitoAnnotations.initMocks; -public class GlobalConfigDtoToScmConfigurationMapperTest { +public class ConfigDtoToScmConfigurationMapperTest { @InjectMocks - private GlobalConfigDtoToScmConfigurationMapperImpl mapper; + private ConfigDtoToScmConfigurationMapperImpl mapper; private String[] expectedUsers = { "trillian", "arthur" }; private String[] expectedGroups = { "admin", "plebs" }; @@ -28,7 +28,7 @@ public class GlobalConfigDtoToScmConfigurationMapperTest { @Test public void shouldMapFields() { - GlobalConfigDto dto = createDefaultDto(); + ConfigDto dto = createDefaultDto(); ScmConfiguration config = mapper.map(dto); assertEquals("prPw" , config.getProxyPassword()); @@ -53,29 +53,29 @@ public class GlobalConfigDtoToScmConfigurationMapperTest { assertTrue(config.isEnabledXsrfProtection()); } - private GlobalConfigDto createDefaultDto() { - GlobalConfigDto globalConfigDto = new GlobalConfigDto(); - globalConfigDto.setProxyPassword("prPw"); - globalConfigDto.setProxyPort(42); - globalConfigDto.setProxyServer("srvr"); - globalConfigDto.setProxyUser("user"); - globalConfigDto.setEnableProxy(true); - globalConfigDto.setRealmDescription("realm"); - globalConfigDto.setEnableRepositoryArchive(true); - globalConfigDto.setDisableGroupingGrid(true); - globalConfigDto.setDateFormat("yyyy"); - globalConfigDto.setAnonymousAccessEnabled(true); - globalConfigDto.setAdminGroups(Sets.newSet(expectedGroups)); - globalConfigDto.setAdminUsers(Sets.newSet(expectedUsers)); - globalConfigDto.setBaseUrl("baseurl"); - globalConfigDto.setForceBaseUrl(true); - globalConfigDto.setLoginAttemptLimit(41); - globalConfigDto.setProxyExcludes(Sets.newSet(expectedExcludes)); - globalConfigDto.setSkipFailedAuthenticators(true); - globalConfigDto.setPluginUrl("https://plug.ins"); - globalConfigDto.setLoginAttemptLimitTimeout(40); - globalConfigDto.setEnabledXsrfProtection(true); + private ConfigDto createDefaultDto() { + ConfigDto configDto = new ConfigDto(); + configDto.setProxyPassword("prPw"); + configDto.setProxyPort(42); + configDto.setProxyServer("srvr"); + configDto.setProxyUser("user"); + configDto.setEnableProxy(true); + configDto.setRealmDescription("realm"); + configDto.setEnableRepositoryArchive(true); + configDto.setDisableGroupingGrid(true); + configDto.setDateFormat("yyyy"); + configDto.setAnonymousAccessEnabled(true); + configDto.setAdminGroups(Sets.newSet(expectedGroups)); + configDto.setAdminUsers(Sets.newSet(expectedUsers)); + configDto.setBaseUrl("baseurl"); + configDto.setForceBaseUrl(true); + configDto.setLoginAttemptLimit(41); + configDto.setProxyExcludes(Sets.newSet(expectedExcludes)); + configDto.setSkipFailedAuthenticators(true); + configDto.setPluginUrl("https://plug.ins"); + configDto.setLoginAttemptLimitTimeout(40); + configDto.setEnabledXsrfProtection(true); - return globalConfigDto; + return configDto; } } 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/ConfigResourceTest.java similarity index 68% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 01ab83d3ac..5365e9fbde 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -30,7 +30,7 @@ import static org.mockito.MockitoAnnotations.initMocks; configuration = "classpath:sonia/scm/configuration/shiro.ini", password = "secret" ) -public class GlobalConfigResourceTest { +public class ConfigResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); @@ -45,36 +45,35 @@ public class GlobalConfigResourceTest { private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @InjectMocks - private GlobalConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper; + private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper; @InjectMocks - private ScmConfigurationToGlobalConfigDtoMapperImpl configToDtoMapper; + private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper; @Before public void prepareEnvironment() { initMocks(this); - GlobalConfigResource globalConfigResource = new GlobalConfigResource(dtoToConfigMapper, - configToDtoMapper, createConfiguration()); + ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration()); - dispatcher.getRegistry().addSingletonResource(globalConfigResource); + dispatcher.getRegistry().addSingletonResource(configResource); } @Test @SubjectAware(username = "readOnly") public void shouldGetGlobalConfig() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + 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/global")); - assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); } @Test @SubjectAware(username = "writeOnly") - public void shouldGetGlobalConfigOnlyWhenAuthorized() throws URISyntaxException { - MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); MockHttpResponse response = new MockHttpResponse(); thrown.expectMessage("Subject does not have permission [configuration:read:global]"); @@ -84,39 +83,38 @@ public class GlobalConfigResourceTest { @Test @SubjectAware(username = "readWrite") - public void shouldUpdateGlobalConfig() throws URISyntaxException, IOException { - URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + public void shouldUpdateConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); byte[] configJson = Resources.toByteArray(url); - MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) - .contentType(VndMediaType.GLOBAL_CONFIG) + MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) + .contentType(VndMediaType.CONFIG) .content(configJson); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); - request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + 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/global")); - assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); } @Test @SubjectAware(username = "readOnly") - public void shouldUpdateGlobalConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { - URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); byte[] configJson = Resources.toByteArray(url); - MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) - .contentType(VndMediaType.GLOBAL_CONFIG) + MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) + .contentType(VndMediaType.CONFIG) .content(configJson); MockHttpResponse response = new MockHttpResponse(); thrown.expectMessage("Subject does not have permission [configuration:write:global]"); - dispatcher.invoke(request, response); } 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 1e2f5b102b..b5fe5c4da4 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 @@ -24,7 +24,7 @@ public class ResourceLinksMock { when(resourceLinks.changesetCollection()).thenReturn(new ResourceLinks.ChangesetCollectionLinks(uriInfo)); when(resourceLinks.sourceCollection()).thenReturn(new ResourceLinks.SourceCollectionLinks(uriInfo)); when(resourceLinks.permissionCollection()).thenReturn(new ResourceLinks.PermissionCollectionLinks(uriInfo)); - when(resourceLinks.globalConfig()).thenReturn(new ResourceLinks.GlobalConfigLinks(uriInfo)); + when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo)); return resourceLinks; } } 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 9b2b9ffcc8..b850a4b4c6 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 @@ -133,15 +133,15 @@ public class ResourceLinksTest { } @Test - public void shouldCreateCorrectGlobalConfigSelfUrl() { - String url = resourceLinks.globalConfig().self(); - assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + public void shouldCreateCorrectConfigSelfUrl() { + String url = resourceLinks.config().self(); + assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url); } @Test - public void shouldCreateCorrectGlobalConfigUpdateUrl() { - String url = resourceLinks.globalConfig().update(); - assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + public void shouldCreateCorrectConfigUpdateUrl() { + String url = resourceLinks.config().update(); + assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url); } @Before 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/ScmConfigurationToConfigDtoMapperTest.java similarity index 93% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java index 3ae2d41f8d..ff20516e5a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -public class ScmConfigurationToGlobalConfigDtoMapperTest { +public class ScmConfigurationToConfigDtoMapperTest { private URI baseUri = URI.create("http://example.com/base/"); @@ -33,7 +33,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); @InjectMocks - private ScmConfigurationToGlobalConfigDtoMapperImpl mapper; + private ScmConfigurationToConfigDtoMapperImpl mapper; private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); @@ -43,7 +43,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { @Before public void init() { initMocks(this); - expectedBaseUri = baseUri.resolve(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + expectedBaseUri = baseUri.resolve(ConfigResource.CONFIG_PATH_V2); subjectThreadState.bind(); ThreadContext.bind(subject); } @@ -59,7 +59,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { when(subject.isPermitted("configuration:write:global")).thenReturn(true); - GlobalConfigDto dto = mapper.map(config); + ConfigDto dto = mapper.map(config); assertEquals("heartOfGold" , dto.getProxyPassword()); assertEquals(1234 , dto.getProxyPort()); @@ -91,7 +91,7 @@ public class ScmConfigurationToGlobalConfigDtoMapperTest { ScmConfiguration config = createConfiguration(); when(subject.hasRole("configuration:write:global")).thenReturn(false); - GlobalConfigDto dto = mapper.map(config); + ConfigDto dto = mapper.map(config); assertEquals("baseurl", dto.getBaseUrl()); assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); 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/config-test-update.json similarity index 100% rename from scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json rename to scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update.json From 4dc905d49a0df236559ac9f6b68c2ab6a14e7969 Mon Sep 17 00:00:00 2001 From: Johannes Schnatterer Date: Tue, 31 Jul 2018 14:25:49 +0000 Subject: [PATCH 13/13] Close branch feature/global_config_v2_endpoint