From ada3d6679fc2586a1a37b68f552ec4ffcf0a03e3 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Tue, 17 Jul 2018 13:39:55 +0200 Subject: [PATCH] Implement git config v2 endpoint --- pom.xml | 1 + .../scm/api/v2/resources/LinkBuilder.java | 0 .../scm/api/v2/resources/UriInfoStore.java | 0 .../main/java/sonia/scm/web/VndMediaType.java | 4 +- scm-plugins/pom.xml | 77 +++++++++++++++ scm-plugins/scm-git-plugin/pom.xml | 14 +++ .../scm/api/v2/resources/GitConfigDto.java | 23 +++++ .../GitConfigDtoToGitConfigMapper.java | 11 +++ .../api/v2/resources/GitConfigResource.java | 93 +++++++++++++++++++ .../GitConfigToGitConfigDtoMapper.java | 45 +++++++++ .../scm/api/v2/resources/MapperModule.java | 16 ++++ .../resources/SimpleRepositoryConfigDto.java | 17 ++++ .../java/sonia/scm/repository/GitConfig.java | 5 +- .../java/sonia/scm/web/GitVndMediaType.java | 5 + .../GitConfigDtoToGitConfigMapperTest.java | 35 +++++++ .../GitConfigToGitConfigDtoMapperTest.java | 77 +++++++++++++++ scm-webapp/pom.xml | 1 - 17 files changed, 420 insertions(+), 4 deletions(-) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java (100%) rename {scm-webapp => scm-core}/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java (100%) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java diff --git a/pom.xml b/pom.xml index 2416371d48..2f872b60b2 100644 --- a/pom.xml +++ b/pom.xml @@ -492,6 +492,7 @@ 1.19.4 2.8.6 4.0 + 3.1.3.Final 1.2.0 diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/LinkBuilder.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java similarity index 100% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java rename to scm-core/src/main/java/sonia/scm/api/v2/resources/UriInfoStore.java 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 fb80450bc8..c7b8d369bd 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -9,8 +9,8 @@ public class VndMediaType { private static final String VERSION = "2"; private static final String TYPE = "application"; private static final String SUBTYPE_PREFIX = "vnd.scmm-"; - private static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX; - private static final String SUFFIX = "+json;v=" + VERSION; + public static final String PREFIX = TYPE + "/" + SUBTYPE_PREFIX; + public static final String SUFFIX = "+json;v=" + VERSION; public static final String USER = PREFIX + "user" + SUFFIX; public static final String GROUP = PREFIX + "group" + SUFFIX; diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 5b61882815..168c5910e4 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -56,6 +56,79 @@ test + + + + com.webcohesion.enunciate + enunciate-core-annotations + ${enunciate.version} + + + + org.mapstruct + mapstruct-jdk8 + ${org.mapstruct.version} + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + provided + + + + de.otto.edison + edison-hal + 2.0.1 + compile + + + + org.projectlombok + lombok + 1.16.18 + provided + + + + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jaxb-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-jackson2-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-guice + ${resteasy.version} + + + + org.jboss.resteasy + resteasy-servlet-initializer + ${resteasy.version} + + @@ -112,6 +185,10 @@ + + 2.9.1 + + release diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 7013c237cb..cdf1775bba 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -59,6 +59,20 @@ test + + com.sun.jersey + jersey-client + ${jersey-client.version} + test + + + + com.sun.jersey.contribs + jersey-apache-client + ${jersey-client.version} + test + + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java new file mode 100644 index 0000000000..2bd2cf755c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDto.java @@ -0,0 +1,23 @@ +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 javax.xml.bind.annotation.XmlElement; + +@NoArgsConstructor +@Getter +@Setter +public class GitConfigDto extends SimpleRepositoryConfigDto { + + @XmlElement(name = "gc-expression") + private String gcExpression; + + @Override + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java new file mode 100644 index 0000000000..74ba684a24 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapper.java @@ -0,0 +1,11 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.repository.GitConfig; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class GitConfigDtoToGitConfigMapper { + public abstract GitConfig map(GitConfigDto dto); +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java new file mode 100644 index 0000000000..a0079464cc --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigResource.java @@ -0,0 +1,93 @@ +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.repository.GitConfig; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.security.Role; +import sonia.scm.web.GitVndMediaType; + +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(GitConfigResource.GIT_CONFIG_PATH_V2) +public class GitConfigResource { + + static final String GIT_CONFIG_PATH_V2 = "v2/config/repository/git"; + private final GitConfigDtoToGitConfigMapper dtoToConfigMapper; + private final GitConfigToGitConfigDtoMapper configToDtoMapper; + private final GitRepositoryHandler repositoryHandler; + + @Inject + public GitConfigResource(GitConfigDtoToGitConfigMapper dtoToConfigMapper, GitConfigToGitConfigDtoMapper configToDtoMapper, GitRepositoryHandler repositoryHandler) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.repositoryHandler = repositoryHandler; + } + + /** + * Returns the git config. + */ + @GET + @Path("") + @Produces(GitVndMediaType.GIT_CONFIG) + @TypeHint(GitConfigDto.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 git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + Response response; + + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + GitConfig config = repositoryHandler.getConfig(); + + if (config == null) { + config = new GitConfig(); + repositoryHandler.setConfig(config); + } + + response = Response.ok(configToDtoMapper.map(config)).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } + + /** + * Modifies the git config. + * + * @param configDto new git configuration as DTO + */ + @PUT + @Path("") + @Consumes(GitVndMediaType.GIT_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 git config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(@Context UriInfo uriInfo, GitConfigDto configDto) { + Response response; + + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + repositoryHandler.setConfig(dtoToConfigMapper.map(configDto)); + repositoryHandler.storeConfig(); + response = Response.created(uriInfo.getRequestUri()).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java new file mode 100644 index 0000000000..ceab24d6f7 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapper.java @@ -0,0 +1,45 @@ +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.repository.GitConfig; +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 GitConfigToGitConfigDtoMapper { + + @Inject + private UriInfoStore uriInfoStore; + + public abstract GitConfigDto map(GitConfig config); + + @AfterMapping + void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(self()); + // TODO: ConfigPermissions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + linksBuilder.single(link("update", update())); + } + target.add(linksBuilder.build()); + } + + private String self() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class); + return linkBuilder.method("get").parameters().href(); + } + + private String update() { + LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), GitConfigResource.class); + return linkBuilder.method("update").parameters().href(); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java new file mode 100644 index 0000000000..267ecd201e --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -0,0 +1,16 @@ +package sonia.scm.api.v2.resources; + +import com.google.inject.AbstractModule; +import com.google.inject.servlet.ServletScopes; +import org.mapstruct.factory.Mappers; + +public class MapperModule extends AbstractModule { + @Override + protected void configure() { + + bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass()); + bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass()); + + bind(UriInfoStore.class).in(ServletScopes.REQUEST); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java new file mode 100644 index 0000000000..45cb0bd199 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/SimpleRepositoryConfigDto.java @@ -0,0 +1,17 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import lombok.Getter; +import lombok.Setter; + +import javax.xml.bind.annotation.XmlElement; +import java.io.File; + +@Getter +@Setter +public abstract class SimpleRepositoryConfigDto extends HalRepresentation { + + private boolean disabled = false; + @XmlElement(name = "repository-directory") + private File repositoryDirectory; +} 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..77352c6ca0 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 @@ -55,5 +55,8 @@ public class GitConfig extends SimpleRepositoryConfig { { return gcExpression; } - + + public void setGcExpression(String gcExpression) { + this.gcExpression = gcExpression; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java new file mode 100644 index 0000000000..f099faf098 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitVndMediaType.java @@ -0,0 +1,5 @@ +package sonia.scm.web; + +public class GitVndMediaType { + public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX; +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java new file mode 100644 index 0000000000..3b09dff51f --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigDtoToGitConfigMapperTest.java @@ -0,0 +1,35 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class GitConfigDtoToGitConfigMapperTest { + + @InjectMocks + private GitConfigDtoToGitConfigMapperImpl mapper; + + @Test + public void shouldMapFields() { + GitConfigDto dto = createDefaultDto(); + GitConfig config = mapper.map(dto); + assertEquals("express", config.getGcExpression()); + assertEquals("repository/directory", config.getRepositoryDirectory().getPath()); + assertEquals(false, config.isDisabled()); + } + + private GitConfigDto createDefaultDto() { + GitConfigDto gitConfigDto = new GitConfigDto(); + gitConfigDto.setGcExpression("express"); + gitConfigDto.setDisabled(false); + gitConfigDto.setRepositoryDirectory(new File("repository/directory")); + return gitConfigDto; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java new file mode 100644 index 0000000000..59f7cc4799 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigToGitConfigDtoMapperTest.java @@ -0,0 +1,77 @@ +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.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; +import sonia.scm.security.Role; + +import java.io.File; +import java.net.URI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class GitConfigToGitConfigDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private UriInfoStore uriInfoStore; + + @InjectMocks + private GitConfigToGitConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() { + when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri); + expectedBaseUri = baseUri.resolve(GitConfigResource.GIT_CONFIG_PATH_V2); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldMapFields() { + GitConfig config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(true); + GitConfigDto dto = mapper.map(config); + + assertEquals("express", dto.getGcExpression()); + assertFalse(dto.isDisabled()); + assertEquals("repository/directory", dto.getRepositoryDirectory().getPath()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + private GitConfig createConfiguration() { + GitConfig config = new GitConfig(); + config.setDisabled(false); + config.setRepositoryDirectory(new File("repository/directory")); + config.setGcExpression("express"); + return config; + } + +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 98c70ff82a..8d91e21025 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -561,7 +561,6 @@ 2.9.1 1.0 0.8.17 - 3.1.3.Final Tomcat e1 javascript:S3827