mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
merge with branch feature/repository_config_v2_endpoint
This commit is contained in:
160
pom.xml
160
pom.xml
@@ -121,28 +121,31 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mokito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -154,6 +157,8 @@
|
||||
<version>9aadeeb</version>
|
||||
<!-- Don't ship this dependency with the app -->
|
||||
<optional>true</optional>
|
||||
<!-- Don't inherit this dependency! -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
@@ -182,6 +187,134 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-core-annotations</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.otto.edison</groupId>
|
||||
<artifactId>edison-hal</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.18</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- rest api -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxb-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-multipart-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-guice</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-servlet-initializer</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>${jaxrs.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mokito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -194,6 +327,22 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-maven-plugin</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
@@ -294,7 +443,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
@@ -554,7 +702,9 @@
|
||||
<servlet.version>3.0.1</servlet.version>
|
||||
|
||||
<jaxrs.version>2.0.1</jaxrs.version>
|
||||
<resteasy.version>3.1.3.Final</resteasy.version>
|
||||
<jersey-client.version>1.19.4</jersey-client.version>
|
||||
<enunciate.version>2.9.1</enunciate.version>
|
||||
<jackson.version>2.8.6</jackson.version>
|
||||
<guice.version>4.0</guice.version>
|
||||
|
||||
|
||||
@@ -85,17 +85,41 @@
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>${jaxrs.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- json -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hypermedia -->
|
||||
<dependency>
|
||||
<groupId>de.otto.edison</groupId>
|
||||
<artifactId>edison-hal</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- (DTO) mapping -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- rest documentation -->
|
||||
<dependency>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-core-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- event bus -->
|
||||
|
||||
@@ -5,12 +5,12 @@ import org.mapstruct.Mapping;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
abstract class BaseMapper<T, D extends HalRepresentation> {
|
||||
public abstract class BaseMapper<T, D extends HalRepresentation> {
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract D map(T object);
|
||||
public abstract D map(T modelObject);
|
||||
|
||||
Instant mapTime(Long epochMilli) {
|
||||
protected Instant mapTime(Long epochMilli) {
|
||||
return epochMilli == null? null: Instant.ofEpochMilli(epochMilli);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
public abstract class CollectionToDtoMapper<E, D extends HalRepresentation> {
|
||||
|
||||
private final String collectionName;
|
||||
private final BaseMapper<E, D> mapper;
|
||||
|
||||
protected CollectionToDtoMapper(String collectionName, BaseMapper<E, D> mapper) {
|
||||
this.collectionName = collectionName;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<E> collection) {
|
||||
List<D> dtos = collection.stream().map(mapper::map).collect(Collectors.toList());
|
||||
return new HalRepresentation(
|
||||
linkingTo().self(createSelfLink()).build(),
|
||||
embeddedBuilder().with(collectionName, dtos).build()
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract String createSelfLink();
|
||||
|
||||
}
|
||||
@@ -23,12 +23,13 @@ import java.util.Arrays;
|
||||
* .create();
|
||||
* </pre>
|
||||
*/
|
||||
class LinkBuilder {
|
||||
@SuppressWarnings("WeakerAccess") // Non-public will result in IllegalAccessError for plugins
|
||||
public class LinkBuilder {
|
||||
private final UriInfo uriInfo;
|
||||
private final Class[] classes;
|
||||
private final ImmutableList<Call> calls;
|
||||
|
||||
LinkBuilder(UriInfo uriInfo, Class... classes) {
|
||||
public LinkBuilder(UriInfo uriInfo, Class... classes) {
|
||||
this(uriInfo, classes, ImmutableList.of());
|
||||
}
|
||||
|
||||
@@ -38,25 +39,24 @@ class LinkBuilder {
|
||||
this.calls = calls;
|
||||
}
|
||||
|
||||
Parameters method(String method) {
|
||||
public Parameters method(String method) {
|
||||
if (calls.size() >= classes.length) {
|
||||
throw new IllegalStateException("no more classes for methods");
|
||||
}
|
||||
return new Parameters(method);
|
||||
}
|
||||
|
||||
URI create() {
|
||||
public URI create() {
|
||||
if (calls.size() < classes.length) {
|
||||
throw new IllegalStateException("not enough methods for all classes");
|
||||
}
|
||||
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
URI relativeUri = createRelativeUri();
|
||||
URI absoluteUri = baseUri.resolve(relativeUri);
|
||||
return absoluteUri;
|
||||
return baseUri.resolve(relativeUri);
|
||||
}
|
||||
|
||||
String href() {
|
||||
public String href() {
|
||||
return create().toString();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class LinkBuilder {
|
||||
return UriBuilder.fromResource(classes[0]);
|
||||
}
|
||||
|
||||
class Parameters {
|
||||
public class Parameters {
|
||||
|
||||
private final String method;
|
||||
|
||||
@@ -95,7 +95,7 @@ class LinkBuilder {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
LinkBuilder parameters(String... parameters) {
|
||||
public LinkBuilder parameters(String... parameters) {
|
||||
return LinkBuilder.this.add(method, parameters);
|
||||
}
|
||||
}
|
||||
@@ -90,9 +90,10 @@ public class ScmConfiguration implements Configuration {
|
||||
/**
|
||||
* the logger for ScmConfiguration
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ScmConfiguration.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScmConfiguration.class);
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // This might be needed for permission checking
|
||||
public static final String PERMISSION = "global";
|
||||
|
||||
@XmlElement(name = "admin-groups")
|
||||
@XmlJavaTypeAdapter(XmlSetStringAdapter.class)
|
||||
@@ -509,6 +510,6 @@ public class ScmConfiguration implements Configuration {
|
||||
@XmlTransient
|
||||
public String getId() {
|
||||
// Don't change this without migrating SCM permission configuration!
|
||||
return "global";
|
||||
return PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,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;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- annotation processor -->
|
||||
<!-- annotation processors -->
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.scm</groupId>
|
||||
@@ -47,6 +47,20 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Annotation processor for DTO mappers-->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Annotation processor for getter, setters, constructors, etc. -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- test scope -->
|
||||
|
||||
<dependency>
|
||||
@@ -56,6 +70,19 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -140,6 +167,96 @@
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>doc</id>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-enunciate-configuration</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/doc</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/enunciate.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>docs</goal>
|
||||
</goals>
|
||||
<phase>compile</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<configFile>${project.build.directory}/enunciate.xml</configFile>
|
||||
<docsDir>${project.build.directory}</docsDir>
|
||||
<docsSubdir>restdocs</docsSubdir>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-top</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-swagger</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-lombok</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/doc/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>sonia.scm.plugins</groupId>
|
||||
<artifactId>scm-git-plugin</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<name>scm-git-plugin</name>
|
||||
<packaging>smp</packaging>
|
||||
<url>https://bitbucket.org/sdorra/scm-manager</url>
|
||||
@@ -19,13 +17,6 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>${servlet.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
@@ -50,15 +41,6 @@
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test scope -->
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.scm</groupId>
|
||||
<artifactId>scm-test</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- create test jar -->
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
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 java.io.File;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class GitConfigDto extends HalRepresentation {
|
||||
|
||||
private File repositoryDirectory;
|
||||
private boolean disabled = false;
|
||||
|
||||
private String gcExpression;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.web.GitVndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
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.Response;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration of the git plugin.
|
||||
*/
|
||||
@Path(GitConfigResource.GIT_CONFIG_PATH_V2)
|
||||
public class GitConfigResource {
|
||||
|
||||
static final String GIT_CONFIG_PATH_V2 = "v2/config/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 does not have the \"configuration:read:git\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response get() {
|
||||
|
||||
GitConfig config = repositoryHandler.getConfig();
|
||||
|
||||
if (config == null) {
|
||||
config = new GitConfig();
|
||||
repositoryHandler.setConfig(config);
|
||||
}
|
||||
|
||||
ConfigurationPermissions.read(config).check();
|
||||
|
||||
return Response.ok(configToDtoMapper.map(config)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the git config.
|
||||
*
|
||||
* @param configDto new configuration object
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@Consumes(GitVndMediaType.GIT_CONFIG)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:git\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(GitConfigDto configDto) {
|
||||
|
||||
GitConfig config = dtoToConfigMapper.map(configDto);
|
||||
|
||||
ConfigurationPermissions.write(config).check();
|
||||
|
||||
repositoryHandler.setConfig(config);
|
||||
repositoryHandler.storeConfig();
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.GitConfig;
|
||||
|
||||
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 extends BaseMapper<GitConfig, GitConfigDto> {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(GitConfig config, @MappingTarget GitConfigDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(self());
|
||||
if (ConfigurationPermissions.write(config).isPermitted()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,9 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class GitConfig extends RepositoryConfig {
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // This might be needed for permission checking
|
||||
public static final String PERMISSION = "git";
|
||||
|
||||
@XmlElement(name = "gc-expression")
|
||||
private String gcExpression;
|
||||
|
||||
@@ -57,10 +60,14 @@ public class GitConfig extends RepositoryConfig {
|
||||
return gcExpression;
|
||||
}
|
||||
|
||||
public void setGcExpression(String gcExpression) {
|
||||
this.gcExpression = 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";
|
||||
return PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ package sonia.scm.web;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import sonia.scm.api.v2.resources.GitConfigDtoToGitConfigMapper;
|
||||
import sonia.scm.api.v2.resources.GitConfigToGitConfigDtoMapper;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
/**
|
||||
@@ -73,6 +73,9 @@ public class GitServletModule extends ServletModule
|
||||
|
||||
bind(LfsBlobStoreFactory.class);
|
||||
|
||||
bind(GitConfigDtoToGitConfigMapper.class).to(Mappers.getMapper(GitConfigDtoToGitConfigMapper.class).getClass());
|
||||
bind(GitConfigToGitConfigDtoMapper.class).to(Mappers.getMapper(GitConfigToGitConfigDtoMapper.class).getClass());
|
||||
|
||||
// serlvelts and filters
|
||||
serve(PATTERN_GIT).with(ScmGitServlet.class);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
public class GitVndMediaType {
|
||||
public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
|
||||
|
||||
private GitVndMediaType() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
@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());
|
||||
assertFalse(config.isDisabled());
|
||||
}
|
||||
|
||||
private GitConfigDto createDefaultDto() {
|
||||
GitConfigDto gitConfigDto = new GitConfigDto();
|
||||
gitConfigDto.setGcExpression("express");
|
||||
gitConfigDto.setDisabled(false);
|
||||
gitConfigDto.setRepositoryDirectory(new File("repository/directory"));
|
||||
return gitConfigDto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
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.repository.GitRepositoryHandler;
|
||||
import sonia.scm.web.GitVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GitConfigResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
|
||||
@InjectMocks
|
||||
private GitConfigDtoToGitConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private GitConfigToGitConfigDtoMapperImpl configToDtoMapper;
|
||||
|
||||
@Mock
|
||||
private GitRepositoryHandler repositoryHandler;
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
GitConfig gitConfig = createConfiguration();
|
||||
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
|
||||
GitConfigResource gitConfigResource = new GitConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler);
|
||||
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfig() throws URISyntaxException, IOException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String responseString = response.getContentAsString();
|
||||
ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class);
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory"));
|
||||
assertTrue(responseString.contains("\"gcExpression\":\"valid Git GC Cron Expression\""));
|
||||
assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/git"));
|
||||
assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/git"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetGitConfigEvenWhenItsEmpty() throws URISyntaxException, IOException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
String responseString = response.getContentAsString();
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetGitConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/git"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:git]");
|
||||
|
||||
get();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldUpdateConfig() throws URISyntaxException {
|
||||
MockHttpResponse response = put();
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:git]");
|
||||
|
||||
put();
|
||||
}
|
||||
|
||||
private MockHttpResponse get() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + GitConfigResource.GIT_CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private MockHttpResponse put() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2)
|
||||
.contentType(GitVndMediaType.GIT_CONFIG)
|
||||
.content("{\"disabled\":true}".getBytes());
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private GitConfig createConfiguration() {
|
||||
GitConfig config = new GitConfig();
|
||||
config.setGcExpression("valid Git GC Cron Expression");
|
||||
config.setDisabled(false);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
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 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.isPermitted("configuration:write:git")).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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFieldsWithoutUpdate() {
|
||||
GitConfig config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:git")).thenReturn(false);
|
||||
GitConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertFalse(dto.getLinks().hasLink("update"));
|
||||
}
|
||||
|
||||
private GitConfig createConfiguration() {
|
||||
GitConfig config = new GitConfig();
|
||||
config.setDisabled(false);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
config.setGcExpression("express");
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[users]
|
||||
readOnly = secret, reader
|
||||
writeOnly = secret, writer
|
||||
readWrite = secret, readerWriter
|
||||
|
||||
[roles]
|
||||
reader = configuration:read:git
|
||||
writer = configuration:write:git
|
||||
readerWriter = configuration:*:git
|
||||
@@ -9,9 +9,7 @@
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>sonia.scm.plugins</groupId>
|
||||
<artifactId>scm-hg-plugin</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<name>scm-hg-plugin</name>
|
||||
<packaging>smp</packaging>
|
||||
<url>https://bitbucket.org/sdorra/scm-manager</url>
|
||||
@@ -31,15 +29,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- test scope -->
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.scm</groupId>
|
||||
<artifactId>scm-test</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- create test jar -->
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class HgConfigAutoConfigurationResource {
|
||||
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
|
||||
|
||||
@Inject
|
||||
public HgConfigAutoConfigurationResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper,
|
||||
HgRepositoryHandler repositoryHandler) {
|
||||
this.dtoToConfigMapper = dtoToConfigMapper;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default hg config and installs the hg binary.
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response autoConfiguration() {
|
||||
return autoConfiguration(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the hg config and installs the hg binary.
|
||||
*
|
||||
* @param configDto new configuration object
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@Consumes(HgVndMediaType.CONFIG)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response autoConfiguration(HgConfigDto configDto) {
|
||||
|
||||
HgConfig config;
|
||||
|
||||
if (configDto != null) {
|
||||
config = dtoToConfigMapper.map(configDto);
|
||||
} else {
|
||||
config = new HgConfig();
|
||||
}
|
||||
|
||||
ConfigurationPermissions.write(config).check();
|
||||
|
||||
repositoryHandler.doAutoConfiguration(config);
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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 java.io.File;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class HgConfigDto extends HalRepresentation {
|
||||
|
||||
private boolean disabled;
|
||||
private File repositoryDirectory;
|
||||
|
||||
private String encoding;
|
||||
private String hgBinary;
|
||||
private String pythonBinary;
|
||||
private String pythonPath;
|
||||
private boolean useOptimizedBytecode;
|
||||
private boolean showRevisionInId;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
||||
@SuppressWarnings("squid:S3306")
|
||||
@Mapper
|
||||
public abstract class HgConfigDtoToHgConfigMapper {
|
||||
public abstract HgConfig map(HgConfigDto dto);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class HgConfigInstallationsDto extends HalRepresentation {
|
||||
|
||||
private List<String> paths;
|
||||
|
||||
public HgConfigInstallationsDto(Links links, List<String> paths) {
|
||||
super(links);
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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 de.otto.edison.hal.HalRepresentation;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.installer.HgInstallerFactory;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
||||
public class HgConfigInstallationsResource {
|
||||
|
||||
public static final String PATH_HG = "hg";
|
||||
public static final String PATH_PYTHON = "python";
|
||||
private final HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper;
|
||||
|
||||
@Inject
|
||||
public HgConfigInstallationsResource(HgConfigInstallationsToDtoMapper hgConfigInstallationsToDtoMapper) {
|
||||
this.hgConfigInstallationsToDtoMapper = hgConfigInstallationsToDtoMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hg installations.
|
||||
*/
|
||||
@GET
|
||||
@Path(PATH_HG)
|
||||
@Produces(HgVndMediaType.INSTALLATIONS)
|
||||
@TypeHint(HalRepresentation.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public HalRepresentation getHgInstallations() {
|
||||
|
||||
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
|
||||
|
||||
return hgConfigInstallationsToDtoMapper.map(
|
||||
HgInstallerFactory.createInstaller().getHgInstallations(), PATH_HG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the python installations.
|
||||
*/
|
||||
@GET
|
||||
@Path(PATH_PYTHON)
|
||||
@Produces(HgVndMediaType.INSTALLATIONS)
|
||||
@TypeHint(HalRepresentation.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public HalRepresentation getPythonInstallations() {
|
||||
|
||||
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
|
||||
|
||||
return hgConfigInstallationsToDtoMapper.map(
|
||||
HgInstallerFactory.createInstaller().getPythonInstallations(), PATH_PYTHON);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
public class HgConfigInstallationsToDtoMapper {
|
||||
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@Inject
|
||||
public HgConfigInstallationsToDtoMapper(UriInfoStore uriInfoStore) {
|
||||
this.uriInfoStore = uriInfoStore;
|
||||
}
|
||||
|
||||
public HgConfigInstallationsDto map(List<String> installations, String path) {
|
||||
return new HgConfigInstallationsDto(linkingTo().self(createSelfLink(path)).build(), installations);
|
||||
}
|
||||
|
||||
private String createSelfLink(String path) {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("getInstallationsResource").parameters().href() + '/' + path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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 de.otto.edison.hal.HalRepresentation;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.installer.HgInstallerFactory;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class HgConfigPackageResource {
|
||||
|
||||
private final HgPackageReader pkgReader;
|
||||
private final AdvancedHttpClient client;
|
||||
private final HgRepositoryHandler handler;
|
||||
private final HgConfigPackagesToDtoMapper configPackageCollectionToDtoMapper;
|
||||
|
||||
@Inject
|
||||
public HgConfigPackageResource(HgPackageReader pkgReader, AdvancedHttpClient client, HgRepositoryHandler handler,
|
||||
HgConfigPackagesToDtoMapper hgConfigPackagesToDtoMapper) {
|
||||
this.pkgReader = pkgReader;
|
||||
this.client = client;
|
||||
this.handler = handler;
|
||||
this.configPackageCollectionToDtoMapper = hgConfigPackagesToDtoMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all mercurial packages.
|
||||
*/
|
||||
@GET
|
||||
@Path("")
|
||||
@Produces(HgVndMediaType.PACKAGES)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(HalRepresentation.class)
|
||||
public HalRepresentation getPackages() {
|
||||
|
||||
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
|
||||
|
||||
return configPackageCollectionToDtoMapper.map(pkgReader.getPackages());
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a mercurial package
|
||||
*
|
||||
* @param pkgId Identifier of the package to install
|
||||
*/
|
||||
@PUT
|
||||
@Path("{pkgId}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
|
||||
@ResponseCode(code = 404, condition = "no package found for id"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response installPackage(@PathParam("pkgId") String pkgId) {
|
||||
Response response;
|
||||
|
||||
ConfigurationPermissions.write(HgConfig.PERMISSION).check();
|
||||
|
||||
HgPackage pkg = pkgReader.getPackage(pkgId);
|
||||
|
||||
if (pkg != null) {
|
||||
if (HgInstallerFactory.createInstaller()
|
||||
.installPackage(client, handler, SCMContext.getContext().getBaseDirectory(), pkg)) {
|
||||
response = Response.noContent().build();
|
||||
} else {
|
||||
response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
} else {
|
||||
response = Response.status(Response.Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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 java.util.List;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class HgConfigPackagesDto extends HalRepresentation {
|
||||
|
||||
private List<HgConfigPackageDto> packages;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public static class HgConfigPackageDto {
|
||||
|
||||
private String arch;
|
||||
private HgConfigDto hgConfigTemplate;
|
||||
private String hgVersion;
|
||||
private String id;
|
||||
private String platform;
|
||||
private String pythonVersion;
|
||||
private long size;
|
||||
private String url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackages;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
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 HgConfigPackagesToDtoMapper {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
public HgConfigPackagesDto map(HgPackages hgpackages) {
|
||||
return map(new HgPackagesNonIterable(hgpackages));
|
||||
}
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
/* Favor warning "Unmapped target property: "attributes", to packages[].hgConfigTemplate"
|
||||
Over error "Unknown property "packages[].hgConfigTemplate.attributes"
|
||||
@Mapping(target = "packages[].hgConfigTemplate.attributes", ignore = true) // Also not for nested DTOs
|
||||
*/
|
||||
protected abstract HgConfigPackagesDto map(HgPackagesNonIterable hgPackagesNonIterable);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(@MappingTarget HgConfigPackagesDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(createSelfLink());
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
private String createSelfLink() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("getPackagesResource").parameters().href();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately, HgPackages is iterable, HgConfigPackagesDto does not need to be iterable and MapStruct refuses to
|
||||
* map an iterable to a non-iterable. So use this little non-iterable "proxy".
|
||||
*/
|
||||
@Getter
|
||||
static class HgPackagesNonIterable {
|
||||
private List<HgPackage> packages;
|
||||
|
||||
HgPackagesNonIterable(HgPackages hgPackages) {
|
||||
this.packages = hgPackages.getPackages();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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 sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
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.Response;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration of the hg plugin.
|
||||
*/
|
||||
@Path(HgConfigResource.HG_CONFIG_PATH_V2)
|
||||
public class HgConfigResource {
|
||||
|
||||
static final String HG_CONFIG_PATH_V2 = "v2/config/hg";
|
||||
|
||||
private final HgConfigDtoToHgConfigMapper dtoToConfigMapper;
|
||||
private final HgConfigToHgConfigDtoMapper configToDtoMapper;
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
private final Provider<HgConfigPackageResource> packagesResource;
|
||||
private final Provider<HgConfigAutoConfigurationResource> autoconfigResource;
|
||||
private final Provider<HgConfigInstallationsResource> installationsResource;
|
||||
|
||||
@Inject
|
||||
public HgConfigResource(HgConfigDtoToHgConfigMapper dtoToConfigMapper, HgConfigToHgConfigDtoMapper configToDtoMapper,
|
||||
HgRepositoryHandler repositoryHandler, Provider<HgConfigPackageResource> packagesResource,
|
||||
Provider<HgConfigAutoConfigurationResource> autoconfigResource,
|
||||
Provider<HgConfigInstallationsResource> installationsResource) {
|
||||
this.dtoToConfigMapper = dtoToConfigMapper;
|
||||
this.configToDtoMapper = configToDtoMapper;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
this.packagesResource = packagesResource;
|
||||
this.autoconfigResource = autoconfigResource;
|
||||
this.installationsResource = installationsResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hg config.
|
||||
*/
|
||||
@GET
|
||||
@Path("")
|
||||
@Produces(HgVndMediaType.CONFIG)
|
||||
@TypeHint(HgConfigDto.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response get() {
|
||||
|
||||
ConfigurationPermissions.read(HgConfig.PERMISSION).check();
|
||||
|
||||
HgConfig config = repositoryHandler.getConfig();
|
||||
|
||||
if (config == null) {
|
||||
config = new HgConfig();
|
||||
repositoryHandler.setConfig(config);
|
||||
}
|
||||
|
||||
return Response.ok(configToDtoMapper.map(config)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the hg config.
|
||||
*
|
||||
* @param configDto new configuration object
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@Consumes(HgVndMediaType.CONFIG)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:hg\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(HgConfigDto configDto) {
|
||||
|
||||
HgConfig config = dtoToConfigMapper.map(configDto);
|
||||
|
||||
ConfigurationPermissions.write(config).check();
|
||||
|
||||
repositoryHandler.setConfig(config);
|
||||
repositoryHandler.storeConfig();
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@Path("packages")
|
||||
public HgConfigPackageResource getPackagesResource() {
|
||||
return packagesResource.get();
|
||||
}
|
||||
|
||||
@Path("auto-configuration")
|
||||
public HgConfigAutoConfigurationResource getAutoConfigurationResource() {
|
||||
return autoconfigResource.get();
|
||||
}
|
||||
|
||||
@Path("installations")
|
||||
public HgConfigInstallationsResource getInstallationsResource() {
|
||||
return installationsResource.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
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 HgConfigToHgConfigDtoMapper extends BaseMapper<HgConfig, HgConfigDto> {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(HgConfig config, @MappingTarget HgConfigDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(self());
|
||||
if (ConfigurationPermissions.write(config).isPermitted()) {
|
||||
linksBuilder.single(link("update", update()));
|
||||
}
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
private String self() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("get").parameters().href();
|
||||
}
|
||||
|
||||
private String update() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), HgConfigResource.class);
|
||||
return linkBuilder.method("update").parameters().href();
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,7 @@ public class HgPackageInstaller implements Runnable
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not downlaod file ".concat(pkg.getUrl()), ex);
|
||||
file = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -48,6 +48,8 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
public class HgConfig extends RepositoryConfig
|
||||
{
|
||||
|
||||
public static final String PERMISSION = "hg";
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
@@ -227,6 +229,6 @@ public class HgConfig extends RepositoryConfig
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
// Don't change this without migrating SCM permission configuration!
|
||||
return "hg";
|
||||
return PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,11 @@ package sonia.scm.web;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import sonia.scm.api.v2.resources.HgConfigDtoToHgConfigMapper;
|
||||
import sonia.scm.api.v2.resources.HgConfigInstallationsToDtoMapper;
|
||||
import sonia.scm.api.v2.resources.HgConfigPackagesToDtoMapper;
|
||||
import sonia.scm.api.v2.resources.HgConfigToHgConfigDtoMapper;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.HgContext;
|
||||
@@ -70,6 +74,11 @@ public class HgServletModule extends ServletModule
|
||||
bind(HgHookManager.class);
|
||||
bind(HgPackageReader.class);
|
||||
|
||||
bind(HgConfigDtoToHgConfigMapper.class).to(Mappers.getMapper(HgConfigDtoToHgConfigMapper.class).getClass());
|
||||
bind(HgConfigToHgConfigDtoMapper.class).to(Mappers.getMapper(HgConfigToHgConfigDtoMapper.class).getClass());
|
||||
bind(HgConfigPackagesToDtoMapper.class).to(Mappers.getMapper(HgConfigPackagesToDtoMapper.class).getClass());
|
||||
bind(HgConfigInstallationsToDtoMapper.class);
|
||||
|
||||
// bind servlets
|
||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
|
||||
public class HgVndMediaType {
|
||||
private static final String PREFIX = VndMediaType.PREFIX + "hgConfig";
|
||||
|
||||
public static final String CONFIG = PREFIX + VndMediaType.SUFFIX;
|
||||
public static final String PACKAGES = PREFIX + "-packages" + VndMediaType.SUFFIX;
|
||||
public static final String INSTALLATIONS = PREFIX + "-installation" + VndMediaType.SUFFIX;
|
||||
|
||||
private HgVndMediaType() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[users]
|
||||
readOnly = secret, reader
|
||||
writeOnly = secret, writer
|
||||
readWrite = secret, readerWriter
|
||||
|
||||
[roles]
|
||||
reader = configuration:read:hg
|
||||
writer = configuration:write:hg
|
||||
readerWriter = configuration:*:hg
|
||||
@@ -0,0 +1,123 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigAutoConfigurationResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigAutoConfigurationResource> resourceProvider;
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
HgConfigAutoConfigurationResource resource =
|
||||
new HgConfigAutoConfigurationResource(dtoToConfigMapper, repositoryHandler);
|
||||
|
||||
when(resourceProvider.get()).thenReturn(resource);
|
||||
dispatcher.getRegistry().addSingletonResource(
|
||||
new HgConfigResource(null, null, null, null,
|
||||
resourceProvider, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldSetDefaultConfigAndInstallHg() throws Exception {
|
||||
MockHttpResponse response = put(null);
|
||||
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
|
||||
HgConfig actualConfig = captureConfig();
|
||||
assertFalse(actualConfig.isDisabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotSetDefaultConfigAndInstallHgWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:hg]");
|
||||
|
||||
put(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldUpdateConfigAndInstallHg() throws Exception {
|
||||
MockHttpResponse response = put("{\"disabled\":true}");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
|
||||
HgConfig actualConfig = captureConfig();
|
||||
assertTrue(actualConfig.isDisabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigAndInstallHgWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:hg]");
|
||||
|
||||
put("{\"disabled\":true}");
|
||||
}
|
||||
|
||||
private MockHttpResponse put(String content) throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/auto-configuration");
|
||||
|
||||
if (content != null) {
|
||||
request
|
||||
.contentType(HgVndMediaType.CONFIG)
|
||||
.content(content.getBytes());
|
||||
}
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private HgConfig captureConfig() {
|
||||
ArgumentCaptor<HgConfig> configCaptor = ArgumentCaptor.forClass(HgConfig.class);
|
||||
verify(repositoryHandler).doAutoConfiguration(configCaptor.capture());
|
||||
return configCaptor.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigDtoToHgConfigMapperTest {
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigDtoToHgConfigMapperImpl mapper;
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
HgConfigDto dto = createDefaultDto();
|
||||
HgConfig config = mapper.map(dto);
|
||||
|
||||
assertTrue(config.isDisabled());
|
||||
assertEquals("repository/directory", config.getRepositoryDirectory().getPath());
|
||||
|
||||
assertEquals("ABC", config.getEncoding());
|
||||
assertEquals("/etc/hg", config.getHgBinary());
|
||||
assertEquals("/py", config.getPythonBinary());
|
||||
assertEquals("/etc/", config.getPythonPath());
|
||||
assertTrue(config.isShowRevisionInId());
|
||||
assertTrue(config.isUseOptimizedBytecode());
|
||||
}
|
||||
|
||||
private HgConfigDto createDefaultDto() {
|
||||
HgConfigDto configDto = new HgConfigDto();
|
||||
configDto.setDisabled(true);
|
||||
configDto.setRepositoryDirectory(new File("repository/directory"));
|
||||
configDto.setEncoding("ABC");
|
||||
configDto.setHgBinary("/etc/hg");
|
||||
configDto.setPythonBinary("/py");
|
||||
configDto.setPythonPath("/etc/");
|
||||
configDto.setShowRevisionInId(true);
|
||||
configDto.setUseOptimizedBytecode(true);
|
||||
|
||||
return configDto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigInstallationsResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigInstallationsToDtoMapper mapper;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigInstallationsResource> resourceProvider;
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
HgConfigInstallationsResource resource = new HgConfigInstallationsResource(mapper);
|
||||
|
||||
when(resourceProvider.get()).thenReturn(resource);
|
||||
dispatcher.getRegistry().addSingletonResource(
|
||||
new HgConfigResource(null, null, null, null,
|
||||
null, resourceProvider));
|
||||
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetHgInstallations() throws Exception {
|
||||
MockHttpResponse response = get("hg");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String contentAsString = response.getContentAsString();
|
||||
assertThat(contentAsString).contains("{\"paths\":[");
|
||||
assertThat(contentAsString).contains("hg");
|
||||
assertThat(contentAsString).doesNotContain("python");
|
||||
|
||||
assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/hg");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetHgInstallationsWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:hg]");
|
||||
|
||||
get("hg");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetPythonInstallations() throws Exception {
|
||||
MockHttpResponse response = get("python");
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String contentAsString = response.getContentAsString();
|
||||
assertThat(contentAsString).contains("{\"paths\":[");
|
||||
assertThat(contentAsString).contains("python");
|
||||
|
||||
assertThat(contentAsString).contains("\"self\":{\"href\":\"/v2/config/hg/installations/python");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetPythonInstallationsWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:hg]");
|
||||
|
||||
get("python");
|
||||
}
|
||||
|
||||
private MockHttpResponse get(String path) throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + path);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
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 java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigInstallationsToDtoMapperTest {
|
||||
|
||||
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigInstallationsToDtoMapper mapper;
|
||||
|
||||
private URI expectedBaseUri;
|
||||
|
||||
private String expectedPath = "path";
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/installations/" + expectedPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
List<String> installations = Arrays.asList("/hg", "/bin/hg");
|
||||
|
||||
HgConfigInstallationsDto dto = mapper.map(installations, expectedPath);
|
||||
|
||||
assertThat(dto.getPaths()).isEqualTo(installations);
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
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.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackageReader;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.createPackage;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigPackageResourceTest {
|
||||
|
||||
public static final String URI = "/" + HgConfigResource.HG_CONFIG_PATH_V2 + "/packages";
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = java.net.URI.create("/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigPackagesToDtoMapperImpl mapper;
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private HgPackageReader hgPackageReader;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private AdvancedHttpClient advancedHttpClient;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigPackageResource> hgConfigPackageResourceProvider;
|
||||
|
||||
@Mock
|
||||
private HgPackage hgPackage;
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
setupResources();
|
||||
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
|
||||
when(hgPackageReader.getPackages().getPackages()).thenReturn(createPackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetPackages() throws Exception {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String responseString = response.getContentAsString();
|
||||
ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class);
|
||||
|
||||
JsonNode packages = responseJson.get("packages");
|
||||
assertThat(packages).isNotNull();
|
||||
assertThat(packages).hasSize(2);
|
||||
|
||||
JsonNode package1 = packages.get(0);
|
||||
assertThat(package1.get("_links")).isNull();
|
||||
|
||||
JsonNode hgConfigTemplate = package1.get("hgConfigTemplate");
|
||||
assertThat(hgConfigTemplate).isNotNull();
|
||||
assertThat(hgConfigTemplate.get("_links")).isNull();
|
||||
|
||||
assertThat(responseString).contains("\"_links\":{\"self\":{\"href\":\"/v2/config/hg/packages");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetPackagesWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:hg]");
|
||||
|
||||
get();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldInstallPackage() throws Exception {
|
||||
String packgeId = "ourPackage";
|
||||
String url = "http://url";
|
||||
|
||||
setupPackageInstallation(packgeId, url);
|
||||
when(advancedHttpClient.get(url).request().contentAsStream())
|
||||
.thenReturn(new ByteArrayInputStream("mockedFile".getBytes()));
|
||||
|
||||
MockHttpResponse response = put(packgeId);
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldHandleFailingInstallation() throws Exception {
|
||||
String packgeId = "ourPackage";
|
||||
String url = "http://url";
|
||||
|
||||
setupPackageInstallation(packgeId, url);
|
||||
when(advancedHttpClient.get(url).request().contentAsStream())
|
||||
.thenThrow(new IOException("mocked Exception"));
|
||||
|
||||
MockHttpResponse response = put(packgeId);
|
||||
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldHandlePackagesThatAreNotFound() throws Exception {
|
||||
String packageId = "this-package-does-not-ex";
|
||||
when(hgPackageReader.getPackage(packageId)).thenReturn(null);
|
||||
MockHttpResponse response = put(packageId);
|
||||
assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotInstallPackageWhenNotAuthorized() throws Exception {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:hg]");
|
||||
|
||||
put("don-t-care");
|
||||
}
|
||||
|
||||
private List<HgPackage> createPackages() {
|
||||
return Arrays.asList(createPackage(), new HgPackage());
|
||||
}
|
||||
|
||||
private MockHttpResponse get() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get(URI);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private MockHttpResponse put(String pckgId) throws URISyntaxException {
|
||||
String packgeIdParam = "";
|
||||
if (pckgId != null) {
|
||||
packgeIdParam = "/" + pckgId;
|
||||
}
|
||||
MockHttpRequest request = MockHttpRequest.put(URI + packgeIdParam);
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private void setupResources() {
|
||||
HgConfigPackageResource hgConfigPackageResource =
|
||||
new HgConfigPackageResource(hgPackageReader, advancedHttpClient, repositoryHandler, mapper);
|
||||
|
||||
when(hgConfigPackageResourceProvider.get()).thenReturn(hgConfigPackageResource);
|
||||
dispatcher.getRegistry().addSingletonResource(
|
||||
new HgConfigResource(null, null, null,
|
||||
hgConfigPackageResourceProvider, null, null));
|
||||
}
|
||||
|
||||
private void setupPackageInstallation(String packgeId, String url) throws IOException {
|
||||
when(hgPackage.getId()).thenReturn(packgeId);
|
||||
when(hgPackageReader.getPackage(packgeId)).thenReturn(hgPackage);
|
||||
when(repositoryHandler.getConfig()).thenReturn(new HgConfig());
|
||||
when(hgPackage.getHgConfigTemplate()).thenReturn(new HgConfig());
|
||||
when(hgPackage.getUrl()).thenReturn(url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
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.installer.HgPackage;
|
||||
import sonia.scm.installer.HgPackages;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsPackage;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.createPackage;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigPackagesToDtoMapperTest {
|
||||
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigPackagesToDtoMapperImpl mapper;
|
||||
|
||||
private URI expectedBaseUri;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
expectedBaseUri = baseUri.resolve(HgConfigResource.HG_CONFIG_PATH_V2 + "/packages");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
HgPackages hgPackages = new HgPackages();
|
||||
hgPackages.setPackages(createPackages());
|
||||
|
||||
HgConfigPackagesDto dto = mapper.map(hgPackages);
|
||||
|
||||
assertThat(dto.getPackages()).hasSize(2);
|
||||
|
||||
HgConfigPackagesDto.HgConfigPackageDto hgPackageDto1 = dto.getPackages().get(0);
|
||||
assertEqualsPackage(hgPackageDto1);
|
||||
|
||||
HgConfigPackagesDto.HgConfigPackageDto hgPackageDto2 = dto.getPackages().get(1);
|
||||
// Just verify a random field
|
||||
assertThat(hgPackageDto2.getId()).isNull();
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
}
|
||||
|
||||
|
||||
private List<HgPackage> createPackages() {
|
||||
return Arrays.asList(createPackage(), new HgPackage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
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.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.web.HgVndMediaType;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigDtoToHgConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigToHgConfigDtoMapperImpl configToDtoMapper;
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigPackageResource> packagesResource;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigAutoConfigurationResource> autoconfigResource;
|
||||
|
||||
@Mock
|
||||
private Provider<HgConfigInstallationsResource> installationsResource;
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
HgConfig gitConfig = createConfiguration();
|
||||
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
|
||||
HgConfigResource gitConfigResource =
|
||||
new HgConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler, packagesResource,
|
||||
autoconfigResource, installationsResource);
|
||||
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetHgConfig() throws URISyntaxException, IOException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String responseString = response.getContentAsString();
|
||||
ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class);
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory"));
|
||||
assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/hg"));
|
||||
assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/hg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetHgConfigEvenWhenItsEmpty() throws URISyntaxException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
String responseString = response.getContentAsString();
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetHgConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/hg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:hg]");
|
||||
|
||||
get();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldUpdateConfig() throws URISyntaxException {
|
||||
MockHttpResponse response = put();
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:hg]");
|
||||
|
||||
put();
|
||||
}
|
||||
|
||||
private MockHttpResponse get() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + HgConfigResource.HG_CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private MockHttpResponse put() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + HgConfigResource.HG_CONFIG_PATH_V2)
|
||||
.contentType(HgVndMediaType.CONFIG)
|
||||
.content("{\"disabled\":true}".getBytes());
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private HgConfig createConfiguration() {
|
||||
HgConfig config = new HgConfig();
|
||||
config.setDisabled(false);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import sonia.scm.installer.HgPackage;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
class HgConfigTests {
|
||||
|
||||
private HgConfigTests() {
|
||||
}
|
||||
|
||||
static HgConfig createConfiguration() {
|
||||
HgConfig config = new HgConfig();
|
||||
config.setDisabled(true);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
|
||||
config.setEncoding("ABC");
|
||||
config.setHgBinary("/etc/hg");
|
||||
config.setPythonBinary("/py");
|
||||
config.setPythonPath("/etc/");
|
||||
config.setShowRevisionInId(true);
|
||||
config.setUseOptimizedBytecode(true);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static void assertEqualsConfiguration(HgConfigDto dto) {
|
||||
assertTrue(dto.isDisabled());
|
||||
assertEquals("repository/directory", dto.getRepositoryDirectory().getPath());
|
||||
|
||||
assertEquals("ABC", dto.getEncoding());
|
||||
assertEquals("/etc/hg", dto.getHgBinary());
|
||||
assertEquals("/py", dto.getPythonBinary());
|
||||
assertEquals("/etc/", dto.getPythonPath());
|
||||
assertTrue(dto.isShowRevisionInId());
|
||||
assertTrue(dto.isUseOptimizedBytecode());
|
||||
}
|
||||
|
||||
static HgPackage createPackage() {
|
||||
HgPackage hgPackage= new HgPackage();
|
||||
hgPackage.setArch("arch");
|
||||
hgPackage.setId("1");
|
||||
hgPackage.setHgVersion("2");
|
||||
hgPackage.setPlatform("someOs");
|
||||
hgPackage.setPythonVersion("3");
|
||||
hgPackage.setSize(4);
|
||||
hgPackage.setUrl("https://package");
|
||||
hgPackage.setHgConfigTemplate(createConfiguration());
|
||||
return hgPackage;
|
||||
}
|
||||
|
||||
static void assertEqualsPackage(HgConfigPackagesDto.HgConfigPackageDto dto) {
|
||||
assertEquals("arch", dto.getArch());
|
||||
assertEquals("1", dto.getId());
|
||||
assertEquals("2", dto.getHgVersion());
|
||||
assertEquals("someOs", dto.getPlatform());
|
||||
assertEquals("3", dto.getPythonVersion());
|
||||
assertEquals(4, dto.getSize());
|
||||
assertEquals("https://package", dto.getUrl());
|
||||
|
||||
assertEqualsConfiguration(dto.getHgConfigTemplate());
|
||||
assertTrue(dto.getHgConfigTemplate().getLinks().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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.HgConfig;
|
||||
|
||||
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;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.assertEqualsConfiguration;
|
||||
import static sonia.scm.api.v2.resources.HgConfigTests.createConfiguration;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HgConfigToHgConfigDtoMapperTest {
|
||||
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private HgConfigToHgConfigDtoMapperImpl 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(HgConfigResource.HG_CONFIG_PATH_V2);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
HgConfig config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:hg")).thenReturn(true);
|
||||
HgConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEqualsConfiguration(dto);
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFieldsWithoutUpdate() {
|
||||
HgConfig config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:hg")).thenReturn(false);
|
||||
HgConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertFalse(dto.getLinks().hasLink("update"));
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>sonia.scm.plugins</groupId>
|
||||
<artifactId>scm-svn-plugin</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<name>scm-svn-plugin</name>
|
||||
<packaging>smp</packaging>
|
||||
<url>https://bitbucket.org/sdorra/scm-manager</url>
|
||||
@@ -19,13 +17,6 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>${servlet.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.svnkit</groupId>
|
||||
<artifactId>svnkit</artifactId>
|
||||
@@ -44,15 +35,6 @@
|
||||
<version>${svnkit.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test scope -->
|
||||
|
||||
<dependency>
|
||||
<groupId>sonia.scm</groupId>
|
||||
<artifactId>scm-test</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- create test jar -->
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
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.repository.Compatibility;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class SvnConfigDto extends HalRepresentation {
|
||||
|
||||
private boolean disabled;
|
||||
private File repositoryDirectory;
|
||||
|
||||
private boolean enabledGZip;
|
||||
private Compatibility compatibility;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
||||
@SuppressWarnings("squid:S3306")
|
||||
@Mapper
|
||||
public abstract class SvnConfigDtoToSvnConfigMapper {
|
||||
public abstract SvnConfig map(SvnConfigDto dto);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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 sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.web.SvnVndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
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.Response;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration of the svn plugin.
|
||||
*/
|
||||
@Path(SvnConfigResource.SVN_CONFIG_PATH_V2)
|
||||
public class SvnConfigResource {
|
||||
|
||||
static final String SVN_CONFIG_PATH_V2 = "v2/config/svn";
|
||||
private final SvnConfigDtoToSvnConfigMapper dtoToConfigMapper;
|
||||
private final SvnConfigToSvnConfigDtoMapper configToDtoMapper;
|
||||
private final SvnRepositoryHandler repositoryHandler;
|
||||
|
||||
@Inject
|
||||
public SvnConfigResource(SvnConfigDtoToSvnConfigMapper dtoToConfigMapper, SvnConfigToSvnConfigDtoMapper configToDtoMapper,
|
||||
SvnRepositoryHandler repositoryHandler) {
|
||||
this.dtoToConfigMapper = dtoToConfigMapper;
|
||||
this.configToDtoMapper = configToDtoMapper;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the svn config.
|
||||
*/
|
||||
@GET
|
||||
@Path("")
|
||||
@Produces(SvnVndMediaType.SVN_CONFIG)
|
||||
@TypeHint(SvnConfigDto.class)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:read:svn\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response get() {
|
||||
|
||||
SvnConfig config = repositoryHandler.getConfig();
|
||||
|
||||
if (config == null) {
|
||||
config = new SvnConfig();
|
||||
repositoryHandler.setConfig(config);
|
||||
}
|
||||
|
||||
ConfigurationPermissions.read(config).check();
|
||||
|
||||
return Response.ok(configToDtoMapper.map(config)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the svn config.
|
||||
*
|
||||
* @param configDto new configuration object
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@Consumes(SvnVndMediaType.SVN_CONFIG)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 204, condition = "update success"),
|
||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"configuration:write:svn\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(SvnConfigDto configDto) {
|
||||
|
||||
SvnConfig config = dtoToConfigMapper.map(configDto);
|
||||
|
||||
ConfigurationPermissions.write(config).check();
|
||||
|
||||
repositoryHandler.setConfig(config);
|
||||
repositoryHandler.storeConfig();
|
||||
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.config.ConfigurationPermissions;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
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 SvnConfigToSvnConfigDtoMapper extends BaseMapper<SvnConfig, SvnConfigDto> {
|
||||
|
||||
@Inject
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(SvnConfig config, @MappingTarget SvnConfigDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(self());
|
||||
if (ConfigurationPermissions.write(config).isPermitted()) {
|
||||
linksBuilder.single(link("update", update()));
|
||||
}
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
private String self() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), SvnConfigResource.class);
|
||||
return linkBuilder.method("get").parameters().href();
|
||||
}
|
||||
|
||||
private String update() {
|
||||
LinkBuilder linkBuilder = new LinkBuilder(uriInfoStore.get(), SvnConfigResource.class);
|
||||
return linkBuilder.method("update").parameters().href();
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
public class SvnConfig extends RepositoryConfig
|
||||
{
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // This might be needed for permission checking
|
||||
public static final String PERMISSION = "svn";
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -112,6 +115,6 @@ public class SvnConfig extends RepositoryConfig
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
// Don't change this without migrating SCM permission configuration!
|
||||
return "svn";
|
||||
return PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,16 @@ package sonia.scm.web;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import sonia.scm.api.v2.resources.SvnConfigDtoToSvnConfigMapper;
|
||||
import sonia.scm.api.v2.resources.SvnConfigToSvnConfigDtoMapper;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.web.filter.AuthenticationFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -72,6 +73,9 @@ public class SvnServletModule extends ServletModule
|
||||
filter(PATTERN_SVN).through(SvnBasicAuthenticationFilter.class);
|
||||
filter(PATTERN_SVN).through(SvnPermissionFilter.class);
|
||||
|
||||
bind(SvnConfigDtoToSvnConfigMapper.class).to(Mappers.getMapper(SvnConfigDtoToSvnConfigMapper.class).getClass());
|
||||
bind(SvnConfigToSvnConfigDtoMapper.class).to(Mappers.getMapper(SvnConfigToSvnConfigDtoMapper.class).getClass());
|
||||
|
||||
Map<String, String> parameters = new HashMap<String, String>();
|
||||
|
||||
parameters.put(PARAMETER_SVN_PARENTPATH,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
public class SvnVndMediaType {
|
||||
public static final String SVN_CONFIG = VndMediaType.PREFIX + "svnConfig" + VndMediaType.SUFFIX;
|
||||
|
||||
private SvnVndMediaType() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SvnConfigDtoToSvnConfigMapperTest {
|
||||
|
||||
@InjectMocks
|
||||
private SvnConfigDtoToSvnConfigMapperImpl mapper;
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
SvnConfigDto dto = createDefaultDto();
|
||||
SvnConfig config = mapper.map(dto);
|
||||
|
||||
assertTrue(config.isDisabled());
|
||||
assertEquals("repository/directory", config.getRepositoryDirectory().getPath());
|
||||
|
||||
assertEquals(Compatibility.PRE15, config.getCompatibility());
|
||||
assertTrue(config.isEnabledGZip());
|
||||
}
|
||||
|
||||
private SvnConfigDto createDefaultDto() {
|
||||
SvnConfigDto configDto = new SvnConfigDto();
|
||||
configDto.setDisabled(true);
|
||||
configDto.setRepositoryDirectory(new File("repository/directory"));
|
||||
configDto.setCompatibility(Compatibility.PRE15);
|
||||
configDto.setEnabledGZip(true);
|
||||
|
||||
return configDto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
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.junit.rules.ExpectedException;
|
||||
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.SvnConfig;
|
||||
import sonia.scm.repository.SvnRepositoryHandler;
|
||||
import sonia.scm.web.SvnVndMediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(
|
||||
configuration = "classpath:sonia/scm/configuration/shiro.ini",
|
||||
password = "secret"
|
||||
)
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SvnConfigResourceTest {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
||||
|
||||
private final URI baseUri = URI.create("/");
|
||||
|
||||
@InjectMocks
|
||||
private SvnConfigDtoToSvnConfigMapperImpl dtoToConfigMapper;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private SvnConfigToSvnConfigDtoMapperImpl configToDtoMapper;
|
||||
|
||||
@Mock
|
||||
private SvnRepositoryHandler repositoryHandler;
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
SvnConfig gitConfig = createConfiguration();
|
||||
when(repositoryHandler.getConfig()).thenReturn(gitConfig);
|
||||
SvnConfigResource gitConfigResource = new SvnConfigResource(dtoToConfigMapper, configToDtoMapper, repositoryHandler);
|
||||
dispatcher.getRegistry().addSingletonResource(gitConfigResource);
|
||||
when(uriInfoStore.get().getBaseUri()).thenReturn(baseUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetSvnConfig() throws URISyntaxException, IOException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
String responseString = response.getContentAsString();
|
||||
ObjectNode responseJson = new ObjectMapper().readValue(responseString, ObjectNode.class);
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
assertTrue(responseJson.get("repositoryDirectory").asText().endsWith("repository/directory"));
|
||||
assertTrue(responseString.contains("\"self\":{\"href\":\"/v2/config/svn"));
|
||||
assertTrue(responseString.contains("\"update\":{\"href\":\"/v2/config/svn"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readWrite")
|
||||
public void shouldGetSvnConfigEvenWhenItsEmpty() throws URISyntaxException, IOException {
|
||||
when(repositoryHandler.getConfig()).thenReturn(null);
|
||||
|
||||
MockHttpResponse response = get();
|
||||
String responseString = response.getContentAsString();
|
||||
|
||||
assertTrue(responseString.contains("\"disabled\":false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldGetSvnConfigWithoutUpdateLink() throws URISyntaxException {
|
||||
MockHttpResponse response = get();
|
||||
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
|
||||
assertFalse(response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/svn"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:read:svn]");
|
||||
|
||||
get();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldUpdateConfig() throws URISyntaxException {
|
||||
MockHttpResponse response = put();
|
||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
thrown.expectMessage("Subject does not have permission [configuration:write:svn]");
|
||||
|
||||
put();
|
||||
}
|
||||
|
||||
private MockHttpResponse get() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + SvnConfigResource.SVN_CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private MockHttpResponse put() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + SvnConfigResource.SVN_CONFIG_PATH_V2)
|
||||
.contentType(SvnVndMediaType.SVN_CONFIG)
|
||||
.content("{\"disabled\":true}".getBytes());
|
||||
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private SvnConfig createConfiguration() {
|
||||
SvnConfig config = new SvnConfig();
|
||||
config.setDisabled(false);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
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.Compatibility;
|
||||
import sonia.scm.repository.SvnConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
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;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SvnConfigToSvnConfigDtoMapperTest {
|
||||
|
||||
private URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private UriInfoStore uriInfoStore;
|
||||
|
||||
@InjectMocks
|
||||
private SvnConfigToSvnConfigDtoMapperImpl 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(SvnConfigResource.SVN_CONFIG_PATH_V2);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
SvnConfig config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:svn")).thenReturn(true);
|
||||
SvnConfigDto dto = mapper.map(config);
|
||||
|
||||
assertTrue(dto.isDisabled());
|
||||
assertEquals("repository/directory", dto.getRepositoryDirectory().getPath());
|
||||
|
||||
assertEquals(Compatibility.PRE15, dto.getCompatibility());
|
||||
assertTrue(dto.isEnabledGZip());
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFieldsWithoutUpdate() {
|
||||
SvnConfig config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:svn")).thenReturn(false);
|
||||
SvnConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertFalse(dto.getLinks().hasLink("update"));
|
||||
}
|
||||
|
||||
private SvnConfig createConfiguration() {
|
||||
SvnConfig config = new SvnConfig();
|
||||
config.setDisabled(true);
|
||||
config.setRepositoryDirectory(new File("repository/directory"));
|
||||
|
||||
config.setCompatibility(Compatibility.PRE15);
|
||||
config.setEnabledGZip(true);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[users]
|
||||
readOnly = secret, reader
|
||||
writeOnly = secret, writer
|
||||
readWrite = secret, readerWriter
|
||||
|
||||
[roles]
|
||||
reader = configuration:read:svn
|
||||
writer = configuration:write:svn
|
||||
readerWriter = configuration:*:svn
|
||||
@@ -141,7 +141,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/scm-server-app.xml</descriptor>
|
||||
|
||||
@@ -31,19 +31,20 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>shiro-unit</artifactId>
|
||||
<scope>test</scope>
|
||||
<!-- scm-test is test scoped in other modules and they might need shiro unit for their tests. -->
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mokito.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -14,5 +14,7 @@ export type Group = Collection & {
|
||||
members: string[],
|
||||
_embedded: {
|
||||
members: Member[]
|
||||
}
|
||||
},
|
||||
creationDate?: string,
|
||||
lastModified?: string
|
||||
};
|
||||
|
||||
@@ -78,24 +78,6 @@
|
||||
|
||||
<!-- json -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-jaxb-annotations</artifactId>
|
||||
@@ -117,48 +99,37 @@
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- rest api -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxb-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-multipart-provider</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-guice</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-servlet-initializer</artifactId>
|
||||
<version>${resteasy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.otto.edison</groupId>
|
||||
<artifactId>edison-hal</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- injection -->
|
||||
@@ -256,14 +227,6 @@
|
||||
<version>${mustache.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- rest documentation -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-core-annotations</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test scope -->
|
||||
|
||||
<dependency>
|
||||
@@ -316,7 +279,6 @@
|
||||
|
||||
<!-- core plugins -->
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>shiro-unit</artifactId>
|
||||
@@ -387,20 +349,12 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.18</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -557,10 +511,8 @@
|
||||
<scm.home>target/scm-it</scm.home>
|
||||
<environment.profile>default</environment.profile>
|
||||
<selenium.version>2.53.1</selenium.version>
|
||||
<enunciate.version>2.9.1</enunciate.version>
|
||||
<wagon.version>1.0</wagon.version>
|
||||
<mustache.version>0.8.17</mustache.version>
|
||||
<resteasy.version>3.1.3.Final</resteasy.version>
|
||||
<netbeans.hint.deploy.server>Tomcat</netbeans.hint.deploy.server>
|
||||
<sonar.issue.ignore.multicriteria>e1</sonar.issue.ignore.multicriteria>
|
||||
<sonar.issue.ignore.multicriteria.e1.ruleKey>javascript:S3827</sonar.issue.ignore.multicriteria.e1.ruleKey>
|
||||
@@ -812,7 +764,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-enunciate-configuration</id>
|
||||
@@ -839,7 +790,6 @@
|
||||
<plugin>
|
||||
<groupId>com.webcohesion.enunciate</groupId>
|
||||
<artifactId>enunciate-maven-plugin</artifactId>
|
||||
<version>${enunciate.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@@ -876,7 +826,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/doc/assembly.xml</descriptor>
|
||||
|
||||
@@ -33,6 +33,7 @@ public class ConfigDto extends HalRepresentation {
|
||||
private String pluginUrl;
|
||||
private long loginAttemptLimitTimeout;
|
||||
private boolean enabledXsrfProtection;
|
||||
private String defaultNamespaceStrategy;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
|
||||
@@ -14,9 +14,7 @@ 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;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage the configuration.
|
||||
@@ -46,7 +44,7 @@ public class ConfigResource {
|
||||
@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 = 403, condition = "not authorized, the current user does not have the \"configuration:read:global\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response get() {
|
||||
@@ -61,19 +59,19 @@ public class ConfigResource {
|
||||
/**
|
||||
* Modifies the global scm config.
|
||||
*
|
||||
* @param configDto new global scm configuration as DTO
|
||||
* @param configDto new configuration object
|
||||
*/
|
||||
@PUT
|
||||
@Path("")
|
||||
@Consumes(VndMediaType.CONFIG)
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 201, condition = "update success"),
|
||||
@ResponseCode(code = 204, 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 = 403, condition = "not authorized, the current user does not have the \"configuration:write:global\" privilege"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(ConfigDto configDto, @Context UriInfo uriInfo) {
|
||||
public Response update(ConfigDto 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.
|
||||
|
||||
@@ -24,6 +24,7 @@ public class GroupDto extends HalRepresentation {
|
||||
private List<String> members;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
|
||||
@@ -15,13 +15,11 @@ 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 ScmConfigurationToConfigDtoMapper {
|
||||
public abstract class ScmConfigurationToConfigDtoMapper extends BaseMapper<ScmConfiguration, ConfigDto> {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
public abstract ConfigDto map(ScmConfiguration config);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self());
|
||||
|
||||
@@ -51,6 +51,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
assertEquals("https://plug.ins" , config.getPluginUrl());
|
||||
assertEquals(40 , config.getLoginAttemptLimitTimeout());
|
||||
assertTrue(config.isEnabledXsrfProtection());
|
||||
assertEquals("username", config.getDefaultNamespaceStrategy());
|
||||
}
|
||||
|
||||
private ConfigDto createDefaultDto() {
|
||||
@@ -75,6 +76,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
configDto.setPluginUrl("https://plug.ins");
|
||||
configDto.setLoginAttemptLimitTimeout(40);
|
||||
configDto.setEnabledXsrfProtection(true);
|
||||
configDto.setDefaultNamespaceStrategy("username");
|
||||
|
||||
return configDto;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
@SubjectAware(
|
||||
@@ -72,7 +70,7 @@ public class ConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "writeOnly")
|
||||
public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException {
|
||||
public void shouldNotGetConfigWhenNotAuthorized() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@@ -96,8 +94,7 @@ public class ConfigResourceTest {
|
||||
|
||||
request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2);
|
||||
response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
dispatcher.invoke(request, response); assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||
assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\""));
|
||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config"));
|
||||
assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config"));
|
||||
@@ -105,7 +102,7 @@ public class ConfigResourceTest {
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "readOnly")
|
||||
public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException {
|
||||
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
|
||||
URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json");
|
||||
byte[] configJson = Resources.toByteArray(url);
|
||||
MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2)
|
||||
|
||||
@@ -81,6 +81,7 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
||||
assertEquals("pluginurl" , dto.getPluginUrl());
|
||||
assertEquals(2 , dto.getLoginAttemptLimitTimeout());
|
||||
assertTrue(dto.isEnabledXsrfProtection());
|
||||
assertEquals("username", dto.getDefaultNamespaceStrategy());
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||
@@ -120,6 +121,7 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
||||
config.setPluginUrl("pluginurl");
|
||||
config.setLoginAttemptLimitTimeout(2);
|
||||
config.setEnabledXsrfProtection(true);
|
||||
config.setDefaultNamespaceStrategy("username");
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ writeOnly = secret, writer
|
||||
readWrite = secret, readerWriter
|
||||
|
||||
[roles]
|
||||
reader = configuration:read
|
||||
writer = configuration:write
|
||||
readerWriter = configuration:*
|
||||
reader = configuration:read:global
|
||||
writer = configuration:write:global
|
||||
readerWriter = configuration:*:global
|
||||
|
||||
11
scm.iml
11
scm.iml
@@ -12,10 +12,11 @@
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-all:1.10.19" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.github.cloudogu:ces-build-lib:9aadeeb" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.cloudbees:groovy-cps:1.21" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.guava:guava:11.0.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.codehaus.groovy:groovy-all:2.4.11" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.10.0" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.github.cloudogu:ces-build-lib:9aadeeb" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.cloudbees:groovy-cps:1.21" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:11.0.1" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.codehaus.groovy:groovy-all:2.4.11" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in New Issue
Block a user